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

Commit a6e0f74c authored by Mihir Patel's avatar Mihir Patel
Browse files

Reporting the Autofill IDs and Positions of active views in list view

Test: atest CtsContentCaptureServiceTestCases, observed events reported only once on scroll and stop as well as on fling
Bug: 211899037
Change-Id: Id67fcc360bf63fcab1d9f345c31d2bf7b9a173d6
parent bbb13e73
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -623,7 +623,7 @@ public final class DeviceConfig {
    private static final List<String> PUBLIC_NAMESPACES =
            Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
                    NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL,
                    NAMESPACE_DEVICE_POLICY_MANAGER);
                    NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE);
    /**
     * Privacy related properties definitions.
     *
+24 −0
Original line number Diff line number Diff line
@@ -44,6 +44,30 @@ import java.util.List;
 */
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.
     *
+9 −0
Original line number Diff line number Diff line
@@ -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.
     *
     * 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()}.
     */
    public static final int TYPE_VIEW_APPEARED = 1;
+9 −0
Original line number Diff line number Diff line
@@ -279,6 +279,15 @@ public final class ContentCaptureManager {
    public static final String DEVICE_CONFIG_PROPERTY_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.
     *
+141 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -37,6 +38,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.Trace;
import android.provider.DeviceConfig;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
@@ -65,6 +67,7 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
import android.view.ViewStructure;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -73,6 +76,9 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.animation.Interpolator;
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.CompletionInfo;
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;

    /**
     * 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.
     */
@@ -850,8 +873,44 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        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) {
        super(context);
        setupDeviceConfigProperties();
        mEdgeGlowBottom = new EdgeEffect(context);
        mEdgeGlowTop = new EdgeEffect(context);
        initAbsListView();
@@ -874,6 +933,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te

    public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setupDeviceConfigProperties();
        mEdgeGlowBottom = new EdgeEffect(context, attrs);
        mEdgeGlowTop = new EdgeEffect(context, attrs);
        initAbsListView();
@@ -4699,6 +4759,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                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;
    }

    /**
     * {@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 {
        @Override
        public void onChanged() {
            super.onChanged();
            mReportChildrenToContentCaptureOnNextUpdate = true;
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
@@ -6666,6 +6801,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        @Override
        public void onInvalidated() {
            super.onInvalidated();
            mReportChildrenToContentCaptureOnNextUpdate = true;
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
@@ -6984,6 +7120,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }

            if (mReportChildrenToContentCaptureOnNextUpdate && childCount > 0) {
                AbsListView.this.reportActiveViewsToContentCapture();
                mReportChildrenToContentCaptureOnNextUpdate = false;
            }
        }

        /**