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 Change-Id: Ib32d35fd61e0d61526209cf5d58ead9515046944
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user