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:
Haoran Zhang
2022-12-05 22:16:03 +00:00
parent a43dbaae9b
commit 7b54a944f4
5 changed files with 263 additions and 3 deletions

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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, "");
}
}

View File

@@ -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
*/