OSDN Git Service

Improved Content Capture and LocusId javadocs.
authorFelipe Leme <felipeal@google.com>
Tue, 26 Mar 2019 23:36:58 +0000 (16:36 -0700)
committerFelipe Leme <felipeal@google.com>
Wed, 27 Mar 2019 20:42:24 +0000 (13:42 -0700)
Test: mmm frameworks/base/:doc-comment-check-docs # and checked resulting HTML

Fixes: 123577059
Fixes: 128427733
Fixes: 123954513

Change-Id: I955e99efa099cbb68477b394450fa468c85f352c

core/java/android/content/LocusId.java
core/java/android/view/View.java
core/java/android/view/autofill/AutofillManagerInternal.java
core/java/android/view/contentcapture/ContentCaptureContext.java
core/java/android/view/contentcapture/ContentCaptureManager.java

index c67ff7c..283cea0 100644 (file)
@@ -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;
@@ -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() {
@@ -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
index 2357db4..245046e 100644 (file)
@@ -8524,11 +8524,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
@@ -8547,6 +8547,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)}
@@ -9263,11 +9273,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
@@ -9466,7 +9478,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
@@ -9497,7 +9509,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
@@ -9718,7 +9730,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
      */
index d5862bd..3de1a03 100644 (file)
@@ -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.
index 5a27e94..0e9b16d 100644 (file)
@@ -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 {
 
@@ -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
@@ -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);
         }
index 817b130..4f3325c 100644 (file)
@@ -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;
@@ -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 {
@@ -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:
@@ -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() {
@@ -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() {
@@ -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
@@ -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.