Add new ContentCapture APIs to let apps change the ContentCaptureContext.
Test: atest CtsContentCaptureServiceTestCases:android.contentcaptureservice.cts.LoginActivityTest#testSimpleLifecycle_changeContextOnCreate \
CtsContentCaptureServiceTestCases:android.contentcaptureservice.cts.LoginActivityTest#testSimpleLifecycle_changeContextAfterCreate
Test: atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureEventTest
Bug: 124266664
Change-Id: I0348e81e1b2bac01363cf615d2ab32e5bab8aee1
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 83df33e..469bdbf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9366,7 +9366,7 @@
* Gets the session used to notify Content Capture events.
*
* @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)},
- * inherited by ancestore, default session or {@code null} if content capture is disabled for
+ * inherited by ancestors, default session or {@code null} if content capture is disabled for
* this view.
*/
@Nullable
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index acb81e0..13e8a65 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -20,10 +20,6 @@
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
-import com.android.internal.util.Preconditions;
-
-import java.io.PrintWriter;
-
/**
* A session that is explicitly created by the app (and hence is a descendant of
* {@link MainContentCaptureSession}).
@@ -35,21 +31,11 @@
@NonNull
private final ContentCaptureSession mParent;
- /**
- * {@link ContentCaptureContext} set by client, or {@code null} when it's the
- * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the
- * context.
- *
- * @hide
- */
- @NonNull
- private final ContentCaptureContext mClientContext;
-
/** @hide */
protected ChildContentCaptureSession(@NonNull ContentCaptureSession parent,
@NonNull ContentCaptureContext clientContext) {
+ super(clientContext);
mParent = parent;
- mClientContext = Preconditions.checkNotNull(clientContext);
}
@Override
@@ -73,6 +59,11 @@
}
@Override
+ public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
+ getMainCaptureSession().notifyContextUpdated(mId, context);
+ }
+
+ @Override
void onDestroy() {
getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId);
}
@@ -101,13 +92,4 @@
boolean isContentCaptureEnabled() {
return getMainCaptureSession().isContentCaptureEnabled();
}
-
- @Override
- void dump(String prefix, PrintWriter pw) {
- if (mClientContext != null) {
- // NOTE: we don't dump clientContent because it could have PII
- pw.print(prefix); pw.println("hasClientContext");
- }
- super.dump(prefix, pw);
- }
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 22254cd..9cdbefa 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -91,13 +91,22 @@
*/
public static final int TYPE_INITIAL_VIEW_TREE_APPEARED = 5;
+ /**
+ * Called after a call to
+ * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
+ *
+ * <p>The passed context is available through {@link #getContentCaptureContext()}.
+ */
+ public static final int TYPE_CONTEXT_UPDATED = 6;
+
/** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_VIEW_APPEARED,
TYPE_VIEW_DISAPPEARED,
TYPE_VIEW_TEXT_CHANGED,
TYPE_INITIAL_VIEW_TREE_APPEARING,
- TYPE_INITIAL_VIEW_TREE_APPEARED
+ TYPE_INITIAL_VIEW_TREE_APPEARED,
+ TYPE_CONTEXT_UPDATED
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType{}
@@ -193,12 +202,13 @@
}
/**
- * Used by {@link #TYPE_SESSION_STARTED}.
+ * Gets the {@link ContentCaptureContext} set calls to
+ * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
*
- * @hide
+ * <p>Only set on {@link #TYPE_CONTEXT_UPDATED} events.
*/
@Nullable
- public ContentCaptureContext getClientContext() {
+ public ContentCaptureContext getContentCaptureContext() {
return mClientContext;
}
@@ -220,8 +230,8 @@
* Gets the type of the event.
*
* @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
- * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_INITIAL_VIEW_TREE_APPEARING}, or
- * {@link #TYPE_INITIAL_VIEW_TREE_APPEARED}.
+ * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_INITIAL_VIEW_TREE_APPEARING},
+ * {@link #TYPE_INITIAL_VIEW_TREE_APPEARED}, or {@link #TYPE_CONTEXT_UPDATED}.
*/
public @EventType int getType() {
return mType;
@@ -299,6 +309,10 @@
if (mText != null) {
pw.print(", text="); pw.println(getSanitizedString(mText));
}
+ if (mClientContext != null) {
+ pw.print(", context="); mClientContext.dump(pw); pw.println();
+
+ }
}
@Override
@@ -325,6 +339,9 @@
if (mText != null) {
string.append(", text=").append(getSanitizedString(mText));
}
+ if (mClientContext != null) {
+ string.append(", context=").append(mClientContext);
+ }
return string.append(']').toString();
}
@@ -345,7 +362,7 @@
if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) {
parcel.writeString(mParentSessionId);
}
- if (mType == TYPE_SESSION_STARTED) {
+ if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) {
parcel.writeParcelable(mClientContext, flags);
}
}
@@ -375,7 +392,7 @@
if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
event.setParentSessionId(parcel.readString());
}
- if (type == TYPE_SESSION_STARTED) {
+ if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
event.setClientContext(parcel.readParcelable(null));
}
return event;
@@ -404,6 +421,8 @@
return "INITIAL_VIEW_HIERARCHY_STARTED";
case TYPE_INITIAL_VIEW_TREE_APPEARED:
return "INITIAL_VIEW_HIERARCHY_FINISHED";
+ case TYPE_CONTEXT_UPDATED:
+ return "CONTEXT_UPDATED";
default:
return "UKNOWN_TYPE: " + type;
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index e028961..b8d3fa6 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -166,6 +166,14 @@
private ContentCaptureSessionId mContentCaptureSessionId;
/**
+ * {@link ContentCaptureContext} set by client, or {@code null} when it's the
+ * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the
+ * context.
+ */
+ @Nullable
+ private ContentCaptureContext mClientContext;
+
+ /**
* List of children session.
*/
@Nullable
@@ -183,6 +191,12 @@
mId = Preconditions.checkNotNull(id);
}
+ // Used by ChildCOntentCaptureSession
+ ContentCaptureSession(@NonNull ContentCaptureContext initialContext) {
+ this();
+ mClientContext = Preconditions.checkNotNull(initialContext);
+ }
+
/** @hide */
@NonNull
abstract MainContentCaptureSession getMainCaptureSession();
@@ -240,6 +254,30 @@
abstract void flush(@FlushReason int reason);
/**
+ * Sets the {@link ContentCaptureContext} associated with the session.
+ *
+ * <p>Typically used to change the context associated with the default session from an activity.
+ */
+ public final void setContentCaptureContext(@Nullable ContentCaptureContext context) {
+ mClientContext = context;
+ updateContentCaptureContext(context);
+ }
+
+ abstract void updateContentCaptureContext(@Nullable ContentCaptureContext context);
+
+ /**
+ * Gets the {@link ContentCaptureContext} associated with the session.
+ *
+ * @return context set on constructor or by
+ * {@link #setContentCaptureContext(ContentCaptureContext)}, or {@code null} if never
+ * explicitly set.
+ */
+ @Nullable
+ public final ContentCaptureContext getContentCaptureContext() {
+ return mClientContext;
+ }
+
+ /**
* Destroys this session, flushing out all pending notifications to the service.
*
* <p>Once destroyed, any new notification will be dropped.
@@ -424,6 +462,9 @@
@CallSuper
void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("id: "); pw.println(mId);
+ if (mClientContext != null) {
+ pw.print(prefix); mClientContext.dump(pw); pw.println();
+ }
synchronized (mLock) {
pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
if (mChildren != null && !mChildren.isEmpty()) {
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 810c967..d949f45 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -15,6 +15,7 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_INITIAL_VIEW_TREE_APPEARED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_INITIAL_VIEW_TREE_APPEARING;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
@@ -269,11 +270,12 @@
private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
final int eventType = event.getType();
if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
- if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
+ if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED
+ && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) {
// TODO(b/120494182): comment when this could happen (dialogs?)
Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
+ ContentCaptureEvent.getTypeAsString(eventType)
- + "): session not started yet");
+ + "): dropping because session not started yet");
return;
}
if (mDisabled.get()) {
@@ -476,6 +478,11 @@
}
}
+ @Override
+ public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
+ notifyContextUpdated(mId, context);
+ }
+
/**
* Resets the buffer and return a {@link ParceledListSlice} with the previous events.
*/
@@ -613,6 +620,12 @@
}
}
+ void notifyContextUpdated(@NonNull String sessionId,
+ @Nullable ContentCaptureContext context) {
+ sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+ .setClientContext(context));
+ }
+
@Override
void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("mContext: "); pw.println(mContext);