diff options
4 files changed, 122 insertions, 67 deletions
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 53f85ea7b119..72029d178534 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2436,65 +2436,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE); return; } - if (!Objects.equals(value, viewState.getCurrentValue())) { - if ((value == null || value.isEmpty()) - && viewState.getCurrentValue() != null - && viewState.getCurrentValue().isText() - && viewState.getCurrentValue().getTextValue() != null - && getSaveInfoLocked() != null) { - final int length = viewState.getCurrentValue().getTextValue().length(); - if (sDebug) { - Slog.d(TAG, "updateLocked(" + id + "): resetting value that was " - + length + " chars long"); - } - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); - mMetricsLogger.write(log); - } - - // Always update the internal state. - viewState.setCurrentValue(value); - - // Must check if this update was caused by autofilling the view, in which - // case we just update the value, but not the UI. - final AutofillValue filledValue = viewState.getAutofilledValue(); - if (filledValue != null) { - if (filledValue.equals(value)) { - if (sVerbose) { - Slog.v(TAG, "ignoring autofilled change on id " + id); - } - viewState.resetState(ViewState.STATE_CHANGED); - return; - } - else { - if ((viewState.id.equals(this.mCurrentViewId)) && - (viewState.getState() & ViewState.STATE_AUTOFILLED) != 0) { - // Remove autofilled state once field is changed after autofilling. - if (sVerbose) { - Slog.v(TAG, "field changed after autofill on id " + id); - } - viewState.resetState(ViewState.STATE_AUTOFILLED); - final ViewState currentView = mViewStates.get(mCurrentViewId); - currentView.maybeCallOnFillReady(flags); - } - } - } - - // Update the internal state... - viewState.setState(ViewState.STATE_CHANGED); - - //..and the UI - 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); + logIfViewClearedLocked(id, value, viewState); + updateViewStateAndUiOnValueChangedLocked(id, value, viewState, flags); } break; case ACTION_VIEW_ENTERED: @@ -2573,6 +2517,68 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return ArrayUtils.contains(response.getIgnoredIds(), id); } + @GuardedBy("mLock") + private void logIfViewClearedLocked(AutofillId id, AutofillValue value, ViewState viewState) { + if ((value == null || value.isEmpty()) + && viewState.getCurrentValue() != null + && viewState.getCurrentValue().isText() + && viewState.getCurrentValue().getTextValue() != null + && getSaveInfoLocked() != null) { + final int length = viewState.getCurrentValue().getTextValue().length(); + if (sDebug) { + Slog.d(TAG, "updateLocked(" + id + "): resetting value that was " + + length + " chars long"); + } + final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); + mMetricsLogger.write(log); + } + } + + @GuardedBy("mLock") + private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value, + 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)) { + // When the update is caused by autofilling the view, just update the + // value, not the UI. + if (sVerbose) { + Slog.v(TAG, "ignoring autofilled change on id " + id); + } + viewState.resetState(ViewState.STATE_CHANGED); + return; + } else if ((viewState.id.equals(this.mCurrentViewId)) + && (viewState.getState() & ViewState.STATE_AUTOFILLED) != 0) { + // Remove autofilled state once field is changed after autofilling. + if (sVerbose) { + Slog.v(TAG, "field changed after autofill on id " + id); + } + viewState.resetState(ViewState.STATE_AUTOFILLED); + 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); + getUiForShowing().filterFillUi(filterText, this); + } + @Override public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId, @Nullable AutofillValue value) { @@ -2602,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); @@ -2645,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"); @@ -2663,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); diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index 84886f83027d..f7c24f080fa4 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -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; diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 57961423061f..5dc43ef8ad56 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -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 diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index 95a4a191a52e..5f6e47b04113 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -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, |