Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2dc2d0b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Improved Content Capture and LocusId javadocs."

parents e48acf1b f0d44c6a
Loading
Loading
Loading
Loading
+37 −6
Original line number Diff line number Diff line
@@ -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
+20 −8
Original line number Diff line number Diff line
@@ -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
@@ -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)}
@@ -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
@@ -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
@@ -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
@@ -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
     */
+4 −1
Original line number Diff line number Diff line
@@ -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.
+5 −4
Original line number Diff line number Diff line
@@ -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);
        }
+158 −13
Original line number Diff line number Diff line
@@ -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.