DO NOT MERGE - Re-add ContentCapture support from standard SDK toolkit.

am: 911cb1fa76

Change-Id: I15c8da47beb6b4dd64b627aebb0288fcb359c3e5
This commit is contained in:
Felipe Leme
2019-05-22 16:00:20 -07:00
committed by android-build-merger
7 changed files with 822 additions and 14 deletions

View File

@@ -3171,10 +3171,13 @@ package android.view {
} }
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
method @android.view.ViewDebug.ExportedProperty(mapping={@android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_CONTENT_CAPTURE_AUTO, to="auto"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_CONTENT_CAPTURE_YES, to="yes"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_CONTENT_CAPTURE_NO, to="no"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS, to="yesExcludeDescendants"), @android.view.ViewDebug.IntToString(from=android.view.View.IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS, to="noExcludeDescendants")}) public int getImportantForContentCapture();
method public android.view.View getTooltipView(); method public android.view.View getTooltipView();
method public boolean isAutofilled(); method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled(); method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
method public final boolean isImportantForContentCapture();
method public void onProvideContentCaptureStructure(@NonNull android.view.ViewStructure, int);
method protected void resetResolvedDrawables(); method protected void resetResolvedDrawables();
method public void resetResolvedLayoutDirection(); method public void resetResolvedLayoutDirection();
method public void resetResolvedPadding(); method public void resetResolvedPadding();
@@ -3185,7 +3188,13 @@ package android.view {
method public boolean restoreFocusNotInCluster(); method public boolean restoreFocusNotInCluster();
method public void setAutofilled(boolean); method public void setAutofilled(boolean);
method public final void setFocusedInCluster(); method public final void setFocusedInCluster();
method public void setImportantForContentCapture(int);
method public void setIsRootNamespace(boolean); method public void setIsRootNamespace(boolean);
field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_AUTO = 0; // 0x0
field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO = 2; // 0x2
field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS = 8; // 0x8
field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES = 1; // 0x1
field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS = 4; // 0x4
} }
public class ViewConfiguration { public class ViewConfiguration {

View File

@@ -1377,6 +1377,74 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/ */
public static final int AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x1; public static final int AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x1;
/** @hide */
@IntDef(prefix = { "IMPORTANT_FOR_CONTENT_CAPTURE_" }, value = {
IMPORTANT_FOR_CONTENT_CAPTURE_AUTO,
IMPORTANT_FOR_CONTENT_CAPTURE_YES,
IMPORTANT_FOR_CONTENT_CAPTURE_NO,
IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS,
IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ContentCaptureImportance {}
/**
* Automatically determine whether a view is important for content capture.
*
* @see #isImportantForContentCapture()
* @see #setImportantForContentCapture(int)
*
* @hide
*/
@TestApi
public static final int IMPORTANT_FOR_CONTENT_CAPTURE_AUTO = 0x0;
/**
* The view is important for content capture, and its children (if any) will be traversed.
*
* @see #isImportantForContentCapture()
* @see #setImportantForContentCapture(int)
*
* @hide
*/
@TestApi
public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES = 0x1;
/**
* The view is not important for content capture, but its children (if any) will be traversed.
*
* @see #isImportantForContentCapture()
* @see #setImportantForContentCapture(int)
*
* @hide
*/
@TestApi
public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO = 0x2;
/**
* The view is important for content capture, but its children (if any) will not be traversed.
*
* @see #isImportantForContentCapture()
* @see #setImportantForContentCapture(int)
*
* @hide
*/
@TestApi
public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS = 0x4;
/**
* The view is not important for content capture, and its children (if any) will not be
* traversed.
*
* @see #isImportantForContentCapture()
* @see #setImportantForContentCapture(int)
*
* @hide
*/
@TestApi
public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS = 0x8;
/** /**
* This view is enabled. Interpretation varies by subclass. * This view is enabled. Interpretation varies by subclass.
* Use with ENABLED_MASK when calling setFlags. * Use with ENABLED_MASK when calling setFlags.
@@ -3349,6 +3417,55 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/* End of masks for mPrivateFlags3 */ /* End of masks for mPrivateFlags3 */
/*
* Masks for mPrivateFlags4, as generated by dumpFlags():
*
* |-------|-------|-------|-------|
* 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
* |-------|-------|-------|-------|
*/
/**
* Mask for obtaining the bits which specify how to determine
* whether a view is important for autofill.
*
* <p>NOTE: the important for content capture values were the first flags added and are set in
* the rightmost position, so we don't need to shift them
*/
private static final int PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK =
IMPORTANT_FOR_CONTENT_CAPTURE_AUTO | IMPORTANT_FOR_CONTENT_CAPTURE_YES
| IMPORTANT_FOR_CONTENT_CAPTURE_NO
| IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS
| IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS;
/*
* Variables used to control when the IntelligenceManager.notifyNodeAdded()/removed() methods
* should be called.
*
* The idea is to call notifyAppeared() after the view is layout and visible, then call
* notifyDisappeared() when it's gone (without known when it was removed from the parent).
*/
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 */ /** @hide */
protected static final int VIEW_STRUCTURE_FOR_ASSIST = 0; protected static final int VIEW_STRUCTURE_FOR_ASSIST = 0;
/** @hide */ /** @hide */
@@ -3972,6 +4089,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060)
int mPrivateFlags3; int mPrivateFlags3;
private int mPrivateFlags4;
/** /**
* This view's request for the visibility of the status bar. * This view's request for the visibility of the status bar.
* @hide * @hide
@@ -8427,6 +8546,65 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
onProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags); onProvideStructure(structure, VIEW_STRUCTURE_FOR_AUTOFILL, flags);
} }
/**
* Populates a {@link ViewStructure} for content capture.
*
* <p>This method is called after a view is that is eligible for content capture
* (for example, if it {@link #isImportantForAutofill()}, an intelligence service is enabled for
* the user, and the activity rendering the view is enabled for content capture) is laid out and
* is visible.
*
* <p>The populated structure is then passed to the service through
* {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
*
* <p><b>Note: </b>views that manage a virtual structure under this view must populate just
* the node representing this view and return right away, then asynchronously report (not
* necessarily in the UI thread) when the children nodes appear, disappear or have their text
* changed by calling
* {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
* {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
* {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence)}
* respectively. The structure for the a child must be created using
* {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
* {@code autofillId} for a child can be obtained either through
* {@code childStructure.getAutofillId()} or
* {@link ContentCaptureSession#newAutofillId(AutofillId, long)}.
*
* <p>When the virtual view hierarchy represents a web page, you should also:
*
* <ul>
* <li>Call {@link ContentCaptureManager#getContentCaptureConditions()} to infer content
* capture events should be generate for that URL.
* <li>Create a new {@link ContentCaptureSession} child for every HTML element that
* renders a new URL (like an {@code IFRAME}) and use that session to notify events from
* that subtree.
* </ul>
*
* <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
* <ul>
* <li>{@link ViewStructure#setChildCount(int)}
* <li>{@link ViewStructure#addChildCount(int)}
* <li>{@link ViewStructure#getChildCount()}
* <li>{@link ViewStructure#newChild(int)}
* <li>{@link ViewStructure#asyncNewChild(int)}
* <li>{@link ViewStructure#asyncCommit()}
* <li>{@link ViewStructure#setWebDomain(String)}
* <li>{@link ViewStructure#newHtmlInfoBuilder(String)}
* <li>{@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}
* <li>{@link ViewStructure#setDataIsSensitive(boolean)}
* <li>{@link ViewStructure#setAlpha(float)}
* <li>{@link ViewStructure#setElevation(float)}
* <li>{@link ViewStructure#setTransformation(Matrix)}
*
* </ul>
*
* @hide
*/
@TestApi
public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
}
/** @hide */ /** @hide */
protected void onProvideStructure(@NonNull ViewStructure structure, protected void onProvideStructure(@NonNull ViewStructure structure,
@ViewStructureType int viewFor, int flags) { @ViewStructureType int viewFor, int flags) {
@@ -9064,6 +9242,274 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return false; return false;
} }
/**
* Gets the mode for determining whether this view is important for content capture.
*
* <p>See {@link #setImportantForContentCapture(int)} and
* {@link #isImportantForContentCapture()} for more info about this mode.
*
* @return {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO} by default, or value passed to
* {@link #setImportantForContentCapture(int)}.
*
* @attr ref android.R.styleable#View_importantForContentCapture
*
* @hide
*/
@ViewDebug.ExportedProperty(mapping = {
@ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_AUTO, to = "auto"),
@ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_YES, to = "yes"),
@ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_NO, to = "no"),
@ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS,
to = "yesExcludeDescendants"),
@ViewDebug.IntToString(from = IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS,
to = "noExcludeDescendants")})
// @InspectableProperty(enumMapping = {
// @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_AUTO, name = "auto"),
// @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_YES, name = "yes"),
// @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_NO, name = "no"),
// @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS,
// name = "yesExcludeDescendants"),
// @EnumEntry(value = IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS,
// name = "noExcludeDescendants"),
// })
@TestApi
public @ContentCaptureImportance int getImportantForContentCapture() {
// NOTE: the important for content capture values were the first flags added and are set in
// the rightmost position, so we don't need to shift them
return mPrivateFlags4 & PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK;
}
/**
* Sets the mode for determining whether this view is considered important for content capture.
*
* <p>The platform determines the importance for autofill automatically but you
* can use this method to customize the behavior. Typically, a view that provides text should
* be marked as {@link #IMPORTANT_FOR_CONTENT_CAPTURE_YES}.
*
* @param mode {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO},
* {@link #IMPORTANT_FOR_CONTENT_CAPTURE_YES}, {@link #IMPORTANT_FOR_CONTENT_CAPTURE_NO},
* {@link #IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS},
* or {@link #IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS}.
*
* @attr ref android.R.styleable#View_importantForContentCapture
*
* @hide
*/
@TestApi
public void setImportantForContentCapture(@ContentCaptureImportance int mode) {
// Reset first
mPrivateFlags4 &= ~PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK;
// Then set again
// NOTE: the important for content capture values were the first flags added and are set in
// the rightmost position, so we don't need to shift them
mPrivateFlags4 |= (mode & PFLAG4_IMPORTANT_FOR_CONTENT_CAPTURE_MASK);
}
/**
* Hints the Android System whether this view is considered important for content capture, based
* on the value explicitly set by {@link #setImportantForContentCapture(int)} and heuristics
* when it's {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO}.
*
* <p>See {@link ContentCaptureManager} for more info about content capture.
*
* @return whether the view is considered important for content capture.
*
* @see #setImportantForContentCapture(int)
* @see #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO
* @see #IMPORTANT_FOR_CONTENT_CAPTURE_YES
* @see #IMPORTANT_FOR_CONTENT_CAPTURE_NO
* @see #IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS
* @see #IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
*
* @hide
*/
@TestApi
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) {
final int parentImportance = ((View) parent).getImportantForContentCapture();
if (parentImportance == IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
|| parentImportance == IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS) {
if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
Log.v(CONTENT_CAPTURE_LOG_TAG, "View (" + this + ") is not important for "
+ "content capture because parent " + parent + "'s importance is "
+ parentImportance);
}
return false;
}
parent = parent.getParent();
}
final int importance = getImportantForContentCapture();
// First, check the explicit states.
if (importance == IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS
|| importance == IMPORTANT_FOR_CONTENT_CAPTURE_YES) {
return true;
}
if (importance == IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS
|| importance == IMPORTANT_FOR_CONTENT_CAPTURE_NO) {
if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
Log.v(CONTENT_CAPTURE_LOG_TAG, "View (" + this + ") is not important for content "
+ "capture because its importance is " + importance);
}
return false;
}
// Then use some heuristics to handle AUTO.
if (importance != IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
Log.w(CONTENT_CAPTURE_LOG_TAG, "invalid content capture importance (" + importance
+ " on view " + this);
return false;
}
// View group is important if at least one children also is
if (this instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) this;
for (int i = 0; i < group.getChildCount(); i++) {
final View child = group.getChildAt(i);
if (child.isImportantForContentCapture()) {
return true;
}
}
}
// If the app developer explicitly set hints or autofill hintsfor it, it's important.
if (getAutofillHints() != null) {
return true;
}
// Otherwise, assume it's not important...
return false;
}
/**
* Helper used to notify the {@link ContentCaptureManager} when the view is removed or
* added, based on whether it's laid out and visible, and without knowing if the parent removed
* it from the view hierarchy.
*
* <p>This method is called from many places (visibility changed, view laid out, view attached
* or detached to/from window, etc...) and hence must contain the logic to call the manager, as
* described below:
*
* <ol>
* <li>It should only be called when content capture is enabled for the view.
* <li>It must call viewAppeared() before viewDisappeared()
* <li>viewAppearead() can only be called when the view is visible and laidout
* <li>It should not call the same event twice.
* </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());
}
try {
notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(appeared);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
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.getContentCaptureOptions() == null) return;
// Then check if it's enabled in the context...
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;
ContentCaptureSession session = getContentCaptureSession();
if (session == null) return;
if (appeared) {
if (!isLaidOut() || getVisibility() != VISIBLE
|| (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) {
if (DEBUG_CONTENT_CAPTURE) {
Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid="
+ isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+ ", visible=" + (getVisibility() == VISIBLE)
+ ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
& PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+ ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
& PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
}
return;
}
setNotifiedContentCaptureAppeared();
if (ai != null) {
ai.delayNotifyContentCaptureEvent(session, this, appeared);
} else {
if (DEBUG_CONTENT_CAPTURE) {
Log.w(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on appeared for " + this);
}
}
} else {
if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
|| (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
if (DEBUG_CONTENT_CAPTURE) {
Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid="
+ isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+ ", visible=" + (getVisibility() == VISIBLE)
+ ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
& PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+ ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
& PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
}
return;
}
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
if (ai != null) {
ai.delayNotifyContentCaptureEvent(session, this, appeared);
} else {
if (DEBUG_CONTENT_CAPTURE) {
Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on disappeared for " + this);
}
}
}
}
private void setNotifiedContentCaptureAppeared() {
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
}
/** /**
* Sets the (optional) {@link ContentCaptureSession} associated with this view. * Sets the (optional) {@link ContentCaptureSession} associated with this view.
* *
@@ -9316,6 +9762,68 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} }
} }
/**
* Dispatches the initial content capture events for a view structure.
*
* @hide
*/
public void dispatchInitialProvideContentCaptureStructure() {
AttachInfo ai = mAttachInfo;
if (ai == null) {
Log.w(CONTENT_CAPTURE_LOG_TAG,
"dispatchProvideContentCaptureStructure(): no AttachInfo for " + this);
return;
}
ContentCaptureManager ccm = ai.mContentCaptureManager;
if (ccm == null) {
Log.w(CONTENT_CAPTURE_LOG_TAG, "dispatchProvideContentCaptureStructure(): "
+ "no ContentCaptureManager 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.internalNotifyViewTreeEvent(/* started= */ true);
try {
dispatchProvideContentCaptureStructure();
} finally {
session.internalNotifyViewTreeEvent(/* 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) * @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
* *
@@ -13266,6 +13774,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public void dispatchStartTemporaryDetach() { public void dispatchStartTemporaryDetach() {
mPrivateFlags3 |= PFLAG3_TEMPORARY_DETACH; mPrivateFlags3 |= PFLAG3_TEMPORARY_DETACH;
notifyEnterOrExitForAutoFillIfNeeded(false); notifyEnterOrExitForAutoFillIfNeeded(false);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
onStartTemporaryDetach(); onStartTemporaryDetach();
} }
@@ -13292,6 +13801,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
notifyFocusChangeToInputMethodManager(true /* hasFocus */); notifyFocusChangeToInputMethodManager(true /* hasFocus */);
} }
notifyEnterOrExitForAutoFillIfNeeded(true); notifyEnterOrExitForAutoFillIfNeeded(true);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
} }
/** /**
@@ -13883,6 +14393,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
: AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
} }
} }
notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
} }
/** /**
@@ -17578,6 +18090,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} }
// Reset content capture caches // Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
mCachedContentCaptureSession = null; mCachedContentCaptureSession = null;
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
@@ -19587,6 +20100,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
needGlobalAttributesUpdate(false); needGlobalAttributesUpdate(false);
notifyEnterOrExitForAutoFillIfNeeded(true); notifyEnterOrExitForAutoFillIfNeeded(true);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
} }
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -19636,6 +20150,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} }
notifyEnterOrExitForAutoFillIfNeeded(false); notifyEnterOrExitForAutoFillIfNeeded(false);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
} }
/** /**
@@ -21948,6 +22463,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true); notifyEnterOrExitForAutoFillIfNeeded(true);
} }
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
} }
private boolean hasParentWantsFocus() { private boolean hasParentWantsFocus() {
@@ -28158,6 +28675,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/ */
View mTooltipHost; View mTooltipHost;
/**
* The initial structure has been reported so the view is ready to report updates.
*/
boolean mReadyForContentCaptureUpdates;
/**
* Map(keyed by session) of content capture events that need to be notified after the view
* hierarchy is traversed: value is either the view itself for appearead events, or its
* autofill id for disappeared.
*/
SparseArray<ArrayList<Object>> mContentCaptureEvents;
/**
* Cached reference to the {@link ContentCaptureManager}.
*/
ContentCaptureManager mContentCaptureManager;
/** /**
* Creates a new set of attachment information with the specified * Creates a new set of attachment information with the specified
* events handler and thread. * events handler and thread.
@@ -28176,6 +28710,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mRootCallbacks = effectPlayer; mRootCallbacks = effectPlayer;
mTreeObserver = new ViewTreeObserver(context); mTreeObserver = new ViewTreeObserver(context);
} }
private void delayNotifyContentCaptureEvent(@NonNull ContentCaptureSession session,
@NonNull View view, boolean appeared) {
if (mContentCaptureEvents == null) {
// Most of the time there will be just one session, so intial capacity is 1
mContentCaptureEvents = new SparseArray<>(1);
}
int sessionId = session.getId();
// TODO: life would be much easier if we provided a MultiMap implementation somwhere...
ArrayList<Object> events = mContentCaptureEvents.get(sessionId);
if (events == null) {
events = new ArrayList<>();
mContentCaptureEvents.put(sessionId, events);
}
events.add(appeared ? view : view.getAutofillId());
}
@Nullable
ContentCaptureManager getContentCaptureManager(@NonNull Context context) {
if (mContentCaptureManager != null) {
return mContentCaptureManager;
}
mContentCaptureManager = context.getSystemService(ContentCaptureManager.class);
return mContentCaptureManager;
}
} }
/** /**

View File

@@ -3606,7 +3606,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return; return;
} }
final ChildListForAutofill children = getChildrenForAutofill(flags); final ChildListForAutoFillOrContentCapture children = getChildrenForAutofill(flags);
final int childrenCount = children.size(); final int childrenCount = children.size();
structure.setChildCount(childrenCount); structure.setChildCount(childrenCount);
for (int i = 0; i < childrenCount; i++) { for (int i = 0; i < childrenCount; i++) {
@@ -3617,14 +3617,30 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
children.recycle(); 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 * Gets the children for autofill. Children for autofill are the first
* level descendants that are important for autofill. The returned * level descendants that are important for autofill. The returned
* child list object is pooled and the caller must recycle it once done. * child list object is pooled and the caller must recycle it once done.
* @hide */ * @hide */
private @NonNull ChildListForAutofill getChildrenForAutofill( private @NonNull ChildListForAutoFillOrContentCapture getChildrenForAutofill(
@AutofillFlags int flags) { @AutofillFlags int flags) {
final ChildListForAutofill children = ChildListForAutofill final ChildListForAutoFillOrContentCapture children = ChildListForAutoFillOrContentCapture
.obtain(); .obtain();
populateChildrenForAutofill(children, flags); populateChildrenForAutofill(children, flags);
return children; return children;
@@ -3652,6 +3668,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, private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
int childIndex) { int childIndex) {
final View child; final View child;
@@ -8634,16 +8678,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/** /**
* Pooled class that to hold the children for autifill. * Pooled class that to hold the children for autifill.
*/ */
private static class ChildListForAutofill extends ArrayList<View> { private static class ChildListForAutoFillOrContentCapture extends ArrayList<View> {
private static final int MAX_POOL_SIZE = 32; 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); new Pools.SimplePool<>(MAX_POOL_SIZE);
public static ChildListForAutofill obtain() { public static ChildListForAutoFillOrContentCapture obtain() {
ChildListForAutofill list = sPool.acquire(); ChildListForAutoFillOrContentCapture list = sPool.acquire();
if (list == null) { if (list == null) {
list = new ChildListForAutofill(); list = new ChildListForAutoFillOrContentCapture();
} }
return list; return list;
} }

View File

@@ -105,7 +105,11 @@ 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.ContentCaptureSession;
import android.view.contentcapture.MainContentCaptureSession;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller; import android.widget.Scroller;
@@ -220,6 +224,21 @@ public final class ViewRootImpl implements ViewParent,
*/ */
static final int MAX_TRACKBALL_DELAY = 250; static final int MAX_TRACKBALL_DELAY = 250;
/**
* Initial value for {@link #mContentCaptureEnabled}.
*/
private static final int CONTENT_CAPTURE_ENABLED_NOT_CHECKED = 0;
/**
* Value for {@link #mContentCaptureEnabled} when it was checked and set to {@code true}.
*/
private static final int CONTENT_CAPTURE_ENABLED_TRUE = 1;
/**
* Value for {@link #mContentCaptureEnabled} when it was checked and set to {@code false}.
*/
private static final int CONTENT_CAPTURE_ENABLED_FALSE = 2;
@UnsupportedAppUsage @UnsupportedAppUsage
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
@@ -410,6 +429,10 @@ public final class ViewRootImpl implements ViewParent,
boolean mLayoutRequested; boolean mLayoutRequested;
boolean mFirst; boolean mFirst;
@Nullable
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
boolean mPerformContentCapture;
boolean mReportNextDraw; boolean mReportNextDraw;
boolean mFullRedrawNeeded; boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded; boolean mNewSurfaceNeeded;
@@ -607,6 +630,7 @@ public final class ViewRootImpl implements ViewParent,
mTransparentRegion = new Region(); mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region(); mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added mFirst = true; // true for the first time the view is added
mPerformContentCapture = true; // also true for the first time the view is added
mAdded = false; mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context); context);
@@ -2756,9 +2780,55 @@ public final class ViewRootImpl implements ViewParent,
} }
} }
if (mAttachInfo.mContentCaptureEvents != null) {
notifyContentCatpureEvents();
}
mIsInTraversal = false; mIsInTraversal = false;
} }
private void notifyContentCatpureEvents() {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
try {
MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
.getMainContentCaptureSession();
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 {
Log.w(mTag, "invalid content capture event: " + event);
}
}
mainSession.notifyViewTreeEvent(sessionId, /* started= */ false);
}
mAttachInfo.mContentCaptureEvents = null;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void notifySurfaceDestroyed() { private void notifySurfaceDestroyed() {
mSurfaceHolder.ungetCallbacks(); mSurfaceHolder.ungetCallbacks();
SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks(); SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
@@ -2893,6 +2963,13 @@ public final class ViewRootImpl implements ViewParent,
} }
} }
mFirstInputStage.onWindowFocusChanged(hasWindowFocus); mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
// NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
// is lost, so we don't need to to force a flush - there might be other events such as
// text changes, but these should be flushed independently.
if (hasWindowFocus) {
handleContentCaptureFlush();
}
} }
private void fireAccessibilityFocusEventIfHasFocusedNode() { private void fireAccessibilityFocusEventIfHasFocusedNode() {
@@ -3459,6 +3536,86 @@ public final class ViewRootImpl implements ViewParent,
pendingDrawFinished(); pendingDrawFinished();
} }
} }
if (mPerformContentCapture) {
performContentCaptureInitialReport();
}
}
/**
* Checks (and caches) if content capture is enabled for this context.
*/
private boolean isContentCaptureEnabled() {
switch (mContentCaptureEnabled) {
case CONTENT_CAPTURE_ENABLED_TRUE:
return true;
case CONTENT_CAPTURE_ENABLED_FALSE:
return false;
case CONTENT_CAPTURE_ENABLED_NOT_CHECKED:
final boolean reallyEnabled = isContentCaptureReallyEnabled();
mContentCaptureEnabled = reallyEnabled ? CONTENT_CAPTURE_ENABLED_TRUE
: CONTENT_CAPTURE_ENABLED_FALSE;
return reallyEnabled;
default:
Log.w(TAG, "isContentCaptureEnabled(): invalid state " + mContentCaptureEnabled);
return false;
}
}
/**
* Checks (without caching) if content capture is enabled for this context.
*/
private boolean isContentCaptureReallyEnabled() {
// First check if context supports it, so it saves a service lookup when it doesn't
if (mContext.getContentCaptureOptions() == null) return false;
final ContentCaptureManager ccm = mAttachInfo.getContentCaptureManager(mContext);
// Then check if it's enabled in the contex itself.
if (ccm == null || !ccm.isContentCaptureEnabled()) return false;
return true;
}
private void performContentCaptureInitialReport() {
mPerformContentCapture = false; // One-time offer!
final View rootView = mView;
if (DEBUG_CONTENT_CAPTURE) {
Log.v(mTag, "performContentCaptureInitialReport() on " + rootView);
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchContentCapture() for "
+ getClass().getSimpleName());
}
try {
if (!isContentCaptureEnabled()) return;
// Content capture is a go!
rootView.dispatchInitialProvideContentCaptureStructure();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void handleContentCaptureFlush() {
if (DEBUG_CONTENT_CAPTURE) {
Log.v(mTag, "handleContentCaptureFlush()");
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "flushContentCapture for "
+ getClass().getSimpleName());
}
try {
if (!isContentCaptureEnabled()) return;
final ContentCaptureManager ccm = mAttachInfo.mContentCaptureManager;
if (ccm == null) {
Log.w(TAG, "No ContentCapture on AttachInfo");
return;
}
ccm.flush(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
} }
private boolean draw(boolean fullRedrawNeeded) { private boolean draw(boolean fullRedrawNeeded) {

View File

@@ -413,6 +413,9 @@ public class WebView extends AbsoluteLayout
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
} }
if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
}
if (context == null) { if (context == null) {
throw new IllegalArgumentException("Invalid context argument"); throw new IllegalArgumentException("Invalid context argument");
@@ -2795,6 +2798,12 @@ public class WebView extends AbsoluteLayout
mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags); mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
} }
/** @hide */
@Override
public void onProvideContentCaptureStructure(ViewStructure structure, int flags) {
mProvider.getViewDelegate().onProvideContentCaptureStructure(structure, flags);
}
@Override @Override
public void autofill(SparseArray<AutofillValue>values) { public void autofill(SparseArray<AutofillValue>values) {
mProvider.getViewDelegate().autofill(values); mProvider.getViewDelegate().autofill(values);

View File

@@ -1318,7 +1318,8 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
@ViewStructureType int viewFor, int flags) { @ViewStructureType int viewFor, int flags) {
super.onProvideStructure(structure, viewFor, flags); super.onProvideStructure(structure, viewFor, flags);
if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
|| viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
final Adapter adapter = getAdapter(); final Adapter adapter = getAdapter();
if (adapter == null) return; if (adapter == null) return;

View File

@@ -162,6 +162,8 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue; import android.view.autofill.AutofillValue;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CorrectionInfo;
@@ -977,6 +979,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
} }
if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
}
setTextInternal(""); setTextInternal("");
@@ -10550,7 +10555,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
/** /**
* Notify managers (such as {@link AutofillManager}) that are interested in text changes. * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are
* interested on text changes.
*/ */
private void notifyListeningManagersAfterTextChanged() { private void notifyListeningManagersAfterTextChanged() {
@@ -10566,6 +10572,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
afm.notifyValueChanged(TextView.this); afm.notifyValueChanged(TextView.this);
} }
} }
// TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
// of using isLaidout(), so it's not called in cases where it's laid out but a
// notifyAppeared was not sent.
// ContentCapture
if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
if (cm != null && cm.isContentCaptureEnabled()) {
final ContentCaptureSession session = getContentCaptureSession();
if (session != null) {
// TODO(b/111276913): pass flags when edited by user / add CTS test
session.notifyViewTextChanged(getAutofillId(), getText());
}
}
}
} }
private boolean isAutofillable() { private boolean isAutofillable() {
@@ -11409,7 +11431,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final boolean isPassword = hasPasswordTransformationMethod() final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType()); || isPasswordInputType(getInputType());
if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
|| viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
} }
@@ -11425,8 +11448,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
} }
if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
|| viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
if (mLayout == null) { if (mLayout == null) {
if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
}
assumeLayout(); assumeLayout();
} }
Layout layout = mLayout; Layout layout = mLayout;
@@ -11514,7 +11541,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
} }
if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) { if (viewFor == VIEW_STRUCTURE_FOR_ASSIST
|| viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
// Extract style information that applies to the TextView as a whole. // Extract style information that applies to the TextView as a whole.
int style = 0; int style = 0;
int typefaceStyle = getTypefaceStyle(); int typefaceStyle = getTypefaceStyle();
@@ -11542,7 +11570,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
structure.setTextStyle(getTextSize(), getCurrentTextColor(), structure.setTextStyle(getTextSize(), getCurrentTextColor(),
AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
} }
if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
|| viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
structure.setMinTextEms(getMinEms()); structure.setMinTextEms(getMinEms());
structure.setMaxTextEms(getMaxEms()); structure.setMaxTextEms(getMaxEms());
int maxLength = -1; int maxLength = -1;