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

Commit fa4334fc authored by MingWei's avatar MingWei
Browse files

Enable content capture running on background thread

1. Make sure works inside MainContentCaptureSession are delegated to the
   assigned thread. So even when the works are not done in UiThread, the
   synchronization is still guaranteed.
2. Remove requirement of ContentProtectionEventProcessor running on
   UiThread. MainContentCaptureSession is the only caller to the
   processor. And it is guaranteed that only the assigned thread will be
   used. So no need to restrict it on UiThread.
3. Move notifyContentCaptureEvents work away from ViewRootImpl and let
   MainContentCaptureSession decide how to process this.

Test: CtsContentCaptureServiceTestCases, MainContentCaptureSessionTest.
BUG: 309411951
Change-Id: I13ce958da2280171fb2eb85b3321e3c7a7c5d0aa
parent 204f79c0
Loading
Loading
Loading
Loading
+11 −49
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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;
@@ -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");
@@ -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() {
+20 −10
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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;


@@ -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;
@@ -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
@@ -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,
+135 −39
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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;
@@ -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));
        }
        }
    }
    }


@@ -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) {
@@ -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);
    }
    }


    /**
    /**
@@ -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 = () -> {
@@ -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
@@ -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) {
@@ -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);
@@ -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;
@@ -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.");
@@ -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) {
@@ -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 "
@@ -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));
@@ -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)));
    }
    }


@@ -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);
@@ -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
@@ -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();
        }
    }
}
}
+0 −9
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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);
@@ -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();
@@ -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);
@@ -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))) {
@@ -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) {
@@ -183,7 +176,6 @@ public class ContentProtectionEventProcessor {
        }
        }
    }
    }


    @UiThread
    private void flush() {
    private void flush() {
        mLastFlushTime = Instant.now();
        mLastFlushTime = Instant.now();


@@ -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());
+143 −4

File changed.

Preview size limit exceeded, changes collapsed.