Loading core/java/android/content/LocusId.java +37 −6 Original line number Diff line number Diff line Loading @@ -18,19 +18,50 @@ package android.content; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.view.contentcapture.ContentCaptureManager; import com.android.internal.util.Preconditions; import java.io.PrintWriter; /** * Identifier for an unique state in the application. * An identifier for an unique state (locus) in the application. Should be stable across reboots and * backup / restore. * * <p>Should be stable across reboots and backup / restore. * <p>Locus is a new concept introduced on * {@link android.os.Build.VERSION_CODES#Q Android Q} and it lets the intelligence service provided * by the Android System to correlate state between different subsystems such as content capture, * shortcuts, and notifications. * * <p>For example, a chat app could use the context to resume a conversation between 2 users. * <p>For example, if your app provides an activiy representing a chat between 2 users * (say {@code A} and {@code B}, this chat state could be represented by: * * <pre><code> * LocusId chatId = new LocusId("Chat_A_B"); * </code></pre> * * <p>And then you should use that {@code chatId} by: * * <ul> * <li>Setting it in the chat notification (through * {@link android.app.Notification.Builder#setLocusId(LocusId) * Notification.Builder.setLocusId(chatId)}). * <li>Setting it into the {@link android.content.pm.ShortcutInfo} (through * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(LocusId) * ShortcutInfo.Builder.setLocusId(chatId)}), if you provide a launcher shortcut for that chat * conversation. * <li>Associating it with the {@link android.view.contentcapture.ContentCaptureContext} of the * root view of the chat conversation activity (through * {@link android.view.View#getContentCaptureSession()}, then * {@link android.view.contentcapture.ContentCaptureContext.Builder * new ContentCaptureContext.Builder(chatId).build()} and * {@link android.view.contentcapture.ContentCaptureSession#setContentCaptureContext( * android.view.contentcapture.ContentCaptureContext)} - see {@link ContentCaptureManager} * for more info about content capture). * <li>Configuring your app to launch the chat conversation through the * {@link Intent#ACTION_VIEW_LOCUS} intent. * </ul> */ // TODO(b/123577059): make sure this is well documented and understandable public final class LocusId implements Parcelable { private final String mId; Loading @@ -45,7 +76,7 @@ public final class LocusId implements Parcelable { } /** * Gets the {@code id} associated with the locus. * Gets the canonical {@code id} associated with the locus. */ @NonNull public String getId() { Loading Loading @@ -100,7 +131,7 @@ public final class LocusId implements Parcelable { parcel.writeString(mId); } public static final @android.annotation.NonNull Parcelable.Creator<LocusId> CREATOR = public static final @NonNull Parcelable.Creator<LocusId> CREATOR = new Parcelable.Creator<LocusId>() { @NonNull Loading core/java/android/view/View.java +20 −8 Original line number Diff line number Diff line Loading @@ -8525,11 +8525,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Populates a {@link ViewStructure} for Content Capture. * Populates a {@link ViewStructure} for content capture. * * <p>This method is called after a view is that is eligible for Content Capture * <p>This method is called after a view is that is eligible for content capture * (for example, if it {@link #isImportantForAutofill()}, an intelligence service is enabled for * the user, and the activity rendering the view is enabled for Content Capture) is laid out and * the user, and the activity rendering the view is enabled for content capture) is laid out and * is visible. * * <p>The populated structure is then passed to the service through Loading @@ -8548,6 +8548,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@code childStructure.getAutofillId()} or * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}. * * <p>When the virtual view hierarchy represents a web page, you should also: * * <ul> * <li>Call {@link ContentCaptureManager#getContentCaptureConditions()} to infer content * capture events should be generate for that URL. * <li>Create a new {@link ContentCaptureSession} child for every HTML element that * renders a new URL (like an {@code IFRAME}) and use that session to notify events from * that subtree. * </ul> * * <p><b>Note: </b>the following methods of the {@code structure} will be ignored: * <ul> * <li>{@link ViewStructure#setChildCount(int)} Loading Loading @@ -9264,11 +9274,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Hints the Android System whether this view is considered important for Content Capture, based * Hints the Android System whether this view is considered important for content capture, based * on the value explicitly set by {@link #setImportantForContentCapture(int)} and heuristics * when it's {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO}. * * @return whether the view is considered important for autofill. * <p>See {@link ContentCaptureManager} for more info about content capture. * * @return whether the view is considered important for content capture. * * @see #setImportantForContentCapture(int) * @see #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO Loading Loading @@ -9467,7 +9479,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Sets the (optional) {@link ContentCaptureSession} associated with this view. * * <p>This method should be called when you need to associate a {@link ContentCaptureContext} to * the Content Capture events associated with this view or its view hierarchy (if it's a * the content capture events associated with this view or its view hierarchy (if it's a * {@link ViewGroup}). * * <p>For example, if your activity is associated with a web domain, first you would need to Loading Loading @@ -9498,7 +9510,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Gets the session used to notify Content Capture events. * Gets the session used to notify content capture events. * * @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)}, * inherited by ancestors, default session or {@code null} if content capture is disabled for Loading Loading @@ -9719,7 +9731,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Dispatches the initial Content Capture events for a view structure. * Dispatches the initial content capture events for a view structure. * * @hide */ core/java/android/view/autofill/AutofillManagerInternal.java +4 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ public abstract class AutofillManagerInternal { public abstract void onBackKeyPressed(); /** * Gets autofill options for a package * Gets autofill options for a package. * * <p><b>NOTE: </b>this method is called by the {@code ActivityManager} service and hence cannot * hold the main service lock. * * @param packageName The package for which to query. * @param versionCode The package version code. Loading core/java/android/view/contentcapture/ContentCaptureContext.java +5 −4 Original line number Diff line number Diff line Loading @@ -37,7 +37,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Context associated with a {@link ContentCaptureSession}. * Context associated with a {@link ContentCaptureSession} - see {@link ContentCaptureManager} for * more info. */ public final class ContentCaptureContext implements Parcelable { Loading @@ -50,8 +51,7 @@ public final class ContentCaptureContext implements Parcelable { /** * Flag used to indicate that the app explicitly disabled content capture for the activity * (using * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}), * (using {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}), * in which case the service will just receive activity-level events. * * @hide Loading Loading @@ -260,9 +260,10 @@ public final class ContentCaptureContext implements Parcelable { * <li>A unique identifier of the application state (for example, a conversation between * 2 users in a chat app). * * <p>See {@link ContentCaptureManager} for more info about the content capture context. * * @param id id associated with this context. */ // TODO(b/123577059): make sure this is well documented and understandable public Builder(@NonNull LocusId id) { mId = Preconditions.checkNotNull(id); } Loading core/java/android/view/contentcapture/ContentCaptureManager.java +158 −13 Original line number Diff line number Diff line Loading @@ -28,12 +28,15 @@ import android.annotation.UiThread; import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; import android.graphics.Canvas; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.view.View; import android.view.ViewStructure; import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; Loading @@ -46,7 +49,148 @@ import java.lang.annotation.RetentionPolicy; import java.util.Set; /** * TODO(b/123577059): add javadocs / mention it can be null * <p>The {@link ContentCaptureManager} provides additional ways for for apps to * integrate with the content capture subsystem. * * <p>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 switchs 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. * * <p>Content capture was designed with two major concerns in mind: privacy and performance. * * <ul> * <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided * by the device manufacturer and that cannot be changed by the user (although the user can * globaly disable content capture using the Android Settings app). This service can only use the * data for in-device machine learning, which is enforced both by process isolation and * <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>. * <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app * jankiness and overall device system health. For example, its only enabled on apps (or even * specific activities from an app) that were explicitly whitelisted by the intelligence service, * and it buffers the events so they are sent in a batch to the service (see * {@link #isContentCaptureEnabled()} for other cases when its disabled). * </ul> * * <p>In fact, before using this manager, the app developer should check if it's available. Example: * <code> * ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class); * if (mgr != null && mgr.isContentCaptureEnabled()) { * // ... * } * </code> * * <p>App developers usually don't need to explicitly interact with content capture, except when the * app: * * <ul> * <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a * conversation between 2 chat users). * <li>Can have multiple view hierarchies with different contextual meaning (for example, a * browser app with multiple tabs, each representing a different URL). * <li>Contains custom views (that extend View directly and are not provided by the standard * Android SDK. * <li>Contains views that provide their own virtual hierarchy (like a web browser that render the * HTML elements using a Canvas). * </ul> * * <p>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: * * <pre><code> * protected void onCreate(Bundle savedInstanceState) { * // Initialize view structure * ContentCaptureSession session = rootView.getContentCaptureSession(); * if (session != null) { * session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB")); * } * } * </code></pre> * * <p>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}: * * <pre><code> * 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... * } * </code></pre> * * <p>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: * * <ul> * <li>Set it as important for content capture. * <li>Fill {@link ViewStructure} used for content capture. * <li>Notify the {@link ContentCaptureSession} when the text is changed by user input. * </ul> * * <p>Here's an example of the relevant methods for an {@code EditText}-like view: * * <pre><code> * 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()); * } * } * } * </code></pre> * * <p>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 { Loading @@ -69,7 +213,7 @@ public final class ContentCaptureManager { /** * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide * whether the Content Capture service should be created or not * whether the content capture service should be created or not * * <p>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: Loading Loading @@ -340,12 +484,12 @@ public final class ContentCaptureManager { * <p>There are many reasons it could be disabled, such as: * <ul> * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}. * <li>Service disabled content capture for this specific activity. * <li>Service disabled content capture for all activities of this package. * <li>Service disabled content capture globally. * <li>User disabled content capture globally (through Settings). * <li>OEM disabled content capture globally. * <li>Transient errors. * <li>Intelligence service did not whitelist content capture for this activity's package. * <li>Intelligence service did not whitelist content capture for this specific activity. * <li>Intelligence service disabled content capture globally. * <li>User disabled content capture globally through the Android Settings app. * <li>Device manufacturer (OEM) disabled content capture globally. * <li>Transient errors, such as intelligence service package being updated. * </ul> */ public boolean isContentCaptureEnabled() { Loading @@ -369,7 +513,8 @@ public final class ContentCaptureManager { * 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). * (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<ContentCaptureCondition> getContentCaptureConditions() { Loading @@ -393,12 +538,12 @@ public final class ContentCaptureManager { } /** * Gets whether Content Capture is enabled for the given user. * Gets whether content capture is enabled for the given user. * * <p>This method is typically used by the Content Capture Service settings page, so it can * <p>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 * @throws SecurityException if caller is not the app that owns the content capture service * associated with the user. * * @hide Loading Loading @@ -428,7 +573,7 @@ public final class ContentCaptureManager { } /** * Called by the app to request the Content Capture service to remove user-data associated with * Called by the app to request the content capture service to remove user-data associated with * some context. * * @param request object specifying what user data should be removed. Loading Loading
core/java/android/content/LocusId.java +37 −6 Original line number Diff line number Diff line Loading @@ -18,19 +18,50 @@ package android.content; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.view.contentcapture.ContentCaptureManager; import com.android.internal.util.Preconditions; import java.io.PrintWriter; /** * Identifier for an unique state in the application. * An identifier for an unique state (locus) in the application. Should be stable across reboots and * backup / restore. * * <p>Should be stable across reboots and backup / restore. * <p>Locus is a new concept introduced on * {@link android.os.Build.VERSION_CODES#Q Android Q} and it lets the intelligence service provided * by the Android System to correlate state between different subsystems such as content capture, * shortcuts, and notifications. * * <p>For example, a chat app could use the context to resume a conversation between 2 users. * <p>For example, if your app provides an activiy representing a chat between 2 users * (say {@code A} and {@code B}, this chat state could be represented by: * * <pre><code> * LocusId chatId = new LocusId("Chat_A_B"); * </code></pre> * * <p>And then you should use that {@code chatId} by: * * <ul> * <li>Setting it in the chat notification (through * {@link android.app.Notification.Builder#setLocusId(LocusId) * Notification.Builder.setLocusId(chatId)}). * <li>Setting it into the {@link android.content.pm.ShortcutInfo} (through * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(LocusId) * ShortcutInfo.Builder.setLocusId(chatId)}), if you provide a launcher shortcut for that chat * conversation. * <li>Associating it with the {@link android.view.contentcapture.ContentCaptureContext} of the * root view of the chat conversation activity (through * {@link android.view.View#getContentCaptureSession()}, then * {@link android.view.contentcapture.ContentCaptureContext.Builder * new ContentCaptureContext.Builder(chatId).build()} and * {@link android.view.contentcapture.ContentCaptureSession#setContentCaptureContext( * android.view.contentcapture.ContentCaptureContext)} - see {@link ContentCaptureManager} * for more info about content capture). * <li>Configuring your app to launch the chat conversation through the * {@link Intent#ACTION_VIEW_LOCUS} intent. * </ul> */ // TODO(b/123577059): make sure this is well documented and understandable public final class LocusId implements Parcelable { private final String mId; Loading @@ -45,7 +76,7 @@ public final class LocusId implements Parcelable { } /** * Gets the {@code id} associated with the locus. * Gets the canonical {@code id} associated with the locus. */ @NonNull public String getId() { Loading Loading @@ -100,7 +131,7 @@ public final class LocusId implements Parcelable { parcel.writeString(mId); } public static final @android.annotation.NonNull Parcelable.Creator<LocusId> CREATOR = public static final @NonNull Parcelable.Creator<LocusId> CREATOR = new Parcelable.Creator<LocusId>() { @NonNull Loading
core/java/android/view/View.java +20 −8 Original line number Diff line number Diff line Loading @@ -8525,11 +8525,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Populates a {@link ViewStructure} for Content Capture. * Populates a {@link ViewStructure} for content capture. * * <p>This method is called after a view is that is eligible for Content Capture * <p>This method is called after a view is that is eligible for content capture * (for example, if it {@link #isImportantForAutofill()}, an intelligence service is enabled for * the user, and the activity rendering the view is enabled for Content Capture) is laid out and * the user, and the activity rendering the view is enabled for content capture) is laid out and * is visible. * * <p>The populated structure is then passed to the service through Loading @@ -8548,6 +8548,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@code childStructure.getAutofillId()} or * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}. * * <p>When the virtual view hierarchy represents a web page, you should also: * * <ul> * <li>Call {@link ContentCaptureManager#getContentCaptureConditions()} to infer content * capture events should be generate for that URL. * <li>Create a new {@link ContentCaptureSession} child for every HTML element that * renders a new URL (like an {@code IFRAME}) and use that session to notify events from * that subtree. * </ul> * * <p><b>Note: </b>the following methods of the {@code structure} will be ignored: * <ul> * <li>{@link ViewStructure#setChildCount(int)} Loading Loading @@ -9264,11 +9274,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Hints the Android System whether this view is considered important for Content Capture, based * Hints the Android System whether this view is considered important for content capture, based * on the value explicitly set by {@link #setImportantForContentCapture(int)} and heuristics * when it's {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO}. * * @return whether the view is considered important for autofill. * <p>See {@link ContentCaptureManager} for more info about content capture. * * @return whether the view is considered important for content capture. * * @see #setImportantForContentCapture(int) * @see #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO Loading Loading @@ -9467,7 +9479,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Sets the (optional) {@link ContentCaptureSession} associated with this view. * * <p>This method should be called when you need to associate a {@link ContentCaptureContext} to * the Content Capture events associated with this view or its view hierarchy (if it's a * the content capture events associated with this view or its view hierarchy (if it's a * {@link ViewGroup}). * * <p>For example, if your activity is associated with a web domain, first you would need to Loading Loading @@ -9498,7 +9510,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Gets the session used to notify Content Capture events. * Gets the session used to notify content capture events. * * @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)}, * inherited by ancestors, default session or {@code null} if content capture is disabled for Loading Loading @@ -9719,7 +9731,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** * Dispatches the initial Content Capture events for a view structure. * Dispatches the initial content capture events for a view structure. * * @hide */
core/java/android/view/autofill/AutofillManagerInternal.java +4 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ public abstract class AutofillManagerInternal { public abstract void onBackKeyPressed(); /** * Gets autofill options for a package * Gets autofill options for a package. * * <p><b>NOTE: </b>this method is called by the {@code ActivityManager} service and hence cannot * hold the main service lock. * * @param packageName The package for which to query. * @param versionCode The package version code. Loading
core/java/android/view/contentcapture/ContentCaptureContext.java +5 −4 Original line number Diff line number Diff line Loading @@ -37,7 +37,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Context associated with a {@link ContentCaptureSession}. * Context associated with a {@link ContentCaptureSession} - see {@link ContentCaptureManager} for * more info. */ public final class ContentCaptureContext implements Parcelable { Loading @@ -50,8 +51,7 @@ public final class ContentCaptureContext implements Parcelable { /** * Flag used to indicate that the app explicitly disabled content capture for the activity * (using * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}), * (using {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}), * in which case the service will just receive activity-level events. * * @hide Loading Loading @@ -260,9 +260,10 @@ public final class ContentCaptureContext implements Parcelable { * <li>A unique identifier of the application state (for example, a conversation between * 2 users in a chat app). * * <p>See {@link ContentCaptureManager} for more info about the content capture context. * * @param id id associated with this context. */ // TODO(b/123577059): make sure this is well documented and understandable public Builder(@NonNull LocusId id) { mId = Preconditions.checkNotNull(id); } Loading
core/java/android/view/contentcapture/ContentCaptureManager.java +158 −13 Original line number Diff line number Diff line Loading @@ -28,12 +28,15 @@ import android.annotation.UiThread; import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; import android.graphics.Canvas; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.view.View; import android.view.ViewStructure; import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; Loading @@ -46,7 +49,148 @@ import java.lang.annotation.RetentionPolicy; import java.util.Set; /** * TODO(b/123577059): add javadocs / mention it can be null * <p>The {@link ContentCaptureManager} provides additional ways for for apps to * integrate with the content capture subsystem. * * <p>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 switchs 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. * * <p>Content capture was designed with two major concerns in mind: privacy and performance. * * <ul> * <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided * by the device manufacturer and that cannot be changed by the user (although the user can * globaly disable content capture using the Android Settings app). This service can only use the * data for in-device machine learning, which is enforced both by process isolation and * <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>. * <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app * jankiness and overall device system health. For example, its only enabled on apps (or even * specific activities from an app) that were explicitly whitelisted by the intelligence service, * and it buffers the events so they are sent in a batch to the service (see * {@link #isContentCaptureEnabled()} for other cases when its disabled). * </ul> * * <p>In fact, before using this manager, the app developer should check if it's available. Example: * <code> * ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class); * if (mgr != null && mgr.isContentCaptureEnabled()) { * // ... * } * </code> * * <p>App developers usually don't need to explicitly interact with content capture, except when the * app: * * <ul> * <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a * conversation between 2 chat users). * <li>Can have multiple view hierarchies with different contextual meaning (for example, a * browser app with multiple tabs, each representing a different URL). * <li>Contains custom views (that extend View directly and are not provided by the standard * Android SDK. * <li>Contains views that provide their own virtual hierarchy (like a web browser that render the * HTML elements using a Canvas). * </ul> * * <p>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: * * <pre><code> * protected void onCreate(Bundle savedInstanceState) { * // Initialize view structure * ContentCaptureSession session = rootView.getContentCaptureSession(); * if (session != null) { * session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB")); * } * } * </code></pre> * * <p>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}: * * <pre><code> * 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... * } * </code></pre> * * <p>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: * * <ul> * <li>Set it as important for content capture. * <li>Fill {@link ViewStructure} used for content capture. * <li>Notify the {@link ContentCaptureSession} when the text is changed by user input. * </ul> * * <p>Here's an example of the relevant methods for an {@code EditText}-like view: * * <pre><code> * 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()); * } * } * } * </code></pre> * * <p>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 { Loading @@ -69,7 +213,7 @@ public final class ContentCaptureManager { /** * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide * whether the Content Capture service should be created or not * whether the content capture service should be created or not * * <p>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: Loading Loading @@ -340,12 +484,12 @@ public final class ContentCaptureManager { * <p>There are many reasons it could be disabled, such as: * <ul> * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}. * <li>Service disabled content capture for this specific activity. * <li>Service disabled content capture for all activities of this package. * <li>Service disabled content capture globally. * <li>User disabled content capture globally (through Settings). * <li>OEM disabled content capture globally. * <li>Transient errors. * <li>Intelligence service did not whitelist content capture for this activity's package. * <li>Intelligence service did not whitelist content capture for this specific activity. * <li>Intelligence service disabled content capture globally. * <li>User disabled content capture globally through the Android Settings app. * <li>Device manufacturer (OEM) disabled content capture globally. * <li>Transient errors, such as intelligence service package being updated. * </ul> */ public boolean isContentCaptureEnabled() { Loading @@ -369,7 +513,8 @@ public final class ContentCaptureManager { * 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). * (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<ContentCaptureCondition> getContentCaptureConditions() { Loading @@ -393,12 +538,12 @@ public final class ContentCaptureManager { } /** * Gets whether Content Capture is enabled for the given user. * Gets whether content capture is enabled for the given user. * * <p>This method is typically used by the Content Capture Service settings page, so it can * <p>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 * @throws SecurityException if caller is not the app that owns the content capture service * associated with the user. * * @hide Loading Loading @@ -428,7 +573,7 @@ public final class ContentCaptureManager { } /** * Called by the app to request the Content Capture service to remove user-data associated with * Called by the app to request the content capture service to remove user-data associated with * some context. * * @param request object specifying what user data should be removed. Loading