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

Commit 8017ab31 authored by Haoran Zhang's avatar Haoran Zhang Committed by Android (Google) Code Review
Browse files

Merge "Change to apply heuristic and denylist to decide whether trigger fill...

Merge "Change to apply heuristic and denylist to decide whether trigger fill request on unimportant views. Test: 1. atest tests/autofillservice/src/android/autofillservice/cts/servicebehavior/AutofillForAllAppsTest.java 2. bug to track adding unit tests: b/265312963 Bug: b/243453507"
parents 6de452ae 7b54a944
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -3212,6 +3212,9 @@ package android.view.autofill {
    field public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS = "autofill_credential_manager_ignore_views";
    field public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED = "autofill_dialog_enabled";
    field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes";
    field public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS = "non_autofillable_ime_action_ids";
    field public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW = "package_deny_list_for_unimportant_view";
    field public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW = "trigger_fill_request_on_unimportant_view";
  }

  public final class AutofillId implements android.os.Parcelable {
+27 −0
Original line number Diff line number Diff line
@@ -10244,6 +10244,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return mContext.getSystemService(AutofillManager.class);
    }
    /**
     * Check whether current activity / package is in denylist.If it's in the denylist,
     * then the views marked as not important for autofill are not eligible for autofill.
     */
    final boolean isActivityDeniedForAutofillForUnimportantView() {
        final AutofillManager afm = getAutofillManager();
        // keep behavior same with denylist feature not enabled
        if (afm == null) return true;
        return afm.isActivityDeniedForAutofillForUnimportantView();
    }
    /**
     * Check whether current view matches autofillable heuristics
     */
    final boolean isMatchingAutofillableHeuristics() {
        final AutofillManager afm = getAutofillManager();
        // keep default behavior
        if (afm == null) return false;
        return afm.isMatchingAutofillableHeuristics(this);
    }
    private boolean isAutofillable() {
        if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
@@ -10252,6 +10273,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                && isCredential()) return false;
        if (!isImportantForAutofill()) {
            // If view matches heuristics and is not denied, it will be treated same as view that's
            // important for autofill
            if (isMatchingAutofillableHeuristics()
                    && !isActivityDeniedForAutofillForUnimportantView()) {
                return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
            }
            // View is not important for "regular" autofill, so we must check if Augmented Autofill
            // is enabled for the activity
            final AutofillOptions options = mContext.getAutofillOptions();
+3 −1
Original line number Diff line number Diff line
@@ -3723,7 +3723,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            final View child = (preorderedList == null)
                    ? mChildren[childIndex] : preorderedList.get(childIndex);
            if ((flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
                    || child.isImportantForAutofill()) {
                    || child.isImportantForAutofill()
                    || (child.isMatchingAutofillableHeuristics()
                        && !child.isActivityDeniedForAutofillForUnimportantView())) {
                list.add(child);
            } else if (child instanceof ViewGroup) {
                ((ViewGroup) child).populateChildrenForAutofill(list, flags);
+83 −2
Original line number Diff line number Diff line
@@ -16,13 +16,18 @@

package android.view.autofill;

import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;

import com.android.internal.util.ArrayUtils;

import java.util.Arrays;
import java.util.Set;

/**
 * Feature flags associated with autofill.
 * @hide
@@ -119,8 +124,6 @@ public class AutofillFeatureFlags {
    public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG =
            "autofill_credential_manager_suppress_fill_dialog";



    /**
     * Indicates whether credential manager tagged views should suppress save dialog.
     * This flag is further gated by {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
@@ -131,6 +134,50 @@ public class AutofillFeatureFlags {
            "autofill_credential_manager_suppress_save_dialog";
    // END CREDENTIAL MANAGER FLAGS //

    // START AUTOFILL FOR ALL APPS FLAGS //
    /**
     * Sets the list of activities and packages denied for autofill
     *
     * The list is {@code ";"} colon delimited. Activities under a package is separated by
     * {@code ","}. Each package name much be followed by a {@code ":"}. Each package entry must be
     * ends with a {@code ";"}
     *
     * <p>For example, a list with only 1 package would be, {@code Package1:;}. A list with one
     * denied activity {@code Activity1} under {@code Package1} and a full denied package
     * {@code Package2} would be {@code Package1:Activity1;Package2:;}
     *
     * @hide
     */
    @TestApi
    public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW =
            "package_deny_list_for_unimportant_view";

    /**
     * Whether the heuristics check for view is enabled
     *
     * @hide
     */
    @TestApi
    public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW =
            "trigger_fill_request_on_unimportant_view";

    /**
     * Continas imeAction ids that is irrelevant for autofill. For example, ime_action_search. We
     * use this to avoid trigger fill request on unimportant views.
     *
     * The list is {@code ","} delimited.
     *
     * <p> For example, a imeAction list could be "2,3,4", corresponding to ime_action definition
     * in {@link android.view.inputmethod.EditorInfo.java}</p>
     *
     * @hide
     */
    @TestApi
    @SuppressLint("IntentName")
    public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS =
            "non_autofillable_ime_action_ids";
    // END AUTOFILL FOR ALL APPS FLAGS //

    /**
     * Sets a value of delay time to show up the inline tooltip view.
     *
@@ -221,4 +268,38 @@ public class AutofillFeatureFlags {
                DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG,
                DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG);
    }

    /**
     * Whether triggering fill request on unimportant view is enabled.
     *
     * @hide
     */
    public static boolean isTriggerFillRequestOnUnimportantViewEnabled() {
        return DeviceConfig.getBoolean(
            DeviceConfig.NAMESPACE_AUTOFILL,
            DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW, false);
    }

    /**
     * Get the non-autofillable ime actions from flag. This will be used in filtering
     * condition to trigger fill request.
     *
     * @hide
     */
    public static Set<String> getNonAutofillableImeActionIdSetFromFlag() {
        final String mNonAutofillableImeActions = DeviceConfig.getString(
                DeviceConfig.NAMESPACE_AUTOFILL, DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS, "");
        return new ArraySet<>(Arrays.asList(mNonAutofillableImeActions.split(",")));
    }

    /**
     * Get denylist string from flag
     *
     * @hide
     */
    public static String getDenylistStringFromFlag() {
        return DeviceConfig.getString(
            DeviceConfig.NAMESPACE_AUTOFILL,
            DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
    }
}
+147 −0
Original line number Diff line number Diff line
@@ -86,8 +86,13 @@ import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputMethodManager;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
@@ -657,6 +662,23 @@ public final class AutofillManager {

    private final boolean mIsFillDialogEnabled;

    // Indicate whether trigger fill request on unimportant views is enabled
    private boolean mIsTriggerFillRequestOnUnimportantViewEnabled = false;

    // A set containing all non-autofillable ime actions passed by flag
    private Set<String> mNonAutofillableImeActionIdSet = new ArraySet<>();

    // If a package is fully denied, then all views that marked as not
    // important for autofill will not trigger fill request
    private boolean mIsPackageFullyDeniedForAutofillForUnimportantView = false;

    // If a package is partially denied, autofill manager will check whether
    // current activity is in deny set to decide whether to trigger fill request
    private boolean mIsPackagePartiallyDeniedForAutofillForUnimportantView = false;

    // A deny set read from device config
    private Set<String> mDeniedActivitiySet = new ArraySet<>();

    // Indicates whether called the showAutofillDialog() method.
    private boolean mShowAutofillDialogCalled = false;

@@ -816,8 +838,133 @@ public final class AutofillManager {
            sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
            sVerbose = (mOptions.loggingLevel & FLAG_ADD_CLIENT_VERBOSE) != 0;
        }

        mIsTriggerFillRequestOnUnimportantViewEnabled =
            AutofillFeatureFlags.isTriggerFillRequestOnUnimportantViewEnabled();

        mNonAutofillableImeActionIdSet =
            AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag();

        final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag();

        final String packageName = mContext.getPackageName();

        mIsPackageFullyDeniedForAutofillForUnimportantView =
            isPackageFullyDeniedForAutofillForUnimportantView(denyListString, packageName);

        mIsPackagePartiallyDeniedForAutofillForUnimportantView =
            isPackagePartiallyDeniedForAutofillForUnimportantView(denyListString, packageName);

        if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
            setDeniedActivitySetWithDenyList(denyListString, packageName);
        }
    }

    private boolean isPackageFullyDeniedForAutofillForUnimportantView(
            @NonNull String denyListString, @NonNull String packageName) {
        // If "PackageName:;" is in the string, then it means the package name is in denylist
        // and there are no activities specified under it. That means the package is fully
        // denied for autofill
        return denyListString.indexOf(packageName + ":;") != -1;
    }

    private boolean isPackagePartiallyDeniedForAutofillForUnimportantView(
            @NonNull String denyListString, @NonNull String packageName) {
        // This check happens after checking package is not fully denied. If "PackageName:" instead
        // is in denylist, then it means there are specific activities to be denied. So the package
        // is partially denied for autofill
        return denyListString.indexOf(packageName + ":") != -1;
    }

    /**
     * Get the denied activitiy names under specified package from denylist and set it in field
     * mDeniedActivitiySet
     *
     * If using parameter as the example below, the denied activity set would be set to
     * Set{Activity1,Activity2}.
     *
     * @param denyListString Denylist that is got from device config. For example,
     *        "Package1:Activity1,Activity2;Package2:;"
     * @param packageName Specify to extract activities under which package.For example,
     *        "Package1:;"
     */
    private void setDeniedActivitySetWithDenyList(
            @NonNull String denyListString, @NonNull String packageName) {
        // 1. Get the index of where the Package name starts
        final int packageInStringIndex = denyListString.indexOf(packageName + ":");

        // 2. Get the ";" index after this index of package
        final int firstNextSemicolonIndex = denyListString.indexOf(";", packageInStringIndex);

        // 3. Get the activity names substring between the indexes
        final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
        if (activityStringStartIndex < firstNextSemicolonIndex) {
            Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
                    + "formatted");
        }
        final String activitySubstring =
                denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);

        // 4. Split the activity name substring
        final String[] activityStringArray = activitySubstring.split(",");

        // 5. Set the denied activity set
        mDeniedActivitiySet = new ArraySet<>(Arrays.asList(activityStringArray));

        return;
    }

    /**
     * Check whether autofill is denied for current activity or package. Used when a view is marked
     * as not important for autofill, if current activity or package is denied, then the view won't
     * trigger fill request.
     *
     * @hide
     */
    public final boolean isActivityDeniedForAutofillForUnimportantView() {
        if (mIsPackageFullyDeniedForAutofillForUnimportantView) {
            return true;
        }
        if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
            final AutofillClient client = getClient();
            if (client == null) {
                return false;
            }
            final ComponentName clientActivity = client.autofillClientGetComponentName();
            if (mDeniedActivitiySet.contains(clientActivity.flattenToShortString())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check whether view matches autofill-able heuristics
     *
     * @hide
     */
    public final boolean isMatchingAutofillableHeuristics(@NonNull View view) {
        if (!mIsTriggerFillRequestOnUnimportantViewEnabled) {
            return false;
        }
        if (view instanceof EditText) {
            final int actionId = ((EditText) view).getImeOptions();
            if (mNonAutofillableImeActionIdSet.contains(String.valueOf(actionId))) {
                return false;
            }
            return true;
        }
        if (view instanceof CheckBox
                || view instanceof Spinner
                || view instanceof DatePicker
                || view instanceof TimePicker
                || view instanceof RadioGroup) {
            return true;
        }
        return false;
    }


    /**
     * @hide
     */