Implement filtering for Inline Autofill.

Does not include filtering for Augmented Autofill.

The InlineSuggestionsResponse callback is called on every input change
as a mitigation for an IME attempting to glean potentially sensitive
content of the suggestions.

CLs will follow for disabling filtering in certain situations (to
prevent the Keyboard from gleaning the suggestions) and for caching the
inflates).

Test: manual
Test: atest CtsAutoFillServiceTestCases CtsInputMethodTestCases
Bug: 146452916
Change-Id: I396c2cb279f1eb552bbae8c3509cb29c95e86609
This commit is contained in:
Ahaan Ugale
2020-02-07 17:47:09 +01:00
parent cd50731b92
commit 00aa0ae7bf
4 changed files with 71 additions and 18 deletions

View File

@@ -2540,6 +2540,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
ViewState viewState, int flags) {
viewState.setCurrentValue(value);
final String filterText;
if (value == null || !value.isText()) {
filterText = null;
} else {
final CharSequence text = value.getTextValue();
// Text should never be null, but it doesn't hurt to check to avoid a
// system crash...
filterText = (text == null) ? null : text.toString();
}
final AutofillValue filledValue = viewState.getAutofilledValue();
if (filledValue != null) {
if (filledValue.equals(value)) {
@@ -2560,18 +2570,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.maybeCallOnFillReady(flags);
}
} else if (viewState.id.equals(this.mCurrentViewId)
&& (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
requestShowInlineSuggestionsLocked(viewState.getResponse(), filterText);
}
viewState.setState(ViewState.STATE_CHANGED);
final String filterText;
if (value == null || !value.isText()) {
filterText = null;
} else {
final CharSequence text = value.getTextValue();
// Text should never be null, but it doesn't hurt to check to avoid a
// system crash...
filterText = (text == null) ? null : text.toString();
}
getUiForShowing().filterFillUi(filterText, this);
}
@@ -2604,7 +2608,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
if (requestShowInlineSuggestionsLocked(response)) {
if (requestShowInlineSuggestionsLocked(response, filterText)) {
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.setState(ViewState.STATE_INLINE_SHOWN);
//TODO(b/137800469): Fix it to log showed only when IME asks for inflation,
// rather than here where framework sends back the response.
mService.logDatasetShown(id, mClientState);
@@ -2647,7 +2653,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* Returns whether we made a request to show inline suggestions.
*/
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response) {
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
@Nullable String filterText) {
final List<Dataset> datasets = response.getDatasets();
if (datasets == null) {
Log.w(TAG, "response returned null datasets");
@@ -2665,7 +2672,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
InlineSuggestionsResponse inlineSuggestionsResponse =
InlineSuggestionFactory.createInlineSuggestionsResponse(request,
response.getRequestId(),
datasets.toArray(new Dataset[]{}), response.getInlineActions(),
datasets.toArray(new Dataset[]{}), filterText, response.getInlineActions(),
mCurrentViewId, mContext, this, () -> {
synchronized (mLock) {
requestHideFillUi(mCurrentViewId);

View File

@@ -74,6 +74,8 @@ final class ViewState {
public static final int STATE_AUTOFILLED_ONCE = 0x800;
/** View triggered the latest augmented autofill request. */
public static final int STATE_TRIGGERED_AUGMENTED_AUTOFILL = 0x1000;
/** Inline suggestions were shown for this View. */
public static final int STATE_INLINE_SHOWN = 0x2000;
public final AutofillId id;

View File

@@ -313,6 +313,8 @@ final class FillUi {
Slog.e(TAG, "Error inflating remote views", e);
continue;
}
// TODO: Extract the shared filtering logic here and in FillUi to a common
// method.
final DatasetFieldFilter filter = dataset.getFilter(index);
Pattern filterPattern = null;
String valueText = null;
@@ -602,6 +604,7 @@ final class FillUi {
* Returns whether this item matches the value input by the user so it can be included
* in the filtered datasets.
*/
// TODO: Extract the shared filtering logic here and in FillUi to a common method.
public boolean matches(CharSequence filterText) {
if (TextUtils.isEmpty(filterText)) {
// Always show item when the user input is empty

View File

@@ -17,6 +17,7 @@
package com.android.server.autofill.ui;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -24,10 +25,12 @@ import android.content.Context;
import android.os.RemoteException;
import android.service.autofill.Dataset;
import android.service.autofill.InlinePresentation;
import android.text.TextUtils;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.View;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.inline.InlinePresentationSpec;
import android.view.inputmethod.InlineSuggestion;
import android.view.inputmethod.InlineSuggestionInfo;
@@ -42,6 +45,7 @@ import com.android.server.UiThread;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
public final class InlineSuggestionFactory {
private static final String TAG = "InlineSuggestionFactory";
@@ -69,17 +73,19 @@ public final class InlineSuggestionFactory {
@NonNull Runnable onErrorCallback) {
if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
return createInlineSuggestionsResponseInternal(/* isAugmented= */ true, request,
datasets, /* inlineActions= */ null, autofillId, context, onErrorCallback,
datasets, /* filterText= */ null, /* inlineActions= */ null, autofillId, context,
onErrorCallback,
(dataset, filedIndex) -> (v -> inlineSuggestionUiCallback.autofill(dataset)));
}
/**
* Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
* autofill service.
* autofill service, potentially filtering the datasets.
*/
public static InlineSuggestionsResponse createInlineSuggestionsResponse(
@NonNull InlineSuggestionsRequest request, int requestId,
@NonNull Dataset[] datasets,
@Nullable String filterText,
@Nullable List<InlinePresentation> inlineActions,
@NonNull AutofillId autofillId,
@NonNull Context context,
@@ -87,15 +93,15 @@ public final class InlineSuggestionFactory {
@NonNull Runnable onErrorCallback) {
if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, datasets,
inlineActions, autofillId, context, onErrorCallback,
filterText, inlineActions, autofillId, context, onErrorCallback,
(dataset, filedIndex) -> (v -> client.fill(requestId, filedIndex, dataset)));
}
private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
boolean isAugmented, @NonNull InlineSuggestionsRequest request,
@NonNull Dataset[] datasets, @Nullable List<InlinePresentation> inlineActions,
@NonNull AutofillId autofillId, @NonNull Context context,
@NonNull Runnable onErrorCallback,
@NonNull Dataset[] datasets, @Nullable String filterText,
@Nullable List<InlinePresentation> inlineActions, @NonNull AutofillId autofillId,
@NonNull Context context, @NonNull Runnable onErrorCallback,
@NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
@@ -113,6 +119,9 @@ public final class InlineSuggestionFactory {
Slog.w(TAG, "InlinePresentation not found in dataset");
return null;
}
if (!includeDataset(dataset, fieldIndex, filterText)) {
continue;
}
InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
fieldIndex, mergedInlinePresentation(request, i, inlinePresentation),
inlineSuggestionUi, onClickListenerFactory);
@@ -129,6 +138,38 @@ public final class InlineSuggestionFactory {
return new InlineSuggestionsResponse(inlineSuggestions);
}
// TODO: Extract the shared filtering logic here and in FillUi to a common method.
private static boolean includeDataset(Dataset dataset, int fieldIndex,
@Nullable String filterText) {
// Show everything when the user input is empty.
if (TextUtils.isEmpty(filterText)) {
return true;
}
final String constraintLowerCase = filterText.toString().toLowerCase();
// Use the filter provided by the service, if available.
final Dataset.DatasetFieldFilter filter = dataset.getFilter(fieldIndex);
if (filter != null) {
Pattern filterPattern = filter.pattern;
if (filterPattern == null) {
if (sVerbose) {
Slog.v(TAG, "Explicitly disabling filter for dataset id" + dataset.getId());
}
return true;
}
return filterPattern.matcher(constraintLowerCase).matches();
}
final AutofillValue value = dataset.getFieldValues().get(fieldIndex);
if (value == null || !value.isText()) {
return dataset.getAuthentication() == null;
}
final String valueText = value.getTextValue().toString().toLowerCase();
return valueText.toLowerCase().startsWith(constraintLowerCase);
}
private static InlineSuggestion createInlineAction(boolean isAugmented,
@NonNull Context context,
@NonNull InlinePresentation inlinePresentation,