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

Commit 544b39cb authored by Felipe Leme's avatar Felipe Leme
Browse files

Changed Content Catpure workflow so it notifies when multiple changes are made.

Prior to this change, it sent a pair TYPE_INITIAL_VIEW_TREE_APPEARING and
TYPE_INITIAL_VIEW_TREE_APPEARED after the initial layout, then it would send invididual events for
the views appeared / disappeared.

This change improves the workflow by also sending this pair of events after each change, which lets
the service know that a bunch of changes were made at the same layout pass.

Test: atest CtsContentCaptureServiceTestCases # which was updated to listen to the new events
Test: m update-api

Bug: 125395044

Change-Id: Ied9def9c95dd0f7711f59bccb2cc89a766fdc36b
parent d58c1eaa
Loading
Loading
Loading
Loading
+22 −30
Original line number Original line Diff line number Diff line
@@ -9408,20 +9408,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            }
            }
            setNotifiedContentCaptureAppeared();
            setNotifiedContentCaptureAppeared();
            // TODO(b/125395044): instead of post, we should queue it on AttachInfo and then
            if (ai != null) {
            // dispatch on RootImpl, as we're doing with the removed ones (in that case, we should
                ai.delayNotifyContentCaptureEvent(session, this, appeared);
            // merge the delayNotifyContentCaptureDisappeared() into a more generic method that
            } else {
            // takes a session and a command, where the command is either view added or removed
                if (DEBUG_CONTENT_CAPTURE) {
                    Log.w(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on appeared for " + this);
            // The code below doesn't take much for a unique view, but it's called for all views
                }
            // the first time the view hiearchy is laid off, which could acccumulative delay the
            }
            // initial layout. Hence, we're postponing it to a later stage - it might still cost a
            // lost frame (or more), but that jank cost would only happen after the 1st layout.
            Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
                final ViewStructure structure = session.newViewStructure(this);
                onProvideContentCaptureStructure(structure, /* flags= */ 0);
                session.notifyViewAppeared(structure);
            }, /* token= */ null);
        } else {
        } else {
            if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
            if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
@@ -9440,13 +9433,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
            if (ai != null) {
            if (ai != null) {
                ai.delayNotifyContentCaptureDisappeared(session, getAutofillId());
                ai.delayNotifyContentCaptureEvent(session, this, appeared);
            } else {
            } else {
                if (DEBUG_CONTENT_CAPTURE) {
                if (DEBUG_CONTENT_CAPTURE) {
                    Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on gone for " + this);
                    Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on disappeared for " + this);
                }
                }
                Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT,
                        () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null);
            }
            }
        }
        }
    }
    }
