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

Commit 0a64976a authored by Felipe Leme's avatar Felipe Leme Committed by Android (Google) Code Review
Browse files

Merge "Refactored how initial content capture events are sent."

parents 2267d76e 01297698
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -50669,7 +50669,7 @@ package android.view {
    method public void setClickable(boolean);
    method public void setClipBounds(android.graphics.Rect);
    method public void setClipToOutline(boolean);
    method public void setContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureSession);
    method public void setContentCaptureSession(@Nullable android.view.contentcapture.ContentCaptureSession);
    method public void setContentDescription(CharSequence);
    method public void setContextClickable(boolean);
    method public void setDefaultFocusHighlightEnabled(boolean);
+2 −0
Original line number Diff line number Diff line
@@ -9299,6 +9299,8 @@ package android.view.contentcapture {
    method @Nullable public android.view.contentcapture.ViewNode getViewNode();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR;
    field public static final int TYPE_INITIAL_VIEW_TREE_APPEARED = 5; // 0x5
    field public static final int TYPE_INITIAL_VIEW_TREE_APPEARING = 4; // 0x4
    field public static final int TYPE_VIEW_APPEARED = 1; // 0x1
    field public static final int TYPE_VIEW_DISAPPEARED = 2; // 0x2
    field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3
+2 −1
Original line number Diff line number Diff line
@@ -7155,7 +7155,8 @@ public class Activity extends ContextThemeWrapper
        mInstrumentation.onEnterAnimationComplete();
        onEnterAnimationComplete();
        if (getWindow() != null && getWindow().getDecorView() != null) {
            getWindow().getDecorView().getViewTreeObserver().dispatchOnEnterAnimationComplete();
            View decorView = getWindow().getDecorView();
            decorView.getViewTreeObserver().dispatchOnEnterAnimationComplete();
        }
    }

+195 −44
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import android.os.Trace;
import android.sysprop.DisplayProperties;
import android.text.InputType;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.LayoutDirection;
@@ -126,7 +127,6 @@ import android.widget.FrameLayout;
import android.widget.ScrollBarDrawable;
import com.android.internal.R;
import com.android.internal.util.Preconditions;
import com.android.internal.view.TooltipPopup;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.ScrollBarUtils;
@@ -813,6 +813,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    private static final String CONTENT_CAPTURE_LOG_TAG = "View.ContentCapture";
    private static final boolean DEBUG_CONTENT_CAPTURE = false;
    /**
     * When set to true, this view will save its attribute data.
     *
@@ -3396,6 +3398,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     *                             1111 PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK
     *                            1     PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED
     *                           1      PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED
     *                          1       PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED
     *                         1        PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE
     *                         11       PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK
     * |-------|-------|-------|-------|
     */
@@ -3422,6 +3427,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    private static final int PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED = 0x10;
    private static final int PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED = 0x20;
    /*
     * Flags used to cache the value returned by isImportantForContentCapture while the view
     * hierarchy is being traversed.
     */
    private static final int PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED = 0x40;
    private static final int PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE = 0x80;
    private static final int PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK =
            PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED
            | PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE;
    /* End of masks for mPrivateFlags4 */
    /** @hide */
