diff options
| -rw-r--r-- | core/api/test-current.txt | 3 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 27 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 4 | ||||
| -rw-r--r-- | core/java/android/view/autofill/AutofillFeatureFlags.java | 85 | ||||
| -rw-r--r-- | core/java/android/view/autofill/AutofillManager.java | 147 |
5 files changed, 263 insertions, 3 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 84ac868c7e17..bc88922b2d15 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -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 { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c73cfc22104b..5ac98198530c 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -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(); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 46b2cfc3f7a7..0e4ac0109f8e 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -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); diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 59ad15139de1..cba399f996b2 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -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, ""); + } } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 58e7a70b155c..2ad01ed99b13 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -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,9 +838,134 @@ 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 */ public void enableCompatibilityMode() { |