Merge "Reporting the Autofill IDs and Positions of active views in list view" into tm-qpr-dev am: 25192d7d0c

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19054708

Change-Id: I6b8ffade431d057334147ae9cd5386afbad13cbc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mihir Patel
2022-08-10 17:41:15 +00:00
committed by Automerger Merge Worker
5 changed files with 184 additions and 1 deletions

View File

@@ -630,7 +630,7 @@ public final class DeviceConfig {
private static final List<String> PUBLIC_NAMESPACES = private static final List<String> PUBLIC_NAMESPACES =
Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA, Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL, NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL,
NAMESPACE_DEVICE_POLICY_MANAGER); NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE);
/** /**
* Privacy related properties definitions. * Privacy related properties definitions.
* *

View File

@@ -44,6 +44,30 @@ import java.util.List;
*/ */
public abstract class ViewStructure { public abstract class ViewStructure {
/**
* Key used for writing active child view information to the content capture bundle.
*
* The value stored under this key will be an ordered list of Autofill IDs of child views.
*
* TODO(b/241498401): Add @TestApi in Android U
* @hide
*/
public static final String EXTRA_ACTIVE_CHILDREN_IDS =
"android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS";
/**
* Key used for writing the first active child's position to the content capture bundle.
*
* When active child view information is provided under the
* {@link #EXTRA_ACTIVE_CHILDREN_IDS}, the value stored under this key will be the
* 0-based position of the first child view in the list relative to the positions of child views
* in the containing View's dataset.
*
* TODO(b/241498401): Add @TestApi in Android U
* @hide */
public static final String EXTRA_FIRST_ACTIVE_POSITION =
"android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION";
/** /**
* Set the identifier for this view. * Set the identifier for this view.
* *

View File

@@ -55,6 +55,15 @@ public final class ContentCaptureEvent implements Parcelable {
/** /**
* Called when a node has been added to the screen and is visible to the user. * Called when a node has been added to the screen and is visible to the user.
* *
* On API level 33, this event may be re-sent with additional information if a view's children
* have changed, e.g. scrolling Views inside of a ListView. This information will be stored in
* the extras Bundle associated with the event's ViewNode. Within the Bundle, the
* "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" key may be used to get a list of
* Autofill IDs of active child views, and the
* "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" key may be used to get the 0-based
* position of the first active child view in the list relative to the positions of child views
* in the container View's dataset.
*
* <p>The metadata of the node is available through {@link #getViewNode()}. * <p>The metadata of the node is available through {@link #getViewNode()}.
*/ */
public static final int TYPE_VIEW_APPEARED = 1; public static final int TYPE_VIEW_APPEARED = 1;

View File

@@ -279,6 +279,15 @@ public final class ContentCaptureManager {
public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED = public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED =
"service_explicitly_enabled"; "service_explicitly_enabled";
/**
* Device config property used by {@code android.widget.AbsListView} to determine whether or
* not it should report the positions of its children to Content Capture.
*
* @hide
*/
public static final String DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN =
"report_list_view_children";
/** /**
* Maximum number of events that are buffered before sent to the app. * Maximum number of events that are buffered before sent to the app.
* *

View File

@@ -20,6 +20,7 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes; import android.annotation.DrawableRes;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.TestApi; import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -37,6 +38,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.StrictMode; import android.os.StrictMode;
import android.os.Trace; import android.os.Trace;
import android.provider.DeviceConfig;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
@@ -65,6 +67,7 @@ import android.view.ViewDebug;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder; import android.view.ViewHierarchyEncoder;
import android.view.ViewParent; import android.view.ViewParent;
import android.view.ViewStructure;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
@@ -73,6 +76,9 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator; import android.view.animation.LinearInterpolator;
import android.view.autofill.AutofillId;
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;
@@ -633,6 +639,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/ */
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE; private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
* Indicates that reporting positions of child views to content capture is enabled via
* DeviceConfig.
*/
private static boolean sContentCaptureReportingEnabledByDeviceConfig = false;
/**
* Listens for changes to DeviceConfig properties and updates stored values accordingly.
*/
private static DeviceConfig.OnPropertiesChangedListener sDeviceConfigChangeListener = null;
/**
* Indicates that child positions of views should be reported to Content Capture the next time
* that active views are refreshed.
*/
private boolean mReportChildrenToContentCaptureOnNextUpdate = true;
/** /**
* Helper object that renders and controls the fast scroll thumb. * Helper object that renders and controls the fast scroll thumb.
*/ */
@@ -850,8 +873,44 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public void adjustListItemSelectionBounds(Rect bounds); public void adjustListItemSelectionBounds(Rect bounds);
} }
private static class DeviceConfigChangeListener
implements DeviceConfig.OnPropertiesChangedListener {
@Override
public void onPropertiesChanged(
@NonNull DeviceConfig.Properties properties) {
if (!DeviceConfig.NAMESPACE_CONTENT_CAPTURE.equals(properties.getNamespace())) {
return;
}
for (String key : properties.getKeyset()) {
if (!ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN
.equals(key)) {
continue;
}
sContentCaptureReportingEnabledByDeviceConfig = properties.getBoolean(key,
false);
}
}
}
private static void setupDeviceConfigProperties() {
if (sDeviceConfigChangeListener == null) {
sContentCaptureReportingEnabledByDeviceConfig = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN,
false);
sDeviceConfigChangeListener = new DeviceConfigChangeListener();
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
ActivityThread.currentApplication().getMainExecutor(),
sDeviceConfigChangeListener);
}
}
public AbsListView(Context context) { public AbsListView(Context context) {
super(context); super(context);
setupDeviceConfigProperties();
mEdgeGlowBottom = new EdgeEffect(context); mEdgeGlowBottom = new EdgeEffect(context);
mEdgeGlowTop = new EdgeEffect(context); mEdgeGlowTop = new EdgeEffect(context);
initAbsListView(); initAbsListView();
@@ -874,6 +933,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
setupDeviceConfigProperties();
mEdgeGlowBottom = new EdgeEffect(context, attrs); mEdgeGlowBottom = new EdgeEffect(context, attrs);
mEdgeGlowTop = new EdgeEffect(context, attrs); mEdgeGlowTop = new EdgeEffect(context, attrs);
initAbsListView(); initAbsListView();
@@ -4699,6 +4759,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mOnScrollListener.onScrollStateChanged(this, newState); mOnScrollListener.onScrollStateChanged(this, newState);
} }
} }
// When scrolling, we want to report changes in the active children to Content Capture,
// so set the flag to report on the next update only when scrolling has stopped or a fling
// scroll is performed.
if (newState == OnScrollListener.SCROLL_STATE_IDLE
|| newState == OnScrollListener.SCROLL_STATE_FLING) {
mReportChildrenToContentCaptureOnNextUpdate = true;
}
} }
/** /**
@@ -6654,10 +6722,77 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mRecycler.mRecyclerListener = listener; mRecycler.mRecyclerListener = listener;
} }
/**
* {@inheritDoc}
*
* This method will initialize the fields of the {@link ViewStructure}
* using the base implementation in {@link View}. On API level 33 and higher, it may also
* write information about the positions of active views to the extras bundle provided by the
* {@link ViewStructure}.
*
* NOTE: When overriding this method on API level 33, if not calling super() or if changing the
* logic for child views, be sure to provide values for the first active child view position and
* the list of active child views in the {@link ViewStructure}'s extras {@link Bundle} using the
* "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" and
* "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" keys.
*
* @param structure {@link ViewStructure} to be filled in with structured view data.
* @param flags optional flags.
*
* @see View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
*/
@Override
public void onProvideContentCaptureStructure(
@NonNull ViewStructure structure, int flags) {
super.onProvideContentCaptureStructure(structure, flags);
if (!sContentCaptureReportingEnabledByDeviceConfig) {
return;
}
Bundle extras = structure.getExtras();
if (extras == null) {
Log.wtf(TAG, "Unexpected null extras Bundle in ViewStructure");
return;
}
int childCount = getChildCount();
ArrayList<AutofillId> idsList = new ArrayList<>(childCount);
for (int i = 0; i < childCount; ++i) {
View activeView = getChildAt(i);
if (activeView == null) {
continue;
}
idsList.add(activeView.getAutofillId());
}
extras.putParcelableArrayList(ViewStructure.EXTRA_ACTIVE_CHILDREN_IDS,
idsList);
extras.putInt(ViewStructure.EXTRA_FIRST_ACTIVE_POSITION,
getFirstVisiblePosition());
}
private void reportActiveViewsToContentCapture() {
if (!sContentCaptureReportingEnabledByDeviceConfig) {
return;
}
ContentCaptureSession session = getContentCaptureSession();
if (session != null) {
ViewStructure structure = session.newViewStructure(this);
onProvideContentCaptureStructure(structure, /* flags= */ 0);
session.notifyViewAppeared(structure);
}
}
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver { class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override @Override
public void onChanged() { public void onChanged() {
super.onChanged(); super.onChanged();
mReportChildrenToContentCaptureOnNextUpdate = true;
if (mFastScroll != null) { if (mFastScroll != null) {
mFastScroll.onSectionsChanged(); mFastScroll.onSectionsChanged();
} }
@@ -6666,6 +6801,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override @Override
public void onInvalidated() { public void onInvalidated() {
super.onInvalidated(); super.onInvalidated();
mReportChildrenToContentCaptureOnNextUpdate = true;
if (mFastScroll != null) { if (mFastScroll != null) {
mFastScroll.onSectionsChanged(); mFastScroll.onSectionsChanged();
} }
@@ -6984,6 +7120,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
lp.scrappedFromPosition = firstActivePosition + i; lp.scrappedFromPosition = firstActivePosition + i;
} }
} }
if (mReportChildrenToContentCaptureOnNextUpdate && childCount > 0) {
AbsListView.this.reportActiveViewsToContentCapture();
mReportChildrenToContentCaptureOnNextUpdate = false;
}
} }
/** /**