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

Commit 6dd83238 authored by Simranjit Kohli's avatar Simranjit Kohli
Browse files

[Relayout] Part 3: Introduce more functionality[Relayout]

Add in functionality to find all autofillable views.
This is useful when relayout happens, and we need to find
all autofillable views without having an autofill request
trigger, or assist structure generated.

Bug: 238252288
Flag: EXEMPT : DeviceConfig flags used: enable_relayout
exception granted in b/318391032
Test: atest CtsAutoFillServiceTestCases
      atest FrameworksCoreTests:ViewGroupTest

Change-Id: Iae0f5ec10cd17c9524ff2567a5a8cb0c7a0c1775
parent c061e514
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -11003,6 +11003,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            ? afm.isAutofillable(this) : false;
    }
    /**
     * Returns whether the view is autofillable.
     *
     * @return whether the view is autofillable, and should send out autofill request to provider.
     */
    private boolean isAutofillable() {
        if (DBG) {
            Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
@@ -27612,6 +27617,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return null;
    }
    /**
     * Performs the traversal to find views that are autofillable.
     * Autofillable views are added to the provided list.
     *
     * <strong>Note:</strong>This method does not stop at the root namespace
     * boundary.
     *
     * @param autofillableViews The output list of autofillable Views.
     * @hide
     */
    public void findAutofillableViewsByTraversal(@NonNull List<View> autofillableViews) {
        if (isAutofillable()) {
            autofillableViews.add(this);
        }
    }
    /**
     * Look for a child view with the given tag.  If this view has the given
     * tag, return this view.
@@ -30578,6 +30600,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
    }
    // Note that if the function returns true, it indicates aapt did not generate this id.
    // However false value does not indicate that aapt did generated this id.
    private static boolean isViewIdGenerated(int id) {
        return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0;
    }
+13 −0
Original line number Diff line number Diff line
@@ -1500,6 +1500,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        return null;
    }

    /** @hide */
    @Override
    public void findAutofillableViewsByTraversal(@NonNull List<View> autofillableViews) {
        super.findAutofillableViewsByTraversal(autofillableViews);

        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < childrenCount; i++) {
            View child = children[i];
            child.findAutofillableViewsByTraversal(autofillableViews);
        }
    }

    @Override
    public void dispatchWindowFocusChanged(boolean hasFocus) {
        super.dispatchWindowFocusChanged(hasFocus);
+17 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.view.WindowManagerGlobal;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * A controller to manage the autofill requests for the {@link Activity}.
@@ -495,6 +496,22 @@ public final class AutofillClientController implements AutofillManager.AutofillC
        return views;
    }

    @Override
    public List<View> autofillClientFindAutofillableViewsByTraversal() {
        final ArrayList<View> views = new ArrayList<>();
        final ArrayList<ViewRootImpl> roots =
                WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());

        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
            final View rootView = roots.get(rootNum).getView();

            if (rootView != null) {
                rootView.findAutofillableViewsByTraversal(views);
            }
        }
        return views;
    }

    @Override
    public boolean autofillClientIsFillUiShowing() {
        return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing();
+7 −0
Original line number Diff line number Diff line
@@ -873,6 +873,13 @@ public final class AutofillManager {
         */
        @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, int windowId);

        /**
         * Finds all the autofillable views on the screen.
         *
         * @return The list of views that are autofillable.
         */
        List<View> autofillClientFindAutofillableViewsByTraversal();

        /**
         * Runs the specified action on the UI thread.
         */
+56 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.view;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
@@ -29,11 +30,16 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.Region;
import android.platform.test.annotations.Presubmit;
import android.view.autofill.AutofillId;

import androidx.test.filters.SmallTest;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * Test basic functions of ViewGroup.
@@ -182,6 +188,31 @@ public class ViewGroupTest {
        assertRegionContainPoint(3 /* x */, r, false /* contain */); // Outside of bounds
    }

    @Test
    public void testfindAutofillableViewsByTraversal() {
        final Context context = getInstrumentation().getContext();
        final TestView viewGroup = new TestView(context, 200 /* right */);

        // viewA and viewC are autofillable. ViewB isn't.
        final TestView viewA = spy(new AutofillableTestView(context, 100 /* right */));
        final TestView viewB = spy(new NonAutofillableTestView(context, 200 /* right */));
        final TestView viewC = spy(new AutofillableTestView(context, 300 /* right */));

        viewGroup.addView(viewA);
        viewGroup.addView(viewB);
        viewGroup.addView(viewC);

        List<View> autofillableViews = new ArrayList<>();
        viewGroup.findAutofillableViewsByTraversal(autofillableViews);

        verify(viewA).findAutofillableViewsByTraversal(autofillableViews);
        verify(viewB).findAutofillableViewsByTraversal(autofillableViews);
        verify(viewC).findAutofillableViewsByTraversal(autofillableViews);

        assertEquals("Size of autofillable views", 2, autofillableViews.size());
        assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
    }

    private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
        outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
        final ViewParent parent = view.getParent();
@@ -210,4 +241,29 @@ public class ViewGroupTest {
            // We don't layout this view.
        }
    }

    public static class AutofillableTestView extends TestView {
        AutofillableTestView(Context context, int right) {
            super(context, right);
            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
            // Need to set autofill id in such a way that the view is considered part of activity.
            setAutofillId(new AutofillId(LAST_APP_AUTOFILL_ID + 5));
        }

        @Override
        public @AutofillType int getAutofillType() {
            return AUTOFILL_TYPE_TEXT;
        }
    }

    public static class NonAutofillableTestView extends TestView {
        NonAutofillableTestView(Context context, int right) {
            super(context, right);
        }

        @Override
        public @AutofillType int getAutofillType() {
            return AUTOFILL_TYPE_NONE;
        }
    }
}