Implemented nested Content Capture sessions.
Test: atest CtsContentCaptureServiceTestCases
Fixes: 121033016
Change-Id: I46bbd05c363cbda8b66704203455411d38c6a025
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 5166831..04e725e 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -33,7 +33,7 @@
final class ChildContentCaptureSession extends ContentCaptureSession {
@NonNull
- private final MainContentCaptureSession mParent;
+ private final ContentCaptureSession mParent;
/**
* {@link ContentCaptureContext} set by client, or {@code null} when it's the
@@ -46,16 +46,25 @@
private final ContentCaptureContext mClientContext;
/** @hide */
- protected ChildContentCaptureSession(@NonNull MainContentCaptureSession parent,
+ protected ChildContentCaptureSession(@NonNull ContentCaptureSession 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");
+ MainContentCaptureSession getMainCaptureSession() {
+ if (mParent instanceof MainContentCaptureSession) {
+ return (MainContentCaptureSession) mParent;
+ }
+ return mParent.getMainCaptureSession();
+ }
+
+ @Override
+ ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
+ final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
+ getMainCaptureSession().notifyChildSessionStarted(mId, child.mId, clientContext);
+ return child;
}
@Override
@@ -65,27 +74,27 @@
@Override
void onDestroy() {
- mParent.notifyChildSessionFinished(mParent.mId, mId);
+ getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId);
}
@Override
void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
- mParent.notifyViewAppeared(mId, node);
+ getMainCaptureSession().notifyViewAppeared(mId, node);
}
@Override
void internalNotifyViewDisappeared(@NonNull AutofillId id) {
- mParent.notifyViewDisappeared(mId, id);
+ getMainCaptureSession().notifyViewDisappeared(mId, id);
}
@Override
void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
int flags) {
- mParent.notifyViewTextChanged(mId, id, text, flags);
+ getMainCaptureSession().notifyViewTextChanged(mId, id, text, flags);
}
@Override
boolean isContentCaptureEnabled() {
- return mParent.isContentCaptureEnabled();
+ return getMainCaptureSession().isContentCaptureEnabled();
}
@Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 9830790..e962e7c 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -117,13 +117,11 @@
/** @hide */
public void onActivityStarted(@NonNull IBinder applicationToken,
@NonNull ComponentName activityComponent) {
- // TODO(b/121033016): must start all sessions
getMainContentCaptureSession().start(applicationToken, activityComponent);
}
/** @hide */
public void onActivityStopped() {
- // TODO(b/121033016): must finish all sessions
getMainContentCaptureSession().destroy();
}
@@ -135,7 +133,6 @@
* @hide
*/
public void flush() {
- // TODO(b/121033016): must flush all sessions
getMainContentCaptureSession().flush();
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 293ba83..344b9973 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -27,6 +27,7 @@
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
@@ -34,7 +35,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Session used to notify a system-provided Content Capture service about events associated with
@@ -90,11 +90,14 @@
private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final Object mLock = new Object();
+
/**
* Guard use to ignore events after it's destroyed.
*/
@NonNull
- private final AtomicBoolean mDestroyed = new AtomicBoolean();
+ @GuardedBy("mLock")
+ private boolean mDestroyed;
/** @hide */
@Nullable
@@ -108,11 +111,8 @@
/**
* 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
+ @GuardedBy("mLock")
private ArrayList<ContentCaptureSession> mChildren;
/** @hide */
@@ -120,6 +120,10 @@
mCloseGuard.open("destroy");
}
+ /** @hide */
+ @NonNull
+ abstract MainContentCaptureSession getMainCaptureSession();
+
/**
* Gets the id used to identify this session.
*/
@@ -143,10 +147,12 @@
Log.d(TAG, "createContentCaptureSession(" + context + ": parent=" + mId + ", child="
+ child.mId);
}
- if (mChildren == null) {
- mChildren = new ArrayList<>(INITIAL_CHILDREN_CAPACITY);
+ synchronized (mLock) {
+ if (mChildren == null) {
+ mChildren = new ArrayList<>(INITIAL_CHILDREN_CAPACITY);
+ }
+ mChildren.add(child);
}
- mChildren.add(child);
return child;
}
@@ -163,29 +169,31 @@
* <p>Once destroyed, any new notification will be dropped.
*/
public final void destroy() {
- if (!mDestroyed.compareAndSet(false, true)) {
- Log.e(TAG, "destroy(" + mId + "): already destroyed");
- return;
- }
+ synchronized (mLock) {
+ if (mDestroyed) {
+ Log.e(TAG, "destroy(" + mId + "): already destroyed");
+ return;
+ }
+ mDestroyed = true;
- mCloseGuard.close();
+ 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(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
- }
-
- // Finish children first
- if (mChildren != null) {
- final int numberChildren = mChildren.size();
- if (VERBOSE) Log.v(TAG, "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(TAG, "exception destroying child session #" + i + ": " + e);
+ //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(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
+ }
+ // Finish children first
+ if (mChildren != null) {
+ final int numberChildren = mChildren.size();
+ if (VERBOSE) Log.v(TAG, "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(TAG, "exception destroying child session #" + i + ": " + e);
+ }
}
}
}
@@ -305,23 +313,26 @@
}
boolean isContentCaptureEnabled() {
- return !mDestroyed.get();
+ synchronized (mLock) {
+ return !mDestroyed;
+ }
}
@CallSuper
void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("id: "); pw.println(mId);
- pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed.get());
- if (mChildren != null && !mChildren.isEmpty()) {
- final String prefix2 = prefix + " ";
- final int numberChildren = mChildren.size();
- pw.print(prefix); pw.print("number children: "); pw.println(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);
+ synchronized (mLock) {
+ pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
+ if (mChildren != null && !mChildren.isEmpty()) {
+ final String prefix2 = prefix + " ";
+ final int numberChildren = mChildren.size();
+ pw.print(prefix); pw.print("number children: "); pw.println(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
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index c23c85a..44a381e 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -141,6 +141,11 @@
}
@Override
+ MainContentCaptureSession getMainCaptureSession() {
+ return this;
+ }
+
+ @Override
ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
notifyChildSessionStarted(mId, child.mId, clientContext);
@@ -389,14 +394,14 @@
handleResetSession(/* resetState= */ true);
}
- // TODO(b/121033016): once we support multiple sessions, we might need to move some of these
+ // TODO(b/122454205): 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)
+ // TODO(b/122454205): must reset children (which currently is owned by superclass)
mApplicationToken = null;
mComponentName = null;
mEvents = null;
@@ -429,7 +434,7 @@
&& !mDisabled.get();
}
- // TODO(b/121033016): refactor "notifyXXXX" methods below to a common "Buffer" object that is
+ // TODO(b/122454205): 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,