Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit be020f68 authored by Ahaan Ugale's avatar Ahaan Ugale Committed by Android (Google) Code Review
Browse files

Merge changes I396c2cb2,If04eb5ab

* changes:
  Implement filtering for Inline Autofill.
  Some cleanup for autofill.Session.updateLocked()
parents d81c5ee3 00aa0ae7
Loading
Loading
Loading
Loading
+70 −61
Original line number Diff line number Diff line
@@ -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);
+2 −0
Original line number Diff line number Diff line
@@ -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;

+3 −0
Original line number Diff line number Diff line
@@ -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
+47 −6
Original line number Diff line number Diff line
@@ -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,