@@ -5046,11 +5062,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * view (through {@link #setContentCaptureSession(ContentCaptureSession)}.
     */
    @Nullable
    private WeakReference<ContentCaptureSession> mContentCaptureSession;
    private ContentCaptureSession mContentCaptureSession;
    @LayoutRes
    private int mSourceLayoutId = ID_NULL;
    /**
     * Cached reference to the {@link ContentCaptureSession}, is reset on {@link #invalidate()}.
     */
    private ContentCaptureSession mCachedContentCaptureSession;
    /**
     * Simple constructor to use when creating a view from code.
     *
@@ -8237,15 +8258,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * </ul>
     */
    public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                    "onProvideContentCaptureStructure() for " + getClass().getSimpleName());
        }
        try {
        onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    /** @hide */
@@ -8956,6 +8969,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * @see #IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
     */
    public final boolean isImportantForContentCapture() {
        boolean isImportant;
        if ((mPrivateFlags4 & PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED) != 0) {
            isImportant = (mPrivateFlags4 & PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE) != 0;
            return isImportant;
        }
        isImportant = calculateIsImportantForContentCapture();
        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE;
        if (isImportant) {
            mPrivateFlags4 |= PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE;
        }
        mPrivateFlags4 |= PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED;
        return isImportant;
    }
    /**
     * Calculates whether the flag is important for content capture so it can be used by
     * {@link #isImportantForContentCapture()} while the tree is traversed.
     */
    private boolean calculateIsImportantForContentCapture() {
        // Check parent mode to ensure we're important
        ViewParent parent = mParent;
        while (parent instanceof View) {
@@ -8996,9 +9030,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
        // View group is important if at least one children also is
        //TODO(b/111276913): decide if we really need to send the relevant parents or just the
        // leaves (with absolute coordinates). If it's the latter, then we need to update this
        // javadoc and ViewGroup's implementation.
        if (this instanceof ViewGroup) {
            final ViewGroup group = (ViewGroup) this;
            for (int i = 0; i < group.getChildCount(); i++) {
@@ -9035,6 +9066,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * </ol>
     */
    private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) {
        AttachInfo ai = mAttachInfo;
        // Skip it while the view is being laided out for the first time
        if (ai != null && !ai.mReadyForContentCaptureUpdates) return;
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                    "notifyContentCapture(" + appeared + ") for " + getClass().getSimpleName());
@@ -9047,24 +9082,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    }
    private void notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(boolean appeared) {
        AttachInfo ai = mAttachInfo;
        // First check if context has client, so it saves a service lookup when it doesn't
        if (!mContext.isContentCaptureSupported()) return;
        // Then check if it's enabled in the context...
        final ContentCaptureManager ccm = mContext.getSystemService(ContentCaptureManager.class);
        final ContentCaptureManager ccm = ai != null ? ai.getContentCaptureManager(mContext)
                : mContext.getSystemService(ContentCaptureManager.class);
        if (ccm == null || !ccm.isContentCaptureEnabled()) return;
        // ... and finally at the view level
        // NOTE: isImportantForContentCapture() is more expensive than cm.isContentCaptureEnabled()
        if (!isImportantForContentCapture()) return;
        final ContentCaptureSession session = getContentCaptureSession(ccm);
        ContentCaptureSession session = getContentCaptureSession();
        if (session == null) return;
        if (appeared) {
            if (!isLaidOut() || getVisibility() != VISIBLE
                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) {
                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
                if (DEBUG_CONTENT_CAPTURE) {
                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid="
                            + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
                            + ", visible=" + (getVisibility() == VISIBLE)
@@ -9075,8 +9113,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                }
                return;
            }
            mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
            setNotifiedContentCaptureAppeared();
            // TODO(b/123307965): instead of post, we should queue it on AttachInfo and then
            // dispatch on RootImpl, as we're doing with the removed ones (in that case, we should
            // merge the delayNotifyContentCaptureDisappeared() into a more generic method that
            // takes a session and a command, where the command is either view added or removed
            // 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
@@ -9090,7 +9132,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        } else {
            if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
                if (DEBUG_CONTENT_CAPTURE) {
                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid="
                            + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
                            + ", visible=" + (getVisibility() == VISIBLE)
@@ -9103,10 +9145,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            }
            mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
            if (ai != null) {
                ai.delayNotifyContentCaptureDisappeared(session, getAutofillId());
            } else {
                if (DEBUG_CONTENT_CAPTURE) {
                    Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on gone for " + this);
                }
                Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT,
                        () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null);
            }
        }
    }
    private void setNotifiedContentCaptureAppeared() {
        mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
        mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
    }
    /**
     * Sets the (optional) {@link ContentCaptureSession} associated with this view.
@@ -9131,9 +9186,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * {@link ContentCaptureSession#createContentCaptureSession(
     *        android.view.contentcapture.ContentCaptureContext)}.
     */
    public void setContentCaptureSession(@NonNull ContentCaptureSession contentCaptureSession) {
        mContentCaptureSession = new WeakReference<>(
                Preconditions.checkNotNull(contentCaptureSession));
    public void setContentCaptureSession(@Nullable ContentCaptureSession contentCaptureSession) {
        mContentCaptureSession = contentCaptureSession;
    }
    /**
@@ -9145,8 +9199,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    @Nullable
    public final ContentCaptureSession getContentCaptureSession() {
        if (mCachedContentCaptureSession != null) {
            return mCachedContentCaptureSession;
        }
        mCachedContentCaptureSession = getAndCacheContentCaptureSession();
        return mCachedContentCaptureSession;
    }
    @Nullable
    private ContentCaptureSession getAndCacheContentCaptureSession() {
        // First try the session explicitly set by setContentCaptureSession()
        if (mContentCaptureSession != null) return mContentCaptureSession.get();
        if (mContentCaptureSession != null) return mContentCaptureSession;
        // Then the session explicitly set in an ancestor
        ContentCaptureSession session = null;
@@ -9163,21 +9227,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return session;
    }
    /**
     * Optimized version of {@link #getContentCaptureSession()} that avoids a service lookup.
     */
    @Nullable
    private ContentCaptureSession getContentCaptureSession(@NonNull ContentCaptureManager ccm) {
        if (mContentCaptureSession != null) return mContentCaptureSession.get();
        ContentCaptureSession session = null;
        if (mParent instanceof View) {
            session = ((View) mParent).getContentCaptureSession(ccm);
        }
        return session != null ? session : ccm.getMainContentCaptureSession();
    }
    @Nullable
    private AutofillManager getAutofillManager() {
        return mContext.getSystemService(AutofillManager.class);
@@ -9349,6 +9398,62 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
    }
    /**
     * Dispatches the initial Content Capture events for a view structure.
     *
     * @hide
     */
    public void dispatchInitialProvideContentCaptureStructure(@NonNull ContentCaptureManager ccm) {
        AttachInfo ai = mAttachInfo;
        if (ai == null) {
            Log.w(CONTENT_CAPTURE_LOG_TAG,
                    "dispatchProvideContentCaptureStructure(): no AttachInfo for " + this);
            return;
        }
        // We must set it before checkign if the view itself is important, because it might
        // initially not be (for example, if it's empty), although that might change later (for
        // example, if important views are added)
        ai.mReadyForContentCaptureUpdates = true;
        if (!isImportantForContentCapture()) {
            if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.DEBUG)) {
                Log.d(CONTENT_CAPTURE_LOG_TAG,
                        "dispatchProvideContentCaptureStructure(): decorView is not important");
            }
            return;
        }
        ai.mContentCaptureManager = ccm;
        ContentCaptureSession session = getContentCaptureSession();
        if (session == null) {
            if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.DEBUG)) {
                Log.d(CONTENT_CAPTURE_LOG_TAG,
                        "dispatchProvideContentCaptureStructure(): no session for " + this);
            }
            return;
        }
        session.internalNotifyViewHierarchyEvent(/* started= */ true);
        try {
            dispatchProvideContentCaptureStructure();
        } finally {
            session.internalNotifyViewHierarchyEvent(/* started= */ false);
        }
    }
    /** @hide */
    void dispatchProvideContentCaptureStructure() {
        ContentCaptureSession session = getContentCaptureSession();
        if (session != null) {
            ViewStructure structure = session.newViewStructure(this);
            onProvideContentCaptureStructure(structure, /* flags= */ 0);
            setNotifiedContentCaptureAppeared();
            session.notifyViewAppeared(structure);
        }
    }
    /**
     * @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
     *
@@ -17461,6 +17566,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            return;
        }
        // Reset content capture caches
        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
        mCachedContentCaptureSession = null;
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
@@ -27966,6 +28075,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         */
        View mTooltipHost;
        /**
         * The initial structure has been reported so the view is ready to report updates.
         */
        boolean mReadyForContentCaptureUpdates;
        /**
         * Map of ids (per session) that need to be notified after as gone the view hierchy is
         * traversed.
         */
        // TODO(b/121197119): use SparseArray once session id becomes integer
        ArrayMap<String, ArrayList<AutofillId>> mContentCaptureRemovedIds;
        /**
         * Cached reference to the {@link ContentCaptureManager}.
         */
        ContentCaptureManager mContentCaptureManager;
        /**
         * Creates a new set of attachment information with the specified
         * events handler and thread.
@@ -27984,6 +28110,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            mRootCallbacks = effectPlayer;
            mTreeObserver = new ViewTreeObserver(context);
        }
        private void delayNotifyContentCaptureDisappeared(@NonNull ContentCaptureSession session,
                @NonNull AutofillId id) {
            if (mContentCaptureRemovedIds == null) {
                // Most of the time there will be just one session, so intial capacity is 1
                mContentCaptureRemovedIds = new ArrayMap<>(1);
            }
            String sessionId = session.getId();
            // TODO: life would be much easier if we provided a MultiMap implementation somwhere...
            ArrayList<AutofillId> ids = mContentCaptureRemovedIds.get(sessionId);
            if (ids == null) {
                ids = new ArrayList<>();
                mContentCaptureRemovedIds.put(sessionId, ids);
            }
            ids.add(id);
        }
        @Nullable
        private ContentCaptureManager getContentCaptureManager(@NonNull Context context) {
            if (mContentCaptureManager != null) {
                return mContentCaptureManager;
            }
            mContentCaptureManager = context.getSystemService(ContentCaptureManager.class);
            return mContentCaptureManager;
        }
    }
    /**
+54 −8
Original line number Diff line number Diff line
@@ -3603,7 +3603,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            return;
        }

        final ChildListForAutoFill children = getChildrenForAutofill(flags);
        final ChildListForAutoFillOrContentCapture children = getChildrenForAutofill(flags);
        final int childrenCount = children.size();
        structure.setChildCount(childrenCount);
        for (int i = 0; i < childrenCount; i++) {
@@ -3614,13 +3614,31 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        children.recycle();
    }

    /** @hide */
    @Override
    public void dispatchProvideContentCaptureStructure() {
        super.dispatchProvideContentCaptureStructure();

        if (!isLaidOut()) return;

        final ChildListForAutoFillOrContentCapture children = getChildrenForContentCapture();
        final int childrenCount = children.size();
        for (int i = 0; i < childrenCount; i++) {
            final View child = children.get(i);
            child.dispatchProvideContentCaptureStructure();
        }
        children.recycle();
    }

    /**
     * Gets the children for autofill. Children for autofill are the first
     * level descendants that are important for autofill. The returned
     * child list object is pooled and the caller must recycle it once done.
     * @hide */
    private @NonNull ChildListForAutoFill getChildrenForAutofill(@AutofillFlags int flags) {
        final ChildListForAutoFill children = ChildListForAutoFill.obtain();
    private @NonNull ChildListForAutoFillOrContentCapture getChildrenForAutofill(
            @AutofillFlags int flags) {
        final ChildListForAutoFillOrContentCapture children = ChildListForAutoFillOrContentCapture
                .obtain();
        populateChildrenForAutofill(children, flags);
        return children;
    }
@@ -3647,6 +3665,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        }
    }

    private @NonNull ChildListForAutoFillOrContentCapture getChildrenForContentCapture() {
        final ChildListForAutoFillOrContentCapture children = ChildListForAutoFillOrContentCapture
                .obtain();
        populateChildrenForContentCapture(children);
        return children;
    }

    /** @hide */
    private void populateChildrenForContentCapture(ArrayList<View> list) {
        final int childrenCount = mChildrenCount;
        if (childrenCount <= 0) {
            return;
        }
        final ArrayList<View> preorderedList = buildOrderedChildList();
        final boolean customOrder = preorderedList == null
                && isChildrenDrawingOrderEnabled();
        for (int i = 0; i < childrenCount; i++) {
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = (preorderedList == null)
                    ? mChildren[childIndex] : preorderedList.get(childIndex);
            if (child.isImportantForContentCapture()) {
                list.add(child);
            } else if (child instanceof ViewGroup) {
                ((ViewGroup) child).populateChildrenForContentCapture(list);
            }
        }
    }

    private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
            int childIndex) {
        final View child;
@@ -8544,16 +8590,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    /**
     * Pooled class that to hold the children for autifill.
     */
    static class ChildListForAutoFill extends ArrayList<View> {
    private static class ChildListForAutoFillOrContentCapture extends ArrayList<View> {
        private static final int MAX_POOL_SIZE = 32;

        private static final Pools.SimplePool<ChildListForAutoFill> sPool =
        private static final Pools.SimplePool<ChildListForAutoFillOrContentCapture> sPool =
                new Pools.SimplePool<>(MAX_POOL_SIZE);

        public static ChildListForAutoFill obtain() {
            ChildListForAutoFill list = sPool.acquire();
        public static ChildListForAutoFillOrContentCapture obtain() {
            ChildListForAutoFillOrContentCapture list = sPool.acquire();
            if (list == null) {
                list = new ChildListForAutoFill();
                list = new ChildListForAutoFillOrContentCapture();
            }
            return list;
        }
Loading