Loading core/java/android/view/ViewRootImpl.java +11 −49 Original line number Original line Diff line number Diff line Loading @@ -130,7 +130,6 @@ import android.graphics.FrameInfo; import android.graphics.HardwareRenderer; import android.graphics.HardwareRenderer; import android.graphics.HardwareRenderer.FrameDrawingCallback; import android.graphics.HardwareRenderer.FrameDrawingCallback; import android.graphics.HardwareRendererObserver; import android.graphics.HardwareRendererObserver; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PixelFormat; Loading Loading @@ -206,7 +205,6 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.Interpolator; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureSession; import android.view.contentcapture.ContentCaptureSession; Loading Loading @@ -4029,7 +4027,6 @@ public final class ViewRootImpl implements ViewParent, } } private void notifyContentCaptureEvents() { private void notifyContentCaptureEvents() { try { if (!isContentCaptureEnabled()) { if (!isContentCaptureEnabled()) { if (DEBUG_CONTENT_CAPTURE) { if (DEBUG_CONTENT_CAPTURE) { Log.d(mTag, "notifyContentCaptureEvents while disabled"); Log.d(mTag, "notifyContentCaptureEvents while disabled"); Loading @@ -4037,48 +4034,13 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mContentCaptureEvents = null; mAttachInfo.mContentCaptureEvents = null; return; return; } } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); final ContentCaptureManager manager = mAttachInfo.mContentCaptureManager; } if (manager != null && mAttachInfo.mContentCaptureEvents != null) { MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager final MainContentCaptureSession session = manager.getMainContentCaptureSession(); .getMainContentCaptureSession(); session.notifyContentCaptureEvents(mAttachInfo.mContentCaptureEvents); for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) { int sessionId = mAttachInfo.mContentCaptureEvents.keyAt(i); mainSession.notifyViewTreeEvent(sessionId, /* started= */ true); ArrayList<Object> events = mAttachInfo.mContentCaptureEvents .valueAt(i); for_each_event: for (int j = 0; j < events.size(); j++) { Object event = events.get(j); if (event instanceof AutofillId) { mainSession.notifyViewDisappeared(sessionId, (AutofillId) event); } else if (event instanceof View) { View view = (View) event; ContentCaptureSession session = view.getContentCaptureSession(); if (session == null) { Log.w(mTag, "no content capture session on view: " + view); continue for_each_event; } int actualId = session.getId(); if (actualId != sessionId) { Log.w(mTag, "content capture session mismatch for view (" + view + "): was " + sessionId + " before, it's " + actualId + " now"); continue for_each_event; } ViewStructure structure = session.newViewStructure(view); view.onProvideContentCaptureStructure(structure, /* flags= */ 0); session.notifyViewAppeared(structure); } else if (event instanceof Insets) { mainSession.notifyViewInsetsChanged(sessionId, (Insets) event); } else { Log.w(mTag, "invalid content capture event: " + event); } } mainSession.notifyViewTreeEvent(sessionId, /* started= */ false); } } mAttachInfo.mContentCaptureEvents = null; mAttachInfo.mContentCaptureEvents = null; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } } private void notifyHolderSurfaceDestroyed() { private void notifyHolderSurfaceDestroyed() { Loading core/java/android/view/contentcapture/ContentCaptureManager.java +20 −10 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.toSet; import static android.view.contentcapture.ContentCaptureHelper.toSet; import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled; import android.annotation.CallbackExecutor; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.IntDef; Loading Loading @@ -52,6 +53,7 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.RingBuffer; import com.android.internal.util.RingBuffer; import com.android.internal.util.SyncResultReceiver; import com.android.internal.util.SyncResultReceiver; Loading Loading @@ -495,10 +497,9 @@ public final class ContentCaptureManager { @GuardedBy("mLock") @GuardedBy("mLock") private int mFlags; private int mFlags; // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler @Nullable // held at the Application level @GuardedBy("mLock") @NonNull private Handler mHandler; private final Handler mHandler; @GuardedBy("mLock") @GuardedBy("mLock") private MainContentCaptureSession mMainSession; private MainContentCaptureSession mMainSession; Loading Loading @@ -562,11 +563,6 @@ public final class ContentCaptureManager { if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName()); if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName()); // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we // do, then we should optimize it to run the tests after the Choreographer finishes the most // important steps of the frame. mHandler = Handler.createAsync(Looper.getMainLooper()); mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); if (mOptions.contentProtectionOptions.enableReceiver if (mOptions.contentProtectionOptions.enableReceiver Loading Loading @@ -594,13 +590,27 @@ public final class ContentCaptureManager { public MainContentCaptureSession getMainContentCaptureSession() { public MainContentCaptureSession getMainContentCaptureSession() { synchronized (mLock) { synchronized (mLock) { if (mMainSession == null) { if (mMainSession == null) { mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService); mMainSession = new MainContentCaptureSession( mContext, this, prepareContentCaptureHandler(), mService); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); } } return mMainSession; return mMainSession; } } } } @NonNull @GuardedBy("mLock") private Handler prepareContentCaptureHandler() { if (mHandler == null) { if (runOnBackgroundThreadEnabled()) { mHandler = BackgroundThread.getHandler(); } else { mHandler = Handler.createAsync(Looper.getMainLooper()); } } return mHandler; } /** @hide */ /** @hide */ @UiThread @UiThread public void onActivityCreated(@NonNull IBinder applicationToken, public void onActivityCreated(@NonNull IBinder applicationToken, Loading core/java/android/view/contentcapture/MainContentCaptureSession.java +135 −39 Original line number Original line Diff line number Diff line Loading @@ -34,7 +34,6 @@ import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALS import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.UiThread; import android.content.ComponentName; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice; import android.graphics.Insets; import android.graphics.Insets; Loading @@ -50,7 +49,10 @@ import android.text.Spannable; import android.text.TextUtils; import android.text.TextUtils; import android.util.LocalLog; import android.util.LocalLog; import android.util.Log; import android.util.Log; import android.util.SparseArray; import android.util.TimeUtils; import android.util.TimeUtils; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; import android.view.contentcapture.ViewNode.ViewStructureImpl; import android.view.contentprotection.ContentProtectionEventProcessor; import android.view.contentprotection.ContentProtectionEventProcessor; Loading Loading @@ -207,7 +209,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } else { } else { binder = null; binder = null; } } mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder)); mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder)); } } } } Loading Loading @@ -244,9 +247,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** /** * Starts this session. * Starts this session. */ */ @UiThread void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { @NonNull ComponentName component, int flags) { runOnContentCaptureThread(() -> startImpl(token, shareableActivityToken, component, flags)); } private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { checkOnContentCaptureThread(); if (!isContentCaptureEnabled()) return; if (!isContentCaptureEnabled()) return; if (sVerbose) { if (sVerbose) { Loading Loading @@ -280,17 +288,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } } } } @Override @Override void onDestroy() { void onDestroy() { mHandler.removeMessages(MSG_FLUSH); clearAndRunOnContentCaptureThread(() -> { mHandler.post(() -> { try { try { flush(FLUSH_REASON_SESSION_FINISHED); flush(FLUSH_REASON_SESSION_FINISHED); } finally { } finally { destroySession(); destroySession(); } } }); }, MSG_FLUSH); } } /** /** Loading @@ -302,8 +308,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * @hide * @hide */ */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void onSessionStarted(int resultCode, @Nullable IBinder binder) { public void onSessionStarted(int resultCode, @Nullable IBinder binder) { checkOnContentCaptureThread(); if (binder != null) { if (binder != null) { mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); mDirectServiceVulture = () -> { mDirectServiceVulture = () -> { Loading Loading @@ -347,13 +353,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void sendEvent(@NonNull ContentCaptureEvent event) { public void sendEvent(@NonNull ContentCaptureEvent event) { sendEvent(event, /* forceFlush= */ false); sendEvent(event, /* forceFlush= */ false); } } @UiThread private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { checkOnContentCaptureThread(); final int eventType = event.getType(); final int eventType = event.getType(); if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED Loading Loading @@ -396,15 +401,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } } } @UiThread private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { checkOnContentCaptureThread(); if (mContentProtectionEventProcessor != null) { if (mContentProtectionEventProcessor != null) { mContentProtectionEventProcessor.processEvent(event); mContentProtectionEventProcessor.processEvent(event); } } } } @UiThread private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { checkOnContentCaptureThread(); final int eventType = event.getType(); final int eventType = event.getType(); final int maxBufferSize = mManager.mOptions.maxBufferSize; final int maxBufferSize = mManager.mOptions.maxBufferSize; if (mEvents == null) { if (mEvents == null) { Loading Loading @@ -538,13 +543,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession { flush(flushReason); flush(flushReason); } } @UiThread private boolean hasStarted() { private boolean hasStarted() { checkOnContentCaptureThread(); return mState != UNKNOWN_STATE; return mState != UNKNOWN_STATE; } } @UiThread private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { checkOnContentCaptureThread(); if (sVerbose) { if (sVerbose) { Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + ", checkExisting=" + checkExisting); + ", checkExisting=" + checkExisting); Loading Loading @@ -588,8 +593,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); } } @UiThread private void flushIfNeeded(@FlushReason int reason) { private void flushIfNeeded(@FlushReason int reason) { checkOnContentCaptureThread(); if (mEvents == null || mEvents.isEmpty()) { if (mEvents == null || mEvents.isEmpty()) { if (sVerbose) Log.v(TAG, "Nothing to flush"); if (sVerbose) Log.v(TAG, "Nothing to flush"); return; return; Loading @@ -600,8 +605,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override @Override @UiThread public void flush(@FlushReason int reason) { public void flush(@FlushReason int reason) { runOnContentCaptureThread(() -> flushImpl(reason)); } private void flushImpl(@FlushReason int reason) { checkOnContentCaptureThread(); if (mEvents == null || mEvents.size() == 0) { if (mEvents == null || mEvents.size() == 0) { if (sVerbose) { if (sVerbose) { Log.v(TAG, "Don't flush for empty event buffer."); Log.v(TAG, "Don't flush for empty event buffer."); Loading Loading @@ -669,8 +678,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * Resets the buffer and return a {@link ParceledListSlice} with the previous events. * Resets the buffer and return a {@link ParceledListSlice} with the previous events. */ */ @NonNull @NonNull @UiThread private ParceledListSlice<ContentCaptureEvent> clearEvents() { private ParceledListSlice<ContentCaptureEvent> clearEvents() { checkOnContentCaptureThread(); // NOTE: we must save a reference to the current mEvents and then set it to to null, // NOTE: we must save a reference to the current mEvents and then set it to to null, // otherwise clearing it would clear it in the receiving side if the service is also local. // otherwise clearing it would clear it in the receiving side if the service is also local. if (mEvents == null) { if (mEvents == null) { Loading @@ -684,8 +693,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** hide */ /** hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void destroySession() { public void destroySession() { checkOnContentCaptureThread(); if (sDebug) { if (sDebug) { Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " Loading @@ -710,8 +719,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // clearings out. // clearings out. /** @hide */ /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void resetSession(int newState) { public void resetSession(int newState) { checkOnContentCaptureThread(); if (sVerbose) { if (sVerbose) { Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " + getStateAsString(mState) + " to " + getStateAsString(newState)); + getStateAsString(mState) + " to " + getStateAsString(newState)); Loading Loading @@ -794,24 +803,26 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // change should also get get rid of the "internalNotifyXXXX" methods above // change should also get get rid of the "internalNotifyXXXX" methods above void notifyChildSessionStarted(int parentSessionId, int childSessionId, void notifyChildSessionStarted(int parentSessionId, int childSessionId, @NonNull ContentCaptureContext clientContext) { @NonNull ContentCaptureContext clientContext) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) runOnContentCaptureThread( () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) .setParentSessionId(parentSessionId).setClientContext(clientContext), .setParentSessionId(parentSessionId).setClientContext(clientContext), FORCE_FLUSH)); FORCE_FLUSH)); } } void notifyChildSessionFinished(int parentSessionId, int childSessionId) { void notifyChildSessionFinished(int parentSessionId, int childSessionId) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) runOnContentCaptureThread( () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) .setParentSessionId(parentSessionId), FORCE_FLUSH)); .setParentSessionId(parentSessionId), FORCE_FLUSH)); } } void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) runOnContentCaptureThread(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) .setViewNode(node.mNode))); .setViewNode(node.mNode))); } } /** Public because is also used by ViewRootImpl */ void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { runOnContentCaptureThread(() -> sendEvent( mHandler.post(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id))); new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id))); } } Loading @@ -836,52 +847,102 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final int startIndex = Selection.getSelectionStart(text); final int startIndex = Selection.getSelectionStart(text); final int endIndex = Selection.getSelectionEnd(text); final int endIndex = Selection.getSelectionEnd(text); mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) .setAutofillId(id).setText(eventText) .setAutofillId(id).setText(eventText) .setComposingIndex(composingStart, composingEnd) .setComposingIndex(composingStart, composingEnd) .setSelectionIndex(startIndex, endIndex))); .setSelectionIndex(startIndex, endIndex))); } } /** Public because is also used by ViewRootImpl */ void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { runOnContentCaptureThread(() -> mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) .setInsets(viewInsets))); .setInsets(viewInsets))); } } /** Public because is also used by ViewRootImpl */ void notifyViewTreeEvent(int sessionId, boolean started) { public void notifyViewTreeEvent(int sessionId, boolean started) { final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, type), new ContentCaptureEvent(sessionId, type), disableFlush ? !started : FORCE_FLUSH)); disableFlush ? !started : FORCE_FLUSH)); } } void notifySessionResumed(int sessionId) { void notifySessionResumed(int sessionId) { mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH)); new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH)); } } void notifySessionPaused(int sessionId) { void notifySessionPaused(int sessionId) { mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH)); new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH)); } } void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) runOnContentCaptureThread(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) .setClientContext(context), FORCE_FLUSH)); .setClientContext(context), FORCE_FLUSH)); } } /** public because is also used by ViewRootImpl */ /** public because is also used by ViewRootImpl */ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) .setBounds(bounds) .setBounds(bounds) )); )); } } /** public because is also used by ViewRootImpl */ public void notifyContentCaptureEvents( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents)); } private void notifyContentCaptureEventsImpl( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { checkOnContentCaptureThread(); try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); } for (int i = 0; i < contentCaptureEvents.size(); i++) { int sessionId = contentCaptureEvents.keyAt(i); notifyViewTreeEvent(sessionId, /* started= */ true); ArrayList<Object> events = contentCaptureEvents.valueAt(i); for_each_event: for (int j = 0; j < events.size(); j++) { Object event = events.get(j); if (event instanceof AutofillId) { notifyViewDisappeared(sessionId, (AutofillId) event); } else if (event instanceof View) { View view = (View) event; ContentCaptureSession session = view.getContentCaptureSession(); if (session == null) { Log.w(TAG, "no content capture session on view: " + view); continue for_each_event; } int actualId = session.getId(); if (actualId != sessionId) { Log.w(TAG, "content capture session mismatch for view (" + view + "): was " + sessionId + " before, it's " + actualId + " now"); continue for_each_event; } ViewStructure structure = session.newViewStructure(view); view.onProvideContentCaptureStructure(structure, /* flags= */ 0); session.notifyViewAppeared(structure); } else if (event instanceof Insets) { notifyViewInsetsChanged(sessionId, (Insets) event); } else { Log.w(TAG, "invalid content capture event: " + event); } } notifyViewTreeEvent(sessionId, /* started= */ false); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } @Override @Override void dump(@NonNull String prefix, @NonNull PrintWriter pw) { void dump(@NonNull String prefix, @NonNull PrintWriter pw) { super.dump(prefix, pw); super.dump(prefix, pw); Loading Loading @@ -960,17 +1021,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { return getDebugState() + ", reason=" + getFlushReasonAsString(reason); return getDebugState() + ", reason=" + getFlushReasonAsString(reason); } } @UiThread private boolean isContentProtectionReceiverEnabled() { private boolean isContentProtectionReceiverEnabled() { return mManager.mOptions.contentProtectionOptions.enableReceiver; return mManager.mOptions.contentProtectionOptions.enableReceiver; } } @UiThread private boolean isContentCaptureReceiverEnabled() { private boolean isContentCaptureReceiverEnabled() { return mManager.mOptions.enableReceiver; return mManager.mOptions.enableReceiver; } } @UiThread private boolean isContentProtectionEnabled() { private boolean isContentProtectionEnabled() { // Should not be possible for mComponentName to be null here but check anyway // Should not be possible for mComponentName to be null here but check anyway // Should not be possible for groups to be empty if receiver is enabled but check anyway // Should not be possible for groups to be empty if receiver is enabled but check anyway Loading @@ -980,4 +1038,42 @@ public final class MainContentCaptureSession extends ContentCaptureSession { && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); } } /** * Checks that the current work is running on the assigned thread from {@code mHandler}. * * <p>It is not guaranteed that the callers always invoke function from a single thread. * Therefore, accessing internal properties in {@link MainContentCaptureSession} should * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> */ private void checkOnContentCaptureThread() { // TODO(b/309411951): Add metrics to track the issue instead. final boolean onContentCaptureThread = mHandler.getLooper().isCurrentThread(); if (!onContentCaptureThread) { Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread()); } } /** * Ensures that {@code r} will be running on the assigned thread. * * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable. * </p> */ private void runOnContentCaptureThread(@NonNull Runnable r) { if (!mHandler.getLooper().isCurrentThread()) { mHandler.post(r); } else { r.run(); } } private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { if (!mHandler.getLooper().isCurrentThread()) { mHandler.removeMessages(what); mHandler.post(r); } else { r.run(); } } } } core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +0 −9 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package android.view.contentprotection; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.UiThread; import android.content.ContentCaptureOptions; import android.content.ContentCaptureOptions; import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice; import android.os.Handler; import android.os.Handler; Loading Loading @@ -102,7 +101,6 @@ public class ContentProtectionEventProcessor { } } /** Main entry point for {@link ContentCaptureEvent} processing. */ /** Main entry point for {@link ContentCaptureEvent} processing. */ @UiThread public void processEvent(@NonNull ContentCaptureEvent event) { public void processEvent(@NonNull ContentCaptureEvent event) { if (EVENT_TYPES_TO_STORE.contains(event.getType())) { if (EVENT_TYPES_TO_STORE.contains(event.getType())) { storeEvent(event); storeEvent(event); Loading @@ -112,7 +110,6 @@ public class ContentProtectionEventProcessor { } } } } @UiThread private void storeEvent(@NonNull ContentCaptureEvent event) { private void storeEvent(@NonNull ContentCaptureEvent event) { // Ensure receiver gets the package name which might not be set // Ensure receiver gets the package name which might not be set ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode(); ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode(); Loading @@ -121,7 +118,6 @@ public class ContentProtectionEventProcessor { mEventBuffer.append(event); mEventBuffer.append(event); } } @UiThread private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { ViewNode viewNode = event.getViewNode(); ViewNode viewNode = event.getViewNode(); String eventText = ContentProtectionUtils.getEventTextLower(event); String eventText = ContentProtectionUtils.getEventTextLower(event); Loading Loading @@ -154,7 +150,6 @@ public class ContentProtectionEventProcessor { } } } } @UiThread private void loginDetected() { private void loginDetected() { if (mLastFlushTime == null if (mLastFlushTime == null || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { Loading @@ -163,13 +158,11 @@ public class ContentProtectionEventProcessor { resetLoginFlags(); resetLoginFlags(); } } @UiThread private void resetLoginFlags() { private void resetLoginFlags() { mGroupsAll.forEach(group -> group.mFound = false); mGroupsAll.forEach(group -> group.mFound = false); mAnyGroupFound = false; mAnyGroupFound = false; } } @UiThread private void maybeResetLoginFlags() { private void maybeResetLoginFlags() { if (mAnyGroupFound) { if (mAnyGroupFound) { if (mResetLoginRemainingEventsToProcess <= 0) { if (mResetLoginRemainingEventsToProcess <= 0) { Loading @@ -183,7 +176,6 @@ public class ContentProtectionEventProcessor { } } } } @UiThread private void flush() { private void flush() { mLastFlushTime = Instant.now(); mLastFlushTime = Instant.now(); Loading @@ -192,7 +184,6 @@ public class ContentProtectionEventProcessor { mHandler.post(() -> handlerOnLoginDetected(events)); mHandler.post(() -> handlerOnLoginDetected(events)); } } @UiThread @NonNull @NonNull private ParceledListSlice<ContentCaptureEvent> clearEvents() { private ParceledListSlice<ContentCaptureEvent> clearEvents() { List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray()); List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray()); Loading core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +143 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/view/ViewRootImpl.java +11 −49 Original line number Original line Diff line number Diff line Loading @@ -130,7 +130,6 @@ import android.graphics.FrameInfo; import android.graphics.HardwareRenderer; import android.graphics.HardwareRenderer; import android.graphics.HardwareRenderer.FrameDrawingCallback; import android.graphics.HardwareRenderer.FrameDrawingCallback; import android.graphics.HardwareRendererObserver; import android.graphics.HardwareRendererObserver; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PixelFormat; Loading Loading @@ -206,7 +205,6 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.Interpolator; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureSession; import android.view.contentcapture.ContentCaptureSession; Loading Loading @@ -4029,7 +4027,6 @@ public final class ViewRootImpl implements ViewParent, } } private void notifyContentCaptureEvents() { private void notifyContentCaptureEvents() { try { if (!isContentCaptureEnabled()) { if (!isContentCaptureEnabled()) { if (DEBUG_CONTENT_CAPTURE) { if (DEBUG_CONTENT_CAPTURE) { Log.d(mTag, "notifyContentCaptureEvents while disabled"); Log.d(mTag, "notifyContentCaptureEvents while disabled"); Loading @@ -4037,48 +4034,13 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mContentCaptureEvents = null; mAttachInfo.mContentCaptureEvents = null; return; return; } } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); final ContentCaptureManager manager = mAttachInfo.mContentCaptureManager; } if (manager != null && mAttachInfo.mContentCaptureEvents != null) { MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager final MainContentCaptureSession session = manager.getMainContentCaptureSession(); .getMainContentCaptureSession(); session.notifyContentCaptureEvents(mAttachInfo.mContentCaptureEvents); for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) { int sessionId = mAttachInfo.mContentCaptureEvents.keyAt(i); mainSession.notifyViewTreeEvent(sessionId, /* started= */ true); ArrayList<Object> events = mAttachInfo.mContentCaptureEvents .valueAt(i); for_each_event: for (int j = 0; j < events.size(); j++) { Object event = events.get(j); if (event instanceof AutofillId) { mainSession.notifyViewDisappeared(sessionId, (AutofillId) event); } else if (event instanceof View) { View view = (View) event; ContentCaptureSession session = view.getContentCaptureSession(); if (session == null) { Log.w(mTag, "no content capture session on view: " + view); continue for_each_event; } int actualId = session.getId(); if (actualId != sessionId) { Log.w(mTag, "content capture session mismatch for view (" + view + "): was " + sessionId + " before, it's " + actualId + " now"); continue for_each_event; } ViewStructure structure = session.newViewStructure(view); view.onProvideContentCaptureStructure(structure, /* flags= */ 0); session.notifyViewAppeared(structure); } else if (event instanceof Insets) { mainSession.notifyViewInsetsChanged(sessionId, (Insets) event); } else { Log.w(mTag, "invalid content capture event: " + event); } } mainSession.notifyViewTreeEvent(sessionId, /* started= */ false); } } mAttachInfo.mContentCaptureEvents = null; mAttachInfo.mContentCaptureEvents = null; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } } private void notifyHolderSurfaceDestroyed() { private void notifyHolderSurfaceDestroyed() { Loading
core/java/android/view/contentcapture/ContentCaptureManager.java +20 −10 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.toSet; import static android.view.contentcapture.ContentCaptureHelper.toSet; import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled; import android.annotation.CallbackExecutor; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.IntDef; Loading Loading @@ -52,6 +53,7 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.RingBuffer; import com.android.internal.util.RingBuffer; import com.android.internal.util.SyncResultReceiver; import com.android.internal.util.SyncResultReceiver; Loading Loading @@ -495,10 +497,9 @@ public final class ContentCaptureManager { @GuardedBy("mLock") @GuardedBy("mLock") private int mFlags; private int mFlags; // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler @Nullable // held at the Application level @GuardedBy("mLock") @NonNull private Handler mHandler; private final Handler mHandler; @GuardedBy("mLock") @GuardedBy("mLock") private MainContentCaptureSession mMainSession; private MainContentCaptureSession mMainSession; Loading Loading @@ -562,11 +563,6 @@ public final class ContentCaptureManager { if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName()); if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName()); // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we // do, then we should optimize it to run the tests after the Choreographer finishes the most // important steps of the frame. mHandler = Handler.createAsync(Looper.getMainLooper()); mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); if (mOptions.contentProtectionOptions.enableReceiver if (mOptions.contentProtectionOptions.enableReceiver Loading Loading @@ -594,13 +590,27 @@ public final class ContentCaptureManager { public MainContentCaptureSession getMainContentCaptureSession() { public MainContentCaptureSession getMainContentCaptureSession() { synchronized (mLock) { synchronized (mLock) { if (mMainSession == null) { if (mMainSession == null) { mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService); mMainSession = new MainContentCaptureSession( mContext, this, prepareContentCaptureHandler(), mService); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); } } return mMainSession; return mMainSession; } } } } @NonNull @GuardedBy("mLock") private Handler prepareContentCaptureHandler() { if (mHandler == null) { if (runOnBackgroundThreadEnabled()) { mHandler = BackgroundThread.getHandler(); } else { mHandler = Handler.createAsync(Looper.getMainLooper()); } } return mHandler; } /** @hide */ /** @hide */ @UiThread @UiThread public void onActivityCreated(@NonNull IBinder applicationToken, public void onActivityCreated(@NonNull IBinder applicationToken, Loading
core/java/android/view/contentcapture/MainContentCaptureSession.java +135 −39 Original line number Original line Diff line number Diff line Loading @@ -34,7 +34,6 @@ import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALS import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.UiThread; import android.content.ComponentName; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice; import android.graphics.Insets; import android.graphics.Insets; Loading @@ -50,7 +49,10 @@ import android.text.Spannable; import android.text.TextUtils; import android.text.TextUtils; import android.util.LocalLog; import android.util.LocalLog; import android.util.Log; import android.util.Log; import android.util.SparseArray; import android.util.TimeUtils; import android.util.TimeUtils; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; import android.view.contentcapture.ViewNode.ViewStructureImpl; import android.view.contentprotection.ContentProtectionEventProcessor; import android.view.contentprotection.ContentProtectionEventProcessor; Loading Loading @@ -207,7 +209,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } else { } else { binder = null; binder = null; } } mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder)); mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder)); } } } } Loading Loading @@ -244,9 +247,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** /** * Starts this session. * Starts this session. */ */ @UiThread void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { @NonNull ComponentName component, int flags) { runOnContentCaptureThread(() -> startImpl(token, shareableActivityToken, component, flags)); } private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { checkOnContentCaptureThread(); if (!isContentCaptureEnabled()) return; if (!isContentCaptureEnabled()) return; if (sVerbose) { if (sVerbose) { Loading Loading @@ -280,17 +288,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } } } } @Override @Override void onDestroy() { void onDestroy() { mHandler.removeMessages(MSG_FLUSH); clearAndRunOnContentCaptureThread(() -> { mHandler.post(() -> { try { try { flush(FLUSH_REASON_SESSION_FINISHED); flush(FLUSH_REASON_SESSION_FINISHED); } finally { } finally { destroySession(); destroySession(); } } }); }, MSG_FLUSH); } } /** /** Loading @@ -302,8 +308,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * @hide * @hide */ */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void onSessionStarted(int resultCode, @Nullable IBinder binder) { public void onSessionStarted(int resultCode, @Nullable IBinder binder) { checkOnContentCaptureThread(); if (binder != null) { if (binder != null) { mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); mDirectServiceVulture = () -> { mDirectServiceVulture = () -> { Loading Loading @@ -347,13 +353,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void sendEvent(@NonNull ContentCaptureEvent event) { public void sendEvent(@NonNull ContentCaptureEvent event) { sendEvent(event, /* forceFlush= */ false); sendEvent(event, /* forceFlush= */ false); } } @UiThread private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { checkOnContentCaptureThread(); final int eventType = event.getType(); final int eventType = event.getType(); if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED Loading Loading @@ -396,15 +401,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } } } @UiThread private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { checkOnContentCaptureThread(); if (mContentProtectionEventProcessor != null) { if (mContentProtectionEventProcessor != null) { mContentProtectionEventProcessor.processEvent(event); mContentProtectionEventProcessor.processEvent(event); } } } } @UiThread private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { checkOnContentCaptureThread(); final int eventType = event.getType(); final int eventType = event.getType(); final int maxBufferSize = mManager.mOptions.maxBufferSize; final int maxBufferSize = mManager.mOptions.maxBufferSize; if (mEvents == null) { if (mEvents == null) { Loading Loading @@ -538,13 +543,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession { flush(flushReason); flush(flushReason); } } @UiThread private boolean hasStarted() { private boolean hasStarted() { checkOnContentCaptureThread(); return mState != UNKNOWN_STATE; return mState != UNKNOWN_STATE; } } @UiThread private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { checkOnContentCaptureThread(); if (sVerbose) { if (sVerbose) { Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + ", checkExisting=" + checkExisting); + ", checkExisting=" + checkExisting); Loading Loading @@ -588,8 +593,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); } } @UiThread private void flushIfNeeded(@FlushReason int reason) { private void flushIfNeeded(@FlushReason int reason) { checkOnContentCaptureThread(); if (mEvents == null || mEvents.isEmpty()) { if (mEvents == null || mEvents.isEmpty()) { if (sVerbose) Log.v(TAG, "Nothing to flush"); if (sVerbose) Log.v(TAG, "Nothing to flush"); return; return; Loading @@ -600,8 +605,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override @Override @UiThread public void flush(@FlushReason int reason) { public void flush(@FlushReason int reason) { runOnContentCaptureThread(() -> flushImpl(reason)); } private void flushImpl(@FlushReason int reason) { checkOnContentCaptureThread(); if (mEvents == null || mEvents.size() == 0) { if (mEvents == null || mEvents.size() == 0) { if (sVerbose) { if (sVerbose) { Log.v(TAG, "Don't flush for empty event buffer."); Log.v(TAG, "Don't flush for empty event buffer."); Loading Loading @@ -669,8 +678,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * Resets the buffer and return a {@link ParceledListSlice} with the previous events. * Resets the buffer and return a {@link ParceledListSlice} with the previous events. */ */ @NonNull @NonNull @UiThread private ParceledListSlice<ContentCaptureEvent> clearEvents() { private ParceledListSlice<ContentCaptureEvent> clearEvents() { checkOnContentCaptureThread(); // NOTE: we must save a reference to the current mEvents and then set it to to null, // NOTE: we must save a reference to the current mEvents and then set it to to null, // otherwise clearing it would clear it in the receiving side if the service is also local. // otherwise clearing it would clear it in the receiving side if the service is also local. if (mEvents == null) { if (mEvents == null) { Loading @@ -684,8 +693,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** hide */ /** hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void destroySession() { public void destroySession() { checkOnContentCaptureThread(); if (sDebug) { if (sDebug) { Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " Loading @@ -710,8 +719,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // clearings out. // clearings out. /** @hide */ /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @UiThread public void resetSession(int newState) { public void resetSession(int newState) { checkOnContentCaptureThread(); if (sVerbose) { if (sVerbose) { Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " + getStateAsString(mState) + " to " + getStateAsString(newState)); + getStateAsString(mState) + " to " + getStateAsString(newState)); Loading Loading @@ -794,24 +803,26 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // change should also get get rid of the "internalNotifyXXXX" methods above // change should also get get rid of the "internalNotifyXXXX" methods above void notifyChildSessionStarted(int parentSessionId, int childSessionId, void notifyChildSessionStarted(int parentSessionId, int childSessionId, @NonNull ContentCaptureContext clientContext) { @NonNull ContentCaptureContext clientContext) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) runOnContentCaptureThread( () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) .setParentSessionId(parentSessionId).setClientContext(clientContext), .setParentSessionId(parentSessionId).setClientContext(clientContext), FORCE_FLUSH)); FORCE_FLUSH)); } } void notifyChildSessionFinished(int parentSessionId, int childSessionId) { void notifyChildSessionFinished(int parentSessionId, int childSessionId) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) runOnContentCaptureThread( () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) .setParentSessionId(parentSessionId), FORCE_FLUSH)); .setParentSessionId(parentSessionId), FORCE_FLUSH)); } } void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) runOnContentCaptureThread(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) .setViewNode(node.mNode))); .setViewNode(node.mNode))); } } /** Public because is also used by ViewRootImpl */ void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { runOnContentCaptureThread(() -> sendEvent( mHandler.post(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id))); new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id))); } } Loading @@ -836,52 +847,102 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final int startIndex = Selection.getSelectionStart(text); final int startIndex = Selection.getSelectionStart(text); final int endIndex = Selection.getSelectionEnd(text); final int endIndex = Selection.getSelectionEnd(text); mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) .setAutofillId(id).setText(eventText) .setAutofillId(id).setText(eventText) .setComposingIndex(composingStart, composingEnd) .setComposingIndex(composingStart, composingEnd) .setSelectionIndex(startIndex, endIndex))); .setSelectionIndex(startIndex, endIndex))); } } /** Public because is also used by ViewRootImpl */ void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { runOnContentCaptureThread(() -> mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) .setInsets(viewInsets))); .setInsets(viewInsets))); } } /** Public because is also used by ViewRootImpl */ void notifyViewTreeEvent(int sessionId, boolean started) { public void notifyViewTreeEvent(int sessionId, boolean started) { final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, type), new ContentCaptureEvent(sessionId, type), disableFlush ? !started : FORCE_FLUSH)); disableFlush ? !started : FORCE_FLUSH)); } } void notifySessionResumed(int sessionId) { void notifySessionResumed(int sessionId) { mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH)); new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH)); } } void notifySessionPaused(int sessionId) { void notifySessionPaused(int sessionId) { mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH)); new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH)); } } void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) runOnContentCaptureThread(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) .setClientContext(context), FORCE_FLUSH)); .setClientContext(context), FORCE_FLUSH)); } } /** public because is also used by ViewRootImpl */ /** public because is also used by ViewRootImpl */ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { mHandler.post(() -> sendEvent( runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) .setBounds(bounds) .setBounds(bounds) )); )); } } /** public because is also used by ViewRootImpl */ public void notifyContentCaptureEvents( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents)); } private void notifyContentCaptureEventsImpl( @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { checkOnContentCaptureThread(); try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); } for (int i = 0; i < contentCaptureEvents.size(); i++) { int sessionId = contentCaptureEvents.keyAt(i); notifyViewTreeEvent(sessionId, /* started= */ true); ArrayList<Object> events = contentCaptureEvents.valueAt(i); for_each_event: for (int j = 0; j < events.size(); j++) { Object event = events.get(j); if (event instanceof AutofillId) { notifyViewDisappeared(sessionId, (AutofillId) event); } else if (event instanceof View) { View view = (View) event; ContentCaptureSession session = view.getContentCaptureSession(); if (session == null) { Log.w(TAG, "no content capture session on view: " + view); continue for_each_event; } int actualId = session.getId(); if (actualId != sessionId) { Log.w(TAG, "content capture session mismatch for view (" + view + "): was " + sessionId + " before, it's " + actualId + " now"); continue for_each_event; } ViewStructure structure = session.newViewStructure(view); view.onProvideContentCaptureStructure(structure, /* flags= */ 0); session.notifyViewAppeared(structure); } else if (event instanceof Insets) { notifyViewInsetsChanged(sessionId, (Insets) event); } else { Log.w(TAG, "invalid content capture event: " + event); } } notifyViewTreeEvent(sessionId, /* started= */ false); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } @Override @Override void dump(@NonNull String prefix, @NonNull PrintWriter pw) { void dump(@NonNull String prefix, @NonNull PrintWriter pw) { super.dump(prefix, pw); super.dump(prefix, pw); Loading Loading @@ -960,17 +1021,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { return getDebugState() + ", reason=" + getFlushReasonAsString(reason); return getDebugState() + ", reason=" + getFlushReasonAsString(reason); } } @UiThread private boolean isContentProtectionReceiverEnabled() { private boolean isContentProtectionReceiverEnabled() { return mManager.mOptions.contentProtectionOptions.enableReceiver; return mManager.mOptions.contentProtectionOptions.enableReceiver; } } @UiThread private boolean isContentCaptureReceiverEnabled() { private boolean isContentCaptureReceiverEnabled() { return mManager.mOptions.enableReceiver; return mManager.mOptions.enableReceiver; } } @UiThread private boolean isContentProtectionEnabled() { private boolean isContentProtectionEnabled() { // Should not be possible for mComponentName to be null here but check anyway // Should not be possible for mComponentName to be null here but check anyway // Should not be possible for groups to be empty if receiver is enabled but check anyway // Should not be possible for groups to be empty if receiver is enabled but check anyway Loading @@ -980,4 +1038,42 @@ public final class MainContentCaptureSession extends ContentCaptureSession { && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); } } /** * Checks that the current work is running on the assigned thread from {@code mHandler}. * * <p>It is not guaranteed that the callers always invoke function from a single thread. * Therefore, accessing internal properties in {@link MainContentCaptureSession} should * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> */ private void checkOnContentCaptureThread() { // TODO(b/309411951): Add metrics to track the issue instead. final boolean onContentCaptureThread = mHandler.getLooper().isCurrentThread(); if (!onContentCaptureThread) { Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread()); } } /** * Ensures that {@code r} will be running on the assigned thread. * * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable. * </p> */ private void runOnContentCaptureThread(@NonNull Runnable r) { if (!mHandler.getLooper().isCurrentThread()) { mHandler.post(r); } else { r.run(); } } private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { if (!mHandler.getLooper().isCurrentThread()) { mHandler.removeMessages(what); mHandler.post(r); } else { r.run(); } } } }
core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +0 −9 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package android.view.contentprotection; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.UiThread; import android.content.ContentCaptureOptions; import android.content.ContentCaptureOptions; import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice; import android.os.Handler; import android.os.Handler; Loading Loading @@ -102,7 +101,6 @@ public class ContentProtectionEventProcessor { } } /** Main entry point for {@link ContentCaptureEvent} processing. */ /** Main entry point for {@link ContentCaptureEvent} processing. */ @UiThread public void processEvent(@NonNull ContentCaptureEvent event) { public void processEvent(@NonNull ContentCaptureEvent event) { if (EVENT_TYPES_TO_STORE.contains(event.getType())) { if (EVENT_TYPES_TO_STORE.contains(event.getType())) { storeEvent(event); storeEvent(event); Loading @@ -112,7 +110,6 @@ public class ContentProtectionEventProcessor { } } } } @UiThread private void storeEvent(@NonNull ContentCaptureEvent event) { private void storeEvent(@NonNull ContentCaptureEvent event) { // Ensure receiver gets the package name which might not be set // Ensure receiver gets the package name which might not be set ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode(); ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode(); Loading @@ -121,7 +118,6 @@ public class ContentProtectionEventProcessor { mEventBuffer.append(event); mEventBuffer.append(event); } } @UiThread private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { ViewNode viewNode = event.getViewNode(); ViewNode viewNode = event.getViewNode(); String eventText = ContentProtectionUtils.getEventTextLower(event); String eventText = ContentProtectionUtils.getEventTextLower(event); Loading Loading @@ -154,7 +150,6 @@ public class ContentProtectionEventProcessor { } } } } @UiThread private void loginDetected() { private void loginDetected() { if (mLastFlushTime == null if (mLastFlushTime == null || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { Loading @@ -163,13 +158,11 @@ public class ContentProtectionEventProcessor { resetLoginFlags(); resetLoginFlags(); } } @UiThread private void resetLoginFlags() { private void resetLoginFlags() { mGroupsAll.forEach(group -> group.mFound = false); mGroupsAll.forEach(group -> group.mFound = false); mAnyGroupFound = false; mAnyGroupFound = false; } } @UiThread private void maybeResetLoginFlags() { private void maybeResetLoginFlags() { if (mAnyGroupFound) { if (mAnyGroupFound) { if (mResetLoginRemainingEventsToProcess <= 0) { if (mResetLoginRemainingEventsToProcess <= 0) { Loading @@ -183,7 +176,6 @@ public class ContentProtectionEventProcessor { } } } } @UiThread private void flush() { private void flush() { mLastFlushTime = Instant.now(); mLastFlushTime = Instant.now(); Loading @@ -192,7 +184,6 @@ public class ContentProtectionEventProcessor { mHandler.post(() -> handlerOnLoginDetected(events)); mHandler.post(() -> handlerOnLoginDetected(events)); } } @UiThread @NonNull @NonNull private ParceledListSlice<ContentCaptureEvent> clearEvents() { private ParceledListSlice<ContentCaptureEvent> clearEvents() { List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray()); List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray()); Loading
core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +143 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes