Loading api/current.txt +1 −1 Original line number Diff line number Diff line Loading @@ -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); api/system-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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 core/java/android/app/Activity.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); } } Loading core/java/android/view/View.java +195 −44 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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 * |-------|-------|-------|-------| */ Loading @@ -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 */ Loading Loading @@ -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. * Loading Loading @@ -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 */ Loading Loading @@ -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) { Loading Loading @@ -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++) { Loading Loading @@ -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()); Loading @@ -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) Loading @@ -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 Loading @@ -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) Loading @@ -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. Loading @@ -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; } /** Loading @@ -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; Loading @@ -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); Loading Loading @@ -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) * Loading Loading @@ -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 Loading Loading @@ -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. Loading @@ -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; } } /** core/java/android/view/ViewGroup.java +54 −8 Original line number Diff line number Diff line Loading @@ -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++) { Loading @@ -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; } Loading @@ -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; Loading Loading @@ -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 Loading
api/current.txt +1 −1 Original line number Diff line number Diff line Loading @@ -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);
api/system-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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
core/java/android/app/Activity.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); } } Loading
core/java/android/view/View.java +195 −44 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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 * |-------|-------|-------|-------| */ Loading @@ -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 */ Loading Loading @@ -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. * Loading Loading @@ -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 */ Loading Loading @@ -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) { Loading Loading @@ -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++) { Loading Loading @@ -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()); Loading @@ -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) Loading @@ -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 Loading @@ -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) Loading @@ -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. Loading @@ -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; } /** Loading @@ -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; Loading @@ -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); Loading Loading @@ -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) * Loading Loading @@ -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 Loading Loading @@ -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. Loading @@ -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; } } /**
core/java/android/view/ViewGroup.java +54 −8 Original line number Diff line number Diff line Loading @@ -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++) { Loading @@ -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; } Loading @@ -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; Loading Loading @@ -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