Moved createContentCaptureSession() to ContentCaptureSession.
Such move will allow nested sessions. For example, WebView could create a
new session for the main page, then child sessions for IFRAMEs contained on it.
This CL changes the API and provides an initial implementation, although it's
not quite ready yet - it only allows 1 level of children (from the activity
session), but the full implementation is coming soom to a movie theather near
you...
Bug: 121033016
Bug: 117944706
Test: atest CtsContentCaptureServiceTestCases
Change-Id: I86156bb3b8a2c08cb00b9518599eb6d67fbf77c2
diff --git a/api/current.txt b/api/current.txt
index 31ec76e..479c82d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -52224,7 +52224,6 @@
}
public final class ContentCaptureManager {
- method public android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
method public android.content.ComponentName getServiceComponentName();
method public boolean isContentCaptureEnabled();
method public void removeUserData(android.view.contentcapture.UserDataRemovalRequest);
@@ -52233,6 +52232,7 @@
public abstract class ContentCaptureSession implements java.lang.AutoCloseable {
method public void close();
+ method public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
method public final void destroy();
method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
method public final void notifyViewAppeared(android.view.ViewStructure);
diff --git a/api/system-current.txt b/api/system-current.txt
index 751ef34..d70b807 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7553,6 +7553,7 @@
method public int getDisplayId();
method public android.os.Bundle getExtras();
method public int getFlags();
+ method public android.view.contentcapture.ContentCaptureSessionId getParentSessionId();
method public int getTaskId();
method public android.net.Uri getUri();
field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index c5e62f1..64f2355 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -34,13 +34,13 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import android.view.contentcapture.ActivityContentCaptureSession;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.IContentCaptureDirectManager;
+import android.view.contentcapture.MainContentCaptureSession;
import com.android.internal.os.IResultReceiver;
@@ -293,13 +293,26 @@
final List<ContentCaptureEvent> events = parceledEvents.getList();
for (int i = 0; i < events.size(); i++) {
final ContentCaptureEvent event = events.get(i);
+ if (!handleIsRightCallerFor(event, uid)) continue;
String sessionIdString = event.getSessionId();
if (!sessionIdString.equals(lastSessionId)) {
- if (!handleIsRightCallerFor(sessionIdString, uid)) continue;
sessionId = new ContentCaptureSessionId(sessionIdString);
lastSessionId = sessionIdString;
}
- onContentCaptureEvent(sessionId, event);
+ switch (event.getType()) {
+ case ContentCaptureEvent.TYPE_SESSION_STARTED:
+ final ContentCaptureContext clientContext = event.getClientContext();
+ clientContext.setParentSessionId(event.getParentSessionId());
+ mSessionsByUid.put(sessionIdString, uid);
+ onCreateContentCaptureSession(clientContext, sessionId);
+ break;
+ case ContentCaptureEvent.TYPE_SESSION_FINISHED:
+ mSessionsByUid.remove(sessionIdString);
+ onDestroyContentCaptureSession(sessionId);
+ break;
+ default:
+ onContentCaptureEvent(sessionId, event);
+ }
}
}
@@ -314,9 +327,18 @@
}
/**
- * Checks if the given {@code uid} owns the session.
+ * Checks if the given {@code uid} owns the session associated with the event.
*/
- private boolean handleIsRightCallerFor(@NonNull String sessionId, int uid) {
+ private boolean handleIsRightCallerFor(@NonNull ContentCaptureEvent event, int uid) {
+ final String sessionId;
+ switch (event.getType()) {
+ case ContentCaptureEvent.TYPE_SESSION_STARTED:
+ case ContentCaptureEvent.TYPE_SESSION_FINISHED:
+ sessionId = event.getParentSessionId();
+ break;
+ default:
+ sessionId = event.getSessionId();
+ }
final Integer rightUid = mSessionsByUid.get(sessionId);
if (rightUid == null) {
if (VERBOSE) Log.v(TAG, "No session for " + sessionId);
@@ -347,7 +369,7 @@
final Bundle extras;
if (binder != null) {
extras = new Bundle();
- extras.putBinder(ActivityContentCaptureSession.EXTRA_BINDER, binder);
+ extras.putBinder(MainContentCaptureSession.EXTRA_BINDER, binder);
} else {
extras = null;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 468d922..69cd3e6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9046,17 +9046,16 @@
* {@code onCreate()} and associate it with the root view of the activity:
*
* <pre>
- * ContentCaptureManager mgr = getSystemService(ContentCaptureManager.class);
- * if (mgr != null && mgr.isContentCaptureEnabled()) {
- * View rootView = findViewById(R.my_root_view);
- * ContentCaptureSession session = mgr.createContentCaptureSession(new
+ * ContentCaptureSession oldSession = rootView.getContentCaptureSession();
+ * if (oldSession != null) {
+ * ContentCaptureSession newSession = oldSession.createContentCaptureSession(new
* ContentCaptureContext.Builder().setUri(myUrl).build());
- * rootView.setContentCaptureSession(session);
+ * rootView.setContentCaptureSession(newSession);
* }
* </pre>
*
* @param contentCaptureSession a session created by
- * {@link ContentCaptureManager#createContentCaptureSession(
+ * {@link ContentCaptureSession#createContentCaptureSession(
* android.view.contentcapture.ContentCaptureContext)}.
*/
public void setContentCaptureSession(@NonNull ContentCaptureSession contentCaptureSession) {
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
new file mode 100644
index 0000000..5166831
--- /dev/null
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+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}).
+ *
+ * @hide
+ */
+final class ChildContentCaptureSession extends ContentCaptureSession {
+
+ @NonNull
+ private final MainContentCaptureSession 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 MainContentCaptureSession parent,
+ @NonNull ContentCaptureContext clientContext) {
+ mParent = parent;
+ mClientContext = Preconditions.checkNotNull(clientContext);
+ }
+
+ @Override
+ ContentCaptureSession newChild(@NonNull ContentCaptureContext context) {
+ // TODO(b/121033016): implement it
+ throw new UnsupportedOperationException("grand-children not implemented yet");
+ }
+
+ @Override
+ void flush() {
+ mParent.flush();
+ }
+
+ @Override
+ void onDestroy() {
+ mParent.notifyChildSessionFinished(mParent.mId, mId);
+ }
+
+ @Override
+ void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
+ mParent.notifyViewAppeared(mId, node);
+ }
+
+ @Override
+ void internalNotifyViewDisappeared(@NonNull AutofillId id) {
+ mParent.notifyViewDisappeared(mId, id);
+ }
+
+ @Override
+ void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
+ int flags) {
+ mParent.notifyViewTextChanged(mId, id, text, flags);
+ }
+ @Override
+ boolean isContentCaptureEnabled() {
+ return mParent.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/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 9c11743..2d2987a 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -21,6 +21,7 @@
import android.annotation.SystemApi;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -76,7 +77,7 @@
/**
* Flag indicating if this object has the app-provided context (which is set on
- * {@link ContentCaptureManager#createContentCaptureSession(ContentCaptureContext)}).
+ * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}).
*/
private final boolean mHasClientContext;
@@ -91,6 +92,9 @@
private final int mDisplayId;
private final int mFlags;
+ // Fields below are set by the service upon "delivery" and are not marshalled in the parcel
+ private @Nullable String mParentSessionId;
+
/** @hide */
public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
@NonNull ComponentName componentName, int taskId, int displayId, int flags) {
@@ -153,16 +157,33 @@
}
/**
- * Gets the activity associated with this context.
+ * Gets the activity associated with this context, or {@code null} when it is a child session.
*
* @hide
*/
@SystemApi
- public @NonNull ComponentName getActivityComponent() {
+ public @Nullable ComponentName getActivityComponent() {
return mComponentName;
}
/**
+ * Gets the id of the session that originated this session (through
+ * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}),
+ * or {@code null} if this is the main session associated with the Activity's {@link Context}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @Nullable ContentCaptureSessionId getParentSessionId() {
+ return mParentSessionId == null ? null : new ContentCaptureSessionId(mParentSessionId);
+ }
+
+ /** @hide */
+ public void setParentSessionId(@NonNull String parentSessionId) {
+ mParentSessionId = parentSessionId;
+ }
+
+ /**
* Gets the ID of the display associated with this context, as defined by
* {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
*
@@ -242,6 +263,9 @@
pw.print("comp="); pw.print(ComponentName.flattenToShortString(mComponentName));
pw.print(", taskId="); pw.print(mTaskId);
pw.print(", displayId="); pw.print(mDisplayId);
+ if (mParentSessionId != null) {
+ pw.print(", parentId="); pw.print(mParentSessionId);
+ }
if (mFlags > 0) {
pw.print(", flags="); pw.print(mFlags);
}
@@ -262,6 +286,9 @@
.append(", taskId=").append(mTaskId)
.append(", displayId=").append(mDisplayId)
.append(", flags=").append(mFlags);
+ if (mParentSessionId != null) {
+ builder.append(", parentId=").append(mParentSessionId);
+ }
if (mExtras != null) {
// NOTE: cannot print because it could contain PII
builder.append(", hasExtras");
@@ -320,9 +347,9 @@
final int taskId = parcel.readInt();
final int displayId = parcel.readInt();
final int flags = parcel.readInt();
- return new ContentCaptureContext(clientContext, componentName, taskId,
- displayId, flags);
- }
+ return new ContentCaptureContext(clientContext, componentName, taskId, displayId,
+ flags);
+ }
}
@Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 43c9699..9e3da92 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -34,9 +34,9 @@
public final class ContentCaptureEvent implements Parcelable {
/** @hide */
- public static final int TYPE_ACTIVITY_DESTROYED = -2;
+ public static final int TYPE_SESSION_FINISHED = -2;
/** @hide */
- public static final int TYPE_ACTIVITY_CREATED = -1;
+ public static final int TYPE_SESSION_STARTED = -1;
/**
* Called when a node has been added to the screen and is visible to the user.
@@ -78,6 +78,8 @@
private @Nullable AutofillId mId;
private @Nullable ViewNode mNode;
private @Nullable CharSequence mText;
+ private @Nullable String mParentSessionId;
+ private @Nullable ContentCaptureContext mClientContext;
/** @hide */
public ContentCaptureEvent(@NonNull String sessionId, int type, long eventTime, int flags) {
@@ -103,12 +105,52 @@
return this;
}
+ /**
+ * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
+ *
+ * @hide
+ */
+ public ContentCaptureEvent setParentSessionId(@NonNull String parentSessionId) {
+ mParentSessionId = parentSessionId;
+ return this;
+ }
+
+ /**
+ * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
+ *
+ * @hide
+ */
+ public ContentCaptureEvent setClientContext(@NonNull ContentCaptureContext clientContext) {
+ mClientContext = clientContext;
+ return this;
+ }
+
/** @hide */
@NonNull
public String getSessionId() {
return mSessionId;
}
+ /**
+ * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getParentSessionId() {
+ return mParentSessionId;
+ }
+
+ /**
+ * Used by {@link #TYPE_SESSION_STARTED}.
+ *
+ * @hide
+ */
+ @Nullable
+ public ContentCaptureContext getClientContext() {
+ return mClientContext;
+ }
+
/** @hide */
@NonNull
public ContentCaptureEvent setViewNode(@NonNull ViewNode node) {
@@ -191,7 +233,17 @@
pw.print(", id="); pw.print(mId);
}
if (mNode != null) {
- pw.print(", id="); pw.print(mNode.getAutofillId());
+ pw.print(", mNode.id="); pw.print(mNode.getAutofillId());
+ }
+ if (mSessionId != null) {
+ pw.print(", sessionId="); pw.print(mSessionId);
+ }
+ if (mParentSessionId != null) {
+ pw.print(", parentSessionId="); pw.print(mParentSessionId);
+ }
+ if (mText != null) {
+ // Cannot print content because could have PII
+ pw.print(", text="); pw.print(mText.length()); pw.print("_chars");
}
}
@@ -229,6 +281,12 @@
parcel.writeParcelable(mId, flags);
ViewNode.writeToParcel(parcel, mNode, flags);
parcel.writeCharSequence(mText);
+ if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) {
+ parcel.writeString(mParentSessionId);
+ }
+ if (mType == TYPE_SESSION_STARTED) {
+ parcel.writeParcelable(mClientContext, flags);
+ }
}
public static final Parcelable.Creator<ContentCaptureEvent> CREATOR =
@@ -251,6 +309,12 @@
event.setViewNode(node);
}
event.setText(parcel.readCharSequence());
+ if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
+ event.setParentSessionId(parcel.readString());
+ }
+ if (type == TYPE_SESSION_STARTED) {
+ event.setClientContext(parcel.readParcelable(null));
+ }
return event;
}
@@ -263,6 +327,10 @@
/** @hide */
public static String getTypeAsString(@EventType int type) {
switch (type) {
+ case TYPE_SESSION_STARTED:
+ return "SESSION_STARTED";
+ case TYPE_SESSION_FINISHED:
+ return "SESSION_FINISHED";
case TYPE_VIEW_APPEARED:
return "VIEW_APPEARED";
case TYPE_VIEW_DISAPPEARED:
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index fca2857..9830790 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -18,13 +18,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.annotation.UiThread;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.util.Log;
-import android.view.View;
import com.android.internal.util.Preconditions;
@@ -66,7 +66,7 @@
@NonNull
private final Handler mHandler;
- private ActivityContentCaptureSession mMainSession;
+ private MainContentCaptureSession mMainSession;
/** @hide */
public ContentCaptureManager(@NonNull Context context,
@@ -93,46 +93,20 @@
}
/**
- * Creates a new {@link ContentCaptureSession}.
- *
- * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for more info.
- */
- @NonNull
- public ContentCaptureSession createContentCaptureSession(
- @NonNull ContentCaptureContext context) {
- if (DEBUG) Log.d(TAG, "createContentCaptureSession(): " + context);
- // TODO(b/121033016): for now we're updating the main session, but we need instead:
- // 1.Keep a list of sessions
- // 2.Making sure the applicationToken and componentName passed by
- // the activity is used on all of these sessions
- // 3.We might also need to delay the start of all of these sessions until
- // onActivityStarted() is called (and the main session is created).
- // 4.Close (and delete) these sessions when onActivityStopped() is called.
- // 5.Figure out whether each session will have its own mDisabled AtomicBoolean.
- if (mMainSession == null) {
- mMainSession = new ActivityContentCaptureSession(mContext, mHandler, mService,
- mDisabled, Preconditions.checkNotNull(context));
- } else {
- throw new IllegalStateException("Manager already has a session: " + mMainSession);
- }
- return mMainSession;
- }
-
- /**
* Gets the main session associated with the context.
*
* <p>By default there's just one (associated with the activity lifecycle), but apps could
- * explicitly add more using {@link #createContentCaptureSession(ContentCaptureContext)}.
+ * explicitly add more using
+ * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
*
* @hide
*/
@NonNull
- public ActivityContentCaptureSession getMainContentCaptureSession() {
- // TODO(b/121033016): figure out how to manage the "default" session when it support
- // multiple sessions (can't just be the first one, as it could be closed).
+ @UiThread
+ public MainContentCaptureSession getMainContentCaptureSession() {
if (mMainSession == null) {
- mMainSession = new ActivityContentCaptureSession(mContext, mHandler, mService,
- mDisabled, /* clientContext= */ null);
+ mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
+ mDisabled);
if (VERBOSE) {
Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index aedb7a9..9f666a4 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -15,8 +15,10 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureManager.DEBUG;
import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
@@ -30,6 +32,7 @@
import dalvik.system.CloseGuard;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.UUID;
/**
@@ -80,6 +83,8 @@
*/
public static final int STATE_DISABLED_DUPLICATED_ID = 4;
+ private static final int INITIAL_CHILDREN_CAPACITY = 5;
+
/** @hide */
protected final String mTag = getClass().getSimpleName();
@@ -95,19 +100,17 @@
private ContentCaptureSessionId mContentCaptureSessionId;
/**
- * {@link ContentCaptureContext} set by client, or {@code null} when it's the
- * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the
- * context.
- *
- * @hide
+ * List of children session.
*/
+ // TODO(b/121033016): need to synchonize access, either by changing on handler or UI thread
+ // (for now there's no handler on this class, so we need to wait for the next refactoring),
+ // most likely the former (as we have no guarantee that createContentCaptureSession()
+ // it will be called in the UiThread; for example, WebView most likely won't call on it)
@Nullable
- // TODO(b/121042846): move to ChildContentCaptureSession.java
- protected final ContentCaptureContext mClientContext;
+ private ArrayList<ContentCaptureSession> mChildren;
/** @hide */
- protected ContentCaptureSession(@Nullable ContentCaptureContext clientContext) {
- mClientContext = clientContext;
+ protected ContentCaptureSession() {
mCloseGuard.open("destroy");
}
@@ -122,6 +125,28 @@
}
/**
+ * Creates a new {@link ContentCaptureSession}.
+ *
+ * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for more info.
+ */
+ @NonNull
+ public final ContentCaptureSession createContentCaptureSession(
+ @NonNull ContentCaptureContext context) {
+ final ContentCaptureSession child = newChild(context);
+ if (DEBUG) {
+ Log.d(mTag, "createContentCaptureSession(" + context + ": parent=" + mId + ", child= "
+ + child.mId);
+ }
+ if (mChildren == null) {
+ mChildren = new ArrayList<>(INITIAL_CHILDREN_CAPACITY);
+ }
+ mChildren.add(child);
+ return child;
+ }
+
+ abstract ContentCaptureSession newChild(@NonNull ContentCaptureContext context);
+
+ /**
* Flushes the buffered events to the service.
*/
abstract void flush();
@@ -134,24 +159,40 @@
public final void destroy() {
//TODO(b/111276913): mark it as destroyed so other methods are ignored (and test on CTS)
+ //TODO(b/111276913): probably shouldn't check for it
if (!isContentCaptureEnabled()) return;
+ mCloseGuard.close();
+
//TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
// id) and send it to the cache of batched commands
if (VERBOSE) {
Log.v(mTag, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
}
- flush();
+ // Finish children first
+ if (mChildren != null) {
+ final int numberChildren = mChildren.size();
+ if (VERBOSE) Log.v(mTag, "Destroying " + numberChildren + " children first");
+ for (int i = 0; i < numberChildren; i++) {
+ final ContentCaptureSession child = mChildren.get(i);
+ try {
+ child.destroy();
+ } catch (Exception e) {
+ Log.w(mTag, "exception destroying child session #" + i + ": " + e);
+ }
+ }
+ }
- onDestroy();
-
- mCloseGuard.close();
+ try {
+ flush();
+ } finally {
+ onDestroy();
+ }
}
abstract void onDestroy();
-
/** @hide */
@Override
public void close() {
@@ -259,7 +300,19 @@
abstract boolean isContentCaptureEnabled();
- abstract void dump(@NonNull String prefix, @NonNull PrintWriter pw);
+ @CallSuper
+ void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+ if (mChildren != null && !mChildren.isEmpty()) {
+ final String prefix2 = prefix + " ";
+ final int numberChildren = mChildren.size();
+ pw.print(prefix); pw.print("number children: "); pw.print(numberChildren);
+ for (int i = 0; i < numberChildren; i++) {
+ final ContentCaptureSession child = mChildren.get(i);
+ pw.print(prefix); pw.print(i); pw.println(": "); child.dump(prefix2, pw);
+ }
+ }
+
+ }
@Override
public String toString() {
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index cbd3701..01776f8 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -33,7 +33,6 @@
*/
oneway interface IContentCaptureManager {
void startSession(int userId, IBinder activityToken, in ComponentName componentName,
- String sessionId, in ContentCaptureContext clientContext, int flags,
- in IResultReceiver result);
+ String sessionId, int flags, in IResultReceiver result);
void finishSession(int userId, String sessionId);
}
diff --git a/core/java/android/view/contentcapture/ActivityContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
similarity index 82%
rename from core/java/android/view/contentcapture/ActivityContentCaptureSession.java
rename to core/java/android/view/contentcapture/MainContentCaptureSession.java
index a8f3e0b..ea6f2fe 100644
--- a/core/java/android/view/contentcapture/ActivityContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -15,6 +15,8 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
@@ -59,7 +61,7 @@
*
* @hide
*/
-public final class ActivityContentCaptureSession extends ContentCaptureSession {
+public final class MainContentCaptureSession extends ContentCaptureSession {
/**
* Handler message used to flush the buffer.
@@ -129,18 +131,23 @@
// Lazily created on demand.
private ContentCaptureSessionId mContentCaptureSessionId;
- /**
- * @hide */
- protected ActivityContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
- @Nullable IContentCaptureManager systemServerInterface, @NonNull AtomicBoolean disabled,
- @Nullable ContentCaptureContext clientContext) {
- super(clientContext);
+ /** @hide */
+ protected MainContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
+ @Nullable IContentCaptureManager systemServerInterface,
+ @NonNull AtomicBoolean disabled) {
mContext = context;
mHandler = handler;
mSystemServerInterface = systemServerInterface;
mDisabled = disabled;
}
+ @Override
+ ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
+ final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
+ notifyChildSessionStarted(mId, child.mId, clientContext);
+ return child;
+ }
+
/**
* Starts this session.
*
@@ -154,19 +161,19 @@
+ ComponentName.flattenToShortString(activityComponent));
}
- mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleStartSession, this,
+ mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
applicationToken, activityComponent));
}
@Override
void flush() {
- mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleForceFlush, this));
+ mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleForceFlush, this));
}
@Override
void onDestroy() {
mHandler.sendMessage(
- obtainMessage(ActivityContentCaptureSession::handleDestroySession, this));
+ obtainMessage(MainContentCaptureSession::handleDestroySession, this));
}
private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
@@ -188,7 +195,7 @@
try {
mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken,
- componentName, mId, mClientContext, flags, new IResultReceiver.Stub() {
+ componentName, mId, flags, new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) {
IBinder binder = null;
@@ -296,7 +303,7 @@
Log.v(mTag, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
}
mHandler.sendMessageDelayed(
- obtainMessage(ActivityContentCaptureSession::handleFlushIfNeeded, this)
+ obtainMessage(MainContentCaptureSession::handleFlushIfNeeded, this)
.setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS);
}
@@ -312,7 +319,7 @@
if (mEvents == null) return;
if (mDirectServiceInterface == null) {
- Log.w(mTag, "handleForceFlush(): client not available yet");
+ if (DEBUG) Log.d(mTag, "handleForceFlush(): hold your horses, client not ready yet!");
if (!mHandler.hasMessages(MSG_FLUSH)) {
handleScheduleFlush(/* checkExisting= */ false);
}
@@ -367,12 +374,15 @@
handleResetSession(/* resetState= */ true);
}
- // TODO(b/121042846): once we support multiple sessions, we might need to move some of these
+ // TODO(b/121033016): once we support multiple sessions, we might need to move some of these
// clearings out.
private void handleResetSession(boolean resetState) {
if (resetState) {
mState = STATE_UNKNOWN;
}
+
+ // TODO(b/121033016): must reset children (which currently is owned by superclass)
+
mContentCaptureSessionId = null;
mApplicationToken = null;
mComponentName = null;
@@ -386,24 +396,18 @@
@Override
void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
- mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(mId, TYPE_VIEW_APPEARED)
- .setViewNode(node.mNode), /* forceFlush= */ false));
+ notifyViewAppeared(mId, node);
}
@Override
void internalNotifyViewDisappeared(@NonNull AutofillId id) {
- mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(mId, TYPE_VIEW_DISAPPEARED).setAutofillId(id),
- /* forceFlush= */ false));
+ notifyViewDisappeared(mId, id);
}
@Override
void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
int flags) {
- mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleSendEvent, this,
- new ContentCaptureEvent(mId, TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
- .setText(text), /* forceFlush= */ false));
+ notifyViewTextChanged(mId, id, text, flags);
}
@Override
@@ -411,6 +415,44 @@
return mSystemServerInterface != null && !mDisabled.get();
}
+ // TODO(b/121033016): refactor "notifyXXXX" methods below to a common "Buffer" object that is
+ // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
+ // change should also get get rid of the "internalNotifyXXXX" methods above
+ void notifyChildSessionStarted(@NonNull String parentSessionId,
+ @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
+ mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+ .setParentSessionId(parentSessionId)
+ .setClientContext(clientContext),
+ /* forceFlush= */ false));
+ }
+
+ void notifyChildSessionFinished(@NonNull String parentSessionId,
+ @NonNull String childSessionId) {
+ mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+ .setParentSessionId(parentSessionId), /* forceFlush= */ false));
+ }
+
+ void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
+ mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+ .setViewNode(node.mNode), /* forceFlush= */ false));
+ }
+
+ void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
+ mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id),
+ /* forceFlush= */ false));
+ }
+
+ void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
+ @Nullable CharSequence text, int flags) {
+ mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
+ .setText(text), /* forceFlush= */ false));
+ }
+
@Override
void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("id: "); pw.println(mId);
@@ -424,11 +466,6 @@
pw.print(prefix); pw.print("mDirectServiceInterface: ");
pw.println(mDirectServiceInterface);
}
- if (mClientContext != null) {
- // NOTE: we don't dump clientContent because it could have PII
- pw.print(prefix); pw.println("hasClientContext");
-
- }
pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
if (mContentCaptureSessionId != null) {
@@ -459,6 +496,7 @@
pw.print(prefix); pw.print("next flush: ");
TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
}
+ super.dump(prefix, pw);
}
/**
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index e8820ae..0b3fa02 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -20,7 +20,6 @@
import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.content.ComponentName;
@@ -34,7 +33,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.IContentCaptureManager;
import com.android.internal.annotations.GuardedBy;
@@ -164,8 +162,7 @@
@Override
public void startSession(@UserIdInt int userId, @NonNull IBinder activityToken,
- @NonNull ComponentName componentName, @NonNull String sessionId,
- @Nullable ContentCaptureContext clientContext, int flags,
+ @NonNull ComponentName componentName, @NonNull String sessionId, int flags,
@NonNull IResultReceiver result) {
Preconditions.checkNotNull(activityToken);
Preconditions.checkNotNull(componentName);
@@ -175,14 +172,13 @@
// so we don't pass it on startSession (same for Autofill)
final int taskId = getAmInternal().getTaskIdForActivity(activityToken, false);
- // TODO(b/111276913): get from AM as well
+ // TODO(b/121260224): get from AM as well
final int displayId = 0;
synchronized (mLock) {
final ContentCapturePerUserService service = getServiceForUserLocked(userId);
service.startSessionLocked(activityToken, componentName, taskId, displayId,
- sessionId, Binder.getCallingUid(), clientContext, flags,
- mAllowInstantService, result);
+ sessionId, Binder.getCallingUid(), flags, mAllowInstantService, result);
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index f21b0d8..03257e3 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -24,7 +24,6 @@
import android.Manifest;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.assist.AssistContent;
@@ -39,7 +38,6 @@
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
import android.util.Slog;
-import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureSession;
import com.android.internal.annotations.GuardedBy;
@@ -114,8 +112,8 @@
@GuardedBy("mLock")
public void startSessionLocked(@NonNull IBinder activityToken,
@NonNull ComponentName componentName, int taskId, int displayId,
- @NonNull String sessionId, int uid, @Nullable ContentCaptureContext clientContext,
- int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver clientReceiver) {
+ @NonNull String sessionId, int uid, int flags, boolean bindInstantServiceAllowed,
+ @NonNull IResultReceiver clientReceiver) {
if (!isEnabledLocked()) {
setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED, /* binder=*/ null);
return;
@@ -142,7 +140,7 @@
final ContentCaptureServerSession newSession = new ContentCaptureServerSession(getContext(),
mUserId, mLock, activityToken, this, serviceComponentName, componentName, taskId,
- displayId, sessionId, uid, clientContext, flags, bindInstantServiceAllowed,
+ displayId, sessionId, uid, flags, bindInstantServiceAllowed,
mMaster.verbose);
if (mMaster.verbose) {
Slog.v(TAG, "startSession(): new session for "
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index ba98b95..f59636b 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -16,7 +16,6 @@
package com.android.server.contentcapture;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
@@ -56,8 +55,7 @@
ContentCaptureServerSession(@NonNull Context context, int userId, @NonNull Object lock,
@NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service,
@NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName,
- int taskId, int displayId, @NonNull String sessionId, int uid,
- @Nullable ContentCaptureContext clientContext, int flags,
+ int taskId, int displayId, @NonNull String sessionId, int uid, int flags,
boolean bindInstantServiceAllowed, boolean verbose) {
mLock = lock;
mActivityToken = activityToken;
@@ -67,8 +65,8 @@
mRemoteService = new RemoteContentCaptureService(context,
ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, userId, this,
bindInstantServiceAllowed, verbose);
- mContentCaptureContext = new ContentCaptureContext(clientContext, appComponentName, taskId,
- displayId, flags);
+ mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null,
+ appComponentName, taskId, displayId, flags);
}
/**