DO NOT MERGE - Re-add ContentCapture support from standard SDK toolkit.
am: 911cb1fa76
Change-Id: I15c8da47beb6b4dd64b627aebb0288fcb359c3e5
This commit is contained in:
@@ -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 {
|
||||
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 boolean isAutofilled();
|
||||
method public static boolean isDefaultFocusHighlightEnabled();
|
||||
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 public void resetResolvedLayoutDirection();
|
||||
method public void resetResolvedPadding();
|
||||
@@ -3185,7 +3188,13 @@ package android.view {
|
||||
method public boolean restoreFocusNotInCluster();
|
||||
method public void setAutofilled(boolean);
|
||||
method public final void setFocusedInCluster();
|
||||
method public void setImportantForContentCapture(int);
|
||||
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 {
|
||||
|
||||
@@ -1377,6 +1377,74 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
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.
|
||||
* Use with ENABLED_MASK when calling setFlags.
|
||||
@@ -3349,6 +3417,55 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
|
||||
/* 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 */
|
||||
protected static final int VIEW_STRUCTURE_FOR_ASSIST = 0;
|
||||
/** @hide */
|
||||
@@ -3972,6 +4089,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060)
|
||||
int mPrivateFlags3;
|
||||
|
||||
private int mPrivateFlags4;
|
||||
|
||||
/**
|
||||
* This view's request for the visibility of the status bar.
|
||||
* @hide
|
||||
@@ -8427,6 +8546,65 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
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 */
|
||||
protected void onProvideStructure(@NonNull ViewStructure structure,
|
||||
@ViewStructureType int viewFor, int flags) {
|
||||
@@ -9064,6 +9242,274 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
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.
|
||||
*
|
||||
@@ -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)
|
||||
*
|
||||
@@ -13266,6 +13774,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
public void dispatchStartTemporaryDetach() {
|
||||
mPrivateFlags3 |= PFLAG3_TEMPORARY_DETACH;
|
||||
notifyEnterOrExitForAutoFillIfNeeded(false);
|
||||
notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
|
||||
onStartTemporaryDetach();
|
||||
}
|
||||
|
||||
@@ -13292,6 +13801,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
notifyFocusChangeToInputMethodManager(true /* hasFocus */);
|
||||
}
|
||||
notifyEnterOrExitForAutoFillIfNeeded(true);
|
||||
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -13883,6 +14393,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
: AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
|
||||
}
|
||||
}
|
||||
|
||||
notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17578,6 +18090,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
|
||||
// Reset content capture caches
|
||||
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
|
||||
mCachedContentCaptureSession = null;
|
||||
|
||||
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);
|
||||
|
||||
notifyEnterOrExitForAutoFillIfNeeded(true);
|
||||
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
|
||||
}
|
||||
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
|
||||
@@ -19636,6 +20150,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
|
||||
notifyEnterOrExitForAutoFillIfNeeded(false);
|
||||
notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21948,6 +22463,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
|
||||
notifyEnterOrExitForAutoFillIfNeeded(true);
|
||||
}
|
||||
|
||||
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
|
||||
}
|
||||
|
||||
private boolean hasParentWantsFocus() {
|
||||
@@ -28158,6 +28675,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(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
|
||||
* events handler and thread.
|
||||
@@ -28176,6 +28710,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
mRootCallbacks = effectPlayer;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3606,7 +3606,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++) {
|
||||
@@ -3617,14 +3617,30 @@ 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(
|
||||
private @NonNull ChildListForAutoFillOrContentCapture getChildrenForAutofill(
|
||||
@AutofillFlags int flags) {
|
||||
final ChildListForAutofill children = ChildListForAutofill
|
||||
final ChildListForAutoFillOrContentCapture children = ChildListForAutoFillOrContentCapture
|
||||
.obtain();
|
||||
populateChildrenForAutofill(children, flags);
|
||||
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,
|
||||
int childIndex) {
|
||||
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.
|
||||
*/
|
||||
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 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;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,11 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
|
||||
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.autofill.AutofillId;
|
||||
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.widget.Scroller;
|
||||
|
||||
@@ -220,6 +224,21 @@ public final class ViewRootImpl implements ViewParent,
|
||||
*/
|
||||
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
|
||||
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
|
||||
|
||||
@@ -410,6 +429,10 @@ public final class ViewRootImpl implements ViewParent,
|
||||
boolean mLayoutRequested;
|
||||
boolean mFirst;
|
||||
|
||||
@Nullable
|
||||
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
|
||||
boolean mPerformContentCapture;
|
||||
|
||||
boolean mReportNextDraw;
|
||||
boolean mFullRedrawNeeded;
|
||||
boolean mNewSurfaceNeeded;
|
||||
@@ -607,6 +630,7 @@ public final class ViewRootImpl implements ViewParent,
|
||||
mTransparentRegion = new Region();
|
||||
mPreviousTransparentRegion = new Region();
|
||||
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;
|
||||
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
|
||||
context);
|
||||
@@ -2756,9 +2780,55 @@ public final class ViewRootImpl implements ViewParent,
|
||||
}
|
||||
}
|
||||
|
||||
if (mAttachInfo.mContentCaptureEvents != null) {
|
||||
notifyContentCatpureEvents();
|
||||
}
|
||||
|
||||
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() {
|
||||
mSurfaceHolder.ungetCallbacks();
|
||||
SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
|
||||
@@ -2893,6 +2963,13 @@ public final class ViewRootImpl implements ViewParent,
|
||||
}
|
||||
}
|
||||
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() {
|
||||
@@ -3459,6 +3536,86 @@ public final class ViewRootImpl implements ViewParent,
|
||||
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) {
|
||||
|
||||
@@ -413,6 +413,9 @@ public class WebView extends AbsoluteLayout
|
||||
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
|
||||
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
|
||||
}
|
||||
if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
|
||||
setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("Invalid context argument");
|
||||
@@ -2795,6 +2798,12 @@ public class WebView extends AbsoluteLayout
|
||||
mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public void onProvideContentCaptureStructure(ViewStructure structure, int flags) {
|
||||
mProvider.getViewDelegate().onProvideContentCaptureStructure(structure, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void autofill(SparseArray<AutofillValue>values) {
|
||||
mProvider.getViewDelegate().autofill(values);
|
||||
|
||||
@@ -1318,7 +1318,8 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
|
||||
@ViewStructureType int viewFor, int 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();
|
||||
if (adapter == null) return;
|
||||
|
||||
|
||||
@@ -162,6 +162,8 @@ import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.view.contentcapture.ContentCaptureManager;
|
||||
import android.view.contentcapture.ContentCaptureSession;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CorrectionInfo;
|
||||
@@ -977,6 +979,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
|
||||
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
|
||||
}
|
||||
if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
|
||||
setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
@@ -10566,6 +10572,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
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() {
|
||||
@@ -11409,7 +11431,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
|
||||
final boolean isPassword = hasPasswordTransformationMethod()
|
||||
|| 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) {
|
||||
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 (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
|
||||
Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()");
|
||||
}
|
||||
assumeLayout();
|
||||
}
|
||||
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.
|
||||
int style = 0;
|
||||
int typefaceStyle = getTypefaceStyle();
|
||||
@@ -11542,7 +11570,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
structure.setTextStyle(getTextSize(), getCurrentTextColor(),
|
||||
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.setMaxTextEms(getMaxEms());
|
||||
int maxLength = -1;
|
||||
|
||||
Reference in New Issue
Block a user