/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.toSet; import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UiThread; import android.annotation.UserIdInt; import android.app.Activity; import android.app.Service; import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; import android.graphics.Canvas; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Dumpable; import android.util.Log; import android.util.Slog; import android.view.View; import android.view.ViewStructure; import android.view.WindowManager; import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.RingBuffer; import com.android.internal.util.SyncResultReceiver; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; /** *
Provides additional ways for apps to integrate with the content capture subsystem. * *
Content capture provides real-time, continuous capture of application activity, display and * events to an intelligence service that is provided by the Android system. The intelligence * service then uses that info to mediate and speed user journey through different apps. For * example, when the user receives a restaurant address in a chat app and switches to a map app * to search for that restaurant, the intelligence service could offer an autofill dialog to * let the user automatically select its address. * *
Content capture was designed with two major concerns in mind: privacy and performance. * *
In fact, before using this manager, the app developer should check if it's available. Example: *
* ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class);
* if (mgr != null && mgr.isContentCaptureEnabled()) {
* // ...
* }
*
*
* App developers usually don't need to explicitly interact with content capture, except when the * app: * *
The main integration point with content capture is the {@link ContentCaptureSession}. A "main" * session is automatically created by the Android System when content capture is enabled for the * activity and its used by the standard Android views to notify the content capture service of * events such as views being added, views been removed, and text changed by user input. The session * could have a {@link ContentCaptureContext} to provide more contextual info about it, such as * the locus associated with the view hierarchy (see {@link android.content.LocusId} for more info * about locus). By default, the main session doesn't have a {@code ContentCaptureContext}, but you * can change it after its created. Example: * *
* protected void onCreate(Bundle savedInstanceState) {
* // Initialize view structure
* ContentCaptureSession session = rootView.getContentCaptureSession();
* if (session != null) {
* session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB"));
* }
* }
*
*
* If your activity contains view hierarchies with a different contextual meaning, you should * created child sessions for each view hierarchy root. For example, if your activity is a browser, * you could use the main session for the main URL being rendered, then child sessions for each * {@code IFRAME}: * *
* ContentCaptureSession mMainSession;
*
* protected void onCreate(Bundle savedInstanceState) {
* // Initialize view structure...
* mMainSession = rootView.getContentCaptureSession();
* if (mMainSession != null) {
* mMainSession.setContentCaptureContext(
* ContentCaptureContext.forLocusId("https://example.com"));
* }
* }
*
* private void loadIFrame(View iframeRootView, String url) {
* if (mMainSession != null) {
* ContentCaptureSession iFrameSession = mMainSession.newChild(
* ContentCaptureContext.forLocusId(url));
* }
* iframeRootView.setContentCaptureSession(iFrameSession);
* }
* // Load iframe...
* }
*
*
* If your activity has custom views (i.e., views that extend {@link View} directly and provide * just one logical view, not a virtual tree hiearchy) and it provides content that's relevant for * content capture (as of {@link android.os.Build.VERSION_CODES#Q Android Q}, the only relevant * content is text), then your view implementation should: * *
Here's an example of the relevant methods for an {@code EditText}-like view: * *
* public class MyEditText extends View {
*
* public MyEditText(...) {
* if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
* setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
* }
* }
*
* public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
* super.onProvideContentCaptureStructure(structure, flags);
*
* structure.setText(getText(), getSelectionStart(), getSelectionEnd());
* structure.setHint(getHint());
* structure.setInputType(getInputType());
* // set other properties like setTextIdEntry(), setTextLines(), setTextStyle(),
* // setMinTextEms(), setMaxTextEms(), setMaxTextLength()
* }
*
* private void onTextChanged() {
* if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
* ContentCaptureManager mgr = mContext.getSystemService(ContentCaptureManager.class);
* if (cm != null && cm.isContentCaptureEnabled()) {
* ContentCaptureSession session = getContentCaptureSession();
* if (session != null) {
* session.notifyViewTextChanged(getAutofillId(), getText());
* }
* }
* }
*
*
* If your view provides its own virtual hierarchy (for example, if it's a browser that draws * the HTML using {@link Canvas} or native libraries in a different render process), then the view * is also responsible to notify the session when the virtual elements appear and disappear - see * {@link View#onProvideContentCaptureStructure(ViewStructure, int)} for more info. */ @SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE) public final class ContentCaptureManager { private static final String TAG = ContentCaptureManager.class.getSimpleName(); /** @hide */ public static final boolean DEBUG = false; /** @hide */ @TestApi public static final String DUMPABLE_NAME = "ContentCaptureManager"; /** Error happened during the data sharing session. */ public static final int DATA_SHARE_ERROR_UNKNOWN = 1; /** Request has been rejected, because a concurrent data share sessions is in progress. */ public static final int DATA_SHARE_ERROR_CONCURRENT_REQUEST = 2; /** Request has been interrupted because of data share session timeout. */ public static final int DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 3; /** @hide */ @IntDef(flag = false, value = { DATA_SHARE_ERROR_UNKNOWN, DATA_SHARE_ERROR_CONCURRENT_REQUEST, DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED }) @Retention(RetentionPolicy.SOURCE) public @interface DataShareError {} /** @hide */ public static final int RESULT_CODE_OK = 0; /** @hide */ public static final int RESULT_CODE_TRUE = 1; /** @hide */ public static final int RESULT_CODE_FALSE = 2; /** @hide */ public static final int RESULT_CODE_SECURITY_EXCEPTION = -1; /** * ID used to indicate that a session does not exist * @hide */ @SystemApi public static final int NO_SESSION_ID = 0; /** * Timeout for calls to system_server. */ private static final int SYNC_CALLS_TIMEOUT_MS = 5000; /** * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide * whether the content capture service should be created or not * *
By default it should *NOT* be set (or set to {@code "default"}, so the decision is based * on whether the OEM provides an implementation for the service), but it can be overridden to: * *
Set it to {@code 0} or less to disable history. * * @hide */ @TestApi public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size"; /** * Sets the logging level for {@code logcat} statements. * *
Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and * {@link #LOGGING_LEVEL_VERBOSE}. * * @hide */ @TestApi public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level"; /** * Sets how long (in ms) the service is bound while idle. * *
Use {@code 0} to keep it permanently bound.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
/**
* Sets to disable flush when receiving a VIEW_TREE_APPEARING event.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
"disable_flush_for_view_tree_appearing";
/**
* Enables the content protection receiver.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER =
"enable_content_protection_receiver";
/**
* Sets the size of the in-memory ring buffer for the content protection flow.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
"content_protection_buffer_size";
/**
* Sets the config for content protection required groups.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG =
"content_protection_required_groups_config";
/**
* Sets the config for content protection optional groups.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG =
"content_protection_optional_groups_config";
/**
* Sets the threshold for content protection optional groups.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD =
"content_protection_optional_groups_threshold";
/**
* Sets the initial delay for fetching content protection allowlist in milliseconds.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS =
"content_protection_allowlist_delay_ms";
/**
* Sets the timeout for fetching content protection allowlist in milliseconds.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS =
"content_protection_allowlist_timeout_ms";
/**
* Sets the auto disconnect timeout for the content protection service in milliseconds.
*
* @hide
*/
// Unit can't be in the name in order to pass the checkstyle hook, line would be too long.
public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT =
"content_protection_auto_disconnect_timeout_ms";
/** @hide */
@TestApi
public static final int LOGGING_LEVEL_OFF = 0;
/** @hide */
@TestApi
public static final int LOGGING_LEVEL_DEBUG = 1;
/** @hide */
@TestApi
public static final int LOGGING_LEVEL_VERBOSE = 2;
/** @hide */
@IntDef(flag = false, value = {
LOGGING_LEVEL_OFF,
LOGGING_LEVEL_DEBUG,
LOGGING_LEVEL_VERBOSE
})
@Retention(RetentionPolicy.SOURCE)
public @interface LoggingLevel {}
/** @hide */
public static final int DEFAULT_MAX_BUFFER_SIZE = 500; // Enough for typical busy screen.
/** @hide */
public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000;
/** @hide */
public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
/** @hide */
public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
/** @hide */
public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
/** @hide */
public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true;
/** @hide */
public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
/** @hide */
public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
/** @hide */
public static final List By default there's just one (associated with the activity lifecycle), but apps could
* explicitly add more using
* {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
*
* @hide
*/
@NonNull
@UiThread
public ContentCaptureSession getMainContentCaptureSession() {
synchronized (mLock) {
if (mMainSession == null) {
mMainSession = prepareMainSession();
if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
}
return mMainSession;
}
}
@NonNull
@GuardedBy("mLock")
private ContentCaptureSession prepareMainSession() {
if (runOnBackgroundThreadEnabled()) {
return new MainContentCaptureSessionV2(
mContext,
this,
prepareUiHandler(),
prepareContentCaptureHandler(),
mService
);
} else {
return new MainContentCaptureSession(mContext, this, prepareUiHandler(), mService);
}
}
@NonNull
@GuardedBy("mLock")
private Handler prepareContentCaptureHandler() {
if (mContentCaptureHandler == null) {
mContentCaptureHandler = BackgroundThread.getHandler();
}
return mContentCaptureHandler;
}
@NonNull
@GuardedBy("mLock")
private Handler prepareUiHandler() {
if (mUiHandler == null) {
mUiHandler = Handler.createAsync(Looper.getMainLooper());
}
return mUiHandler;
}
/** @hide */
@UiThread
public void onActivityCreated(@NonNull IBinder applicationToken,
@NonNull IBinder shareableActivityToken, @NonNull ComponentName activityComponent) {
if (mOptions.lite) return;
synchronized (mLock) {
getMainContentCaptureSession().start(applicationToken, shareableActivityToken,
activityComponent, mFlags);
}
}
/** @hide */
@UiThread
public void onActivityResumed() {
if (mOptions.lite) return;
getMainContentCaptureSession().notifySessionResumed();
}
/** @hide */
@UiThread
public void onActivityPaused() {
if (mOptions.lite) return;
getMainContentCaptureSession().notifySessionPaused();
}
/** @hide */
@UiThread
public void onActivityDestroyed() {
if (mOptions.lite) return;
getMainContentCaptureSession().destroy();
}
/**
* Flushes the content of all sessions.
*
* Typically called by {@code Activity} when it's paused / resumed.
*
* @hide
*/
@UiThread
public void flush(@FlushReason int reason) {
if (mOptions.lite) return;
getMainContentCaptureSession().flush(reason);
}
/**
* Returns the component name of the system service that is consuming the captured events for
* the current user.
*
* @throws RuntimeException if getting the component name is timed out.
*/
@Nullable
public ComponentName getServiceComponentName() {
if (!isContentCaptureEnabled() && !mOptions.lite) return null;
final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.getServiceComponentName(resultReceiver);
return resultReceiver.getParcelableResult();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
throw new RuntimeException("Fail to get service componentName.");
}
}
/**
* Gets the (optional) intent used to launch the service-specific settings.
*
* This method is static because it's called by Settings, which might not be allowlisted
* for content capture (in which case the ContentCaptureManager on its context would be null).
*
* @hide
*/
// TODO: use "lite" options as it's done by activities from the content capture service
@Nullable
public static ComponentName getServiceSettingsComponentName() {
final IBinder binder = ServiceManager
.checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
if (binder == null) return null;
final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder);
final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
service.getServiceSettingsActivity(resultReceiver);
final int resultCode = resultReceiver.getIntResult();
if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
throw new SecurityException(resultReceiver.getStringResult());
}
return resultReceiver.getParcelableResult();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
Log.e(TAG, "Fail to get service settings componentName: " + e);
return null;
}
}
/**
* Checks whether content capture is enabled for this activity.
*
* There are many reasons it could be disabled, such as:
* This method is typically used by web browsers so they don't generate unnecessary content
* capture events for websites the content capture service is not interested on.
*
* @return list of conditions, or {@code null} if the service didn't set any restriction
* (in which case content capture events should always be generated). If the list is empty,
* then it should not generate any event at all.
*/
@Nullable
public Set Note: this call is not persisted accross reboots, so apps should typically call
* it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
*/
public void setContentCaptureEnabled(boolean enabled) {
if (sDebug) {
Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
}
ContentCaptureSession mainSession;
synchronized (mLock) {
if (enabled) {
mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
} else {
mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP;
}
mainSession = mMainSession;
}
if (mainSession != null) {
mainSession.setDisabled(!enabled);
}
}
/**
* Called by apps to update flag secure when window attributes change.
*
* @hide
*/
public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) {
if (sDebug) {
Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags);
}
final boolean flagSecureEnabled =
(params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
ContentCaptureSession mainSession;
boolean alreadyDisabledByApp;
synchronized (mLock) {
alreadyDisabledByApp = (mFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0;
if (flagSecureEnabled) {
mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
} else {
mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
}
mainSession = mMainSession;
}
// Prevent overriding the status of disabling by app
if (mainSession != null && !alreadyDisabledByApp) {
mainSession.setDisabled(flagSecureEnabled);
}
}
/**
* Explicitly sets enable or disable flush for view tree appearing event.
*
* @hide
*/
@VisibleForTesting
public void setFlushViewTreeAppearingEventDisabled(boolean disabled) {
if (sDebug) {
Log.d(TAG, "setFlushViewTreeAppearingEventDisabled(): setting to " + disabled);
}
synchronized (mLock) {
if (disabled) {
mFlags |= ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
} else {
mFlags &= ~ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
}
}
}
/**
* Gets whether content capture is needed to flush for view tree appearing event.
*
* @hide
*/
public boolean getFlushViewTreeAppearingEventDisabled() {
synchronized (mLock) {
return (mFlags & ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING)
!= 0;
}
}
/**
* Gets whether content capture is enabled for the given user.
*
* This method is typically used by the content capture service settings page, so it can
* provide a toggle to enable / disable it.
*
* @throws SecurityException if caller is not the app that owns the content capture service
* associated with the user.
*
* @hide
*/
@SystemApi
public boolean isContentCaptureFeatureEnabled() {
final SyncResultReceiver resultReceiver = syncRun(
(r) -> mService.isContentCaptureFeatureEnabled(r));
try {
final int resultCode = resultReceiver.getIntResult();
switch (resultCode) {
case RESULT_CODE_TRUE:
return true;
case RESULT_CODE_FALSE:
return false;
default:
Log.wtf(TAG, "received invalid result: " + resultCode);
return false;
}
} catch (SyncResultReceiver.TimeoutException e) {
Log.e(TAG, "Fail to get content capture feature enable status: " + e);
return false;
}
}
/**
* Called by the app to request the content capture service to remove content capture data
* associated with some context.
*
* @param request object specifying what user data should be removed.
*/
public void removeData(@NonNull DataRemovalRequest request) {
Objects.requireNonNull(request);
try {
mService.removeData(request);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Called by the app to request data sharing via writing to a file.
*
* The ContentCaptureService app will receive a read-only file descriptor pointing to the
* same file and will be able to read data being shared from it.
*
* Note: using this API doesn't guarantee the app staying alive and is "best-effort".
* Starting a foreground service would minimize the chances of the app getting killed during the
* file sharing session.
*
* @param request object specifying details of the data being shared.
*/
public void shareData(@NonNull DataShareRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull DataShareWriteAdapter dataShareWriteAdapter) {
Objects.requireNonNull(request);
Objects.requireNonNull(dataShareWriteAdapter);
Objects.requireNonNull(executor);
try {
mService.shareData(request,
new DataShareAdapterDelegate(executor, dataShareWriteAdapter,
mDataShareAdapterResourceManager));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Runs a sync method in the service, properly handling exceptions.
*
* @throws SecurityException if caller is not allowed to execute the method.
*/
@NonNull
private SyncResultReceiver syncRun(@NonNull MyRunnable r) {
final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
r.run(resultReceiver);
final int resultCode = resultReceiver.getIntResult();
if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
throw new SecurityException(resultReceiver.getStringResult());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SyncResultReceiver.TimeoutException e) {
throw new RuntimeException("Fail to get syn run result from SyncResultReceiver.");
}
return resultReceiver;
}
/** @hide */
public void addDumpable(Activity activity) {
if (mDumpable == null) {
mDumpable = new Dumper();
}
activity.addDumpable(mDumpable);
}
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Nullable
public RingBuffer> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS =
Collections.emptyList();
/** @hide */
public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = "";
/** @hide */
public static final List
> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS =
Collections.emptyList();
/** @hide */
public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = "";
/** @hide */
public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0;
/** @hide */
public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 30000;
/** @hide */
public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 250;
/** @hide */
public static final long DEFAULT_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT_MS = 3000;
private final Object mLock = new Object();
@NonNull
private final StrippedContext mContext;
@NonNull
private final IContentCaptureManager mService;
@GuardedBy("mLock")
private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager;
@NonNull
final ContentCaptureOptions mOptions;
// Flags used for starting session.
@GuardedBy("mLock")
private int mFlags;
@Nullable
@GuardedBy("mLock")
private Handler mUiHandler;
@Nullable
@GuardedBy("mLock")
private Handler mContentCaptureHandler;
@GuardedBy("mLock")
private ContentCaptureSession mMainSession;
@Nullable // set on-demand by addDumpable()
private Dumper mDumpable;
// Created here in order to live across activity and session changes
@Nullable private final RingBuffer
*
*/
public boolean isContentCaptureEnabled() {
if (mOptions.lite) return false;
final ContentCaptureSession mainSession;
synchronized (mLock) {
mainSession = mMainSession;
}
// The main session is only set when the activity starts, so we need to return true until
// then.
if (mainSession != null && mainSession.isDisabled()) return false;
return true;
}
/**
* Gets the list of conditions for when content capture should be allowed.
*
*