@@ -28364,11 +28355,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        boolean mReadyForContentCaptureUpdates;
        boolean mReadyForContentCaptureUpdates;
        /**
        /**
         * Map of ids (per session) that need to be notified after as gone the view hierchy is
         * Map(keyed by session) of content capture events that need to be notified after the view
         * traversed.
         * hierarchy is traversed: value is either the view itself for appearead events, or its
         * autofill id for disappeared.
         */
         */
        // TODO(b/121197119): use SparseArray once session id becomes integer
        // TODO(b/121197119): use SparseArray once session id becomes integer
        ArrayMap<String, ArrayList<AutofillId>> mContentCaptureRemovedIds;
        ArrayMap<String, ArrayList<Object>> mContentCaptureEvents;
        /**
        /**
         * Cached reference to the {@link ContentCaptureManager}.
         * Cached reference to the {@link ContentCaptureManager}.
@@ -28394,20 +28386,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            mTreeObserver = new ViewTreeObserver(context);
            mTreeObserver = new ViewTreeObserver(context);
        }
        }
        private void delayNotifyContentCaptureDisappeared(@NonNull ContentCaptureSession session,
        private void delayNotifyContentCaptureEvent(@NonNull ContentCaptureSession session,
                @NonNull AutofillId id) {
                @NonNull View view, boolean appeared) {
            if (mContentCaptureRemovedIds == null) {
            if (mContentCaptureEvents == null) {
                // Most of the time there will be just one session, so intial capacity is 1
                // Most of the time there will be just one session, so intial capacity is 1
                mContentCaptureRemovedIds = new ArrayMap<>(1);
                mContentCaptureEvents = new ArrayMap<>(1);
            }
            }
            String sessionId = session.getId();
            String sessionId = session.getId();
            // TODO: life would be much easier if we provided a MultiMap implementation somwhere...
            // TODO: life would be much easier if we provided a MultiMap implementation somwhere...
            ArrayList<AutofillId> ids = mContentCaptureRemovedIds.get(sessionId);
            ArrayList<Object> events = mContentCaptureEvents.get(sessionId);
            if (ids == null) {
            if (events == null) {
                ids = new ArrayList<>();
                events = new ArrayList<>();
                mContentCaptureRemovedIds.put(sessionId, ids);
                mContentCaptureEvents.put(sessionId, events);
            }
            }
            ids.add(id);
            events.add(appeared ? view : view.getAutofillId());
        }
        }
        @Nullable
        @Nullable
+51 −22
Original line number Original line Diff line number Diff line
@@ -2796,30 +2796,60 @@ public final class ViewRootImpl implements ViewParent,
            }
            }
        }
        }


        // TODO(b/125395044): might need to check for added events too and flush them
        if (mAttachInfo.mContentCaptureEvents != null) {
        if (mAttachInfo.mContentCaptureRemovedIds != null) {
            notifyContentCatpureEvents();
        }

        mIsInTraversal = false;
    }

    private void notifyContentCatpureEvents() {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
        try {
            MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
            MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
                    .getMainContentCaptureSession();
                    .getMainContentCaptureSession();
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureViewsGone");
            if (mainSession == null) {
            try {
                Log.w(mTag, "no MainContentCaptureSession on AttachInfo");
                for (int i = 0; i < mAttachInfo.mContentCaptureRemovedIds.size(); i++) {
                return;
                    String sessionId = mAttachInfo.mContentCaptureRemovedIds
            }
            for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) {
                String sessionId = mAttachInfo.mContentCaptureEvents
                        .keyAt(i);
                        .keyAt(i);
                    ArrayList<AutofillId> ids = mAttachInfo.mContentCaptureRemovedIds
                mainSession.notifyViewHierarchyEvent(sessionId, /* started = */ true);
                ArrayList<Object> events = mAttachInfo.mContentCaptureEvents
                        .valueAt(i);
                        .valueAt(i);
                    mainSession.notifyViewsDisappeared(sessionId, ids);
                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;
                        }
                        String actualId = session.getId().toString();
                        if (!actualId.equals(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 {
                        Log.w(mTag, "invalid content capture event: " + event);
                    }
                }
                mainSession.notifyViewHierarchyEvent(sessionId, /* started = */ false);
            }
            }
                mAttachInfo.mContentCaptureRemovedIds = null;
            mAttachInfo.mContentCaptureEvents = null;
                mAttachInfo.mContentCaptureManager
                        .flush(ContentCaptureSession.FLUSH_REASON_POST_VIEW_ROOT_TRAVERSAL);
        } finally {
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        }
    }
    }


        mIsInTraversal = false;
    }

    private void notifySurfaceDestroyed() {
    private void notifySurfaceDestroyed() {
        mSurfaceHolder.ungetCallbacks();
        mSurfaceHolder.ungetCallbacks();
        SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
        SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
@@ -2950,10 +2980,9 @@ public final class ViewRootImpl implements ViewParent,
            }
            }
        }
        }
        mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
        mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
        // TODO(b/125395044): right now the list of events is always empty on
        // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
        // when hasWindowFocus is false, as the removed events are effectively flushed on
        // is lost, so we don't need to to force a flush - there might be other events such as
        // FLUSH_REASON_POST_VIEW_ROOT_TRAVERSAL. If after the final refactorings that's still the
        // text changes, but these should be flushed independently.
        // case, we should add another reason for FLUSH_REASON_VIEW_ROOT_EXITED
        if (hasWindowFocus) {
        if (hasWindowFocus) {
            performContentCaptureFlushIfNecessary(
            performContentCaptureFlushIfNecessary(
                    ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED);
                    ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED);
+1 −1
Original line number Original line Diff line number Diff line
@@ -85,7 +85,7 @@ final class ChildContentCaptureSession extends ContentCaptureSession {


    @Override
    @Override
    public void internalNotifyViewHierarchyEvent(boolean started) {
    public void internalNotifyViewHierarchyEvent(boolean started) {
        getMainCaptureSession().notifyInitialViewHierarchyEvent(mId, started);
        getMainCaptureSession().notifyViewHierarchyEvent(mId, started);
    }
    }


    @Override
    @Override
+8 −6
Original line number Original line Diff line number Diff line
@@ -72,23 +72,25 @@ public final class ContentCaptureEvent implements Parcelable {
    public static final int TYPE_VIEW_TEXT_CHANGED = 3;
    public static final int TYPE_VIEW_TEXT_CHANGED = 3;


    /**
    /**
     * Called before events (such as {@link #TYPE_VIEW_APPEARED}) representing the initial view
     * Called before events (such as {@link #TYPE_VIEW_APPEARED} and/or
     * hierarchy are sent.
     * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy are sent.
     *
     *
     * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
     * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
     * if the initial view hierarchy doesn't initially have any view that's important for content
     * if the initial view hierarchy doesn't initially have any view that's important for content
     * capture.
     * capture.
     */
     */
    // TODO(b/125395044): change to TYPE_VIEW_TREE_APPEARING
    public static final int TYPE_INITIAL_VIEW_TREE_APPEARING = 4;
    public static final int TYPE_INITIAL_VIEW_TREE_APPEARING = 4;


    /**
    /**
     * Called after events (such as {@link #TYPE_VIEW_APPEARED}) representing the initial view
     * Called after events (such as {@link #TYPE_VIEW_APPEARED} and/or
     * hierarchy are sent.
     * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy were sent.
     *
     *
     * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
     * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
     * if the initial view hierarchy doesn't initially have any view that's important for content
     * if the initial view hierarchy doesn't initially have any view that's important for content
     * capture.
     * capture.
     */
     */
    // TODO(b/125395044): change to TYPE_VIEW_TREE_APPEARED
    public static final int TYPE_INITIAL_VIEW_TREE_APPEARED = 5;
    public static final int TYPE_INITIAL_VIEW_TREE_APPEARED = 5;


    /**
    /**
@@ -418,9 +420,9 @@ public final class ContentCaptureEvent implements Parcelable {
            case TYPE_VIEW_TEXT_CHANGED:
            case TYPE_VIEW_TEXT_CHANGED:
                return "VIEW_TEXT_CHANGED";
                return "VIEW_TEXT_CHANGED";
            case TYPE_INITIAL_VIEW_TREE_APPEARING:
            case TYPE_INITIAL_VIEW_TREE_APPEARING:
                return "INITIAL_VIEW_HIERARCHY_STARTED";
                return "VIEW_TREE_APPEARING";
            case TYPE_INITIAL_VIEW_TREE_APPEARED:
            case TYPE_INITIAL_VIEW_TREE_APPEARED:
                return "INITIAL_VIEW_HIERARCHY_FINISHED";
                return "VIEW_TREE_APPEARED";
            case TYPE_CONTEXT_UPDATED:
            case TYPE_CONTEXT_UPDATED:
                return "CONTEXT_UPDATED";
                return "CONTEXT_UPDATED";
            default:
            default:
+0 −5
Original line number Original line Diff line number Diff line
@@ -134,8 +134,6 @@ public abstract class ContentCaptureSession implements AutoCloseable {
    /** @hide */
    /** @hide */
    public static final int FLUSH_REASON_VIEW_ROOT_ENTERED = 2;
    public static final int FLUSH_REASON_VIEW_ROOT_ENTERED = 2;
    /** @hide */
    /** @hide */
    public static final int FLUSH_REASON_POST_VIEW_ROOT_TRAVERSAL = 3;
    /** @hide */
    public static final int FLUSH_REASON_SESSION_STARTED = 4;
    public static final int FLUSH_REASON_SESSION_STARTED = 4;
    /** @hide */
    /** @hide */
    public static final int FLUSH_REASON_SESSION_FINISHED = 5;
    public static final int FLUSH_REASON_SESSION_FINISHED = 5;
@@ -149,7 +147,6 @@ public abstract class ContentCaptureSession implements AutoCloseable {
            FLUSH_REASON_SESSION_FINISHED,
            FLUSH_REASON_SESSION_FINISHED,
            FLUSH_REASON_IDLE_TIMEOUT,
            FLUSH_REASON_IDLE_TIMEOUT,
            FLUSH_REASON_VIEW_ROOT_ENTERED,
            FLUSH_REASON_VIEW_ROOT_ENTERED,
            FLUSH_REASON_POST_VIEW_ROOT_TRAVERSAL
    })
    })
    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface FlushReason{}
    public @interface FlushReason{}
@@ -512,8 +509,6 @@ public abstract class ContentCaptureSession implements AutoCloseable {
                return "IDLE";
                return "IDLE";
            case FLUSH_REASON_VIEW_ROOT_ENTERED:
            case FLUSH_REASON_VIEW_ROOT_ENTERED:
                return "ENTERED";
                return "ENTERED";
            case FLUSH_REASON_POST_VIEW_ROOT_TRAVERSAL:
                return "TRAVERSAL";
            default:
            default:
                return "UNKOWN-" + reason;
                return "UNKOWN-" + reason;
        }
        }
Loading