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

Commit 974c3114 authored by Ahaan Ugale's avatar Ahaan Ugale
Browse files

AF Inline: Notify the registered AutofillCallback, also fix filtering.

This is missing notifications on the suggestions being hidden/shown
while filtering, but that is a bit harder to solve as we have to pipe
this state back from AutofillInlineSessionController. (although, I'm not
certain we even want this behavior, however it is inconsistent with the
dropdown behavior for now)

See bugs linked for what is fixed for filtering (some are not marked as
fixed as they still might need cts tests).
The main change is to make the privacy protection mechanism a bit
smarter - it now only applies to text-matching based logic.

Fix: 157763435
Fix: 156930859
Bug: 155517211
Bug: 157762527
Test: atest CtsAutoFillServiceTestCases:DatasetFilteringInlineTest \
  CtsAutoFillServiceTestCases:InlineFilteringTest \
  CtsAutoFillServiceTestCases:DatasetFilteringDropdownTest
Test: atest android.autofillservice.cts.inline
Change-Id: Icf94e21ba0df3b15a32454038772967cc1f6da79
parent 5958828b
Loading
Loading
Loading
Loading
+43 −10
Original line number Diff line number Diff line
@@ -2590,8 +2590,26 @@ public final class AutofillManager {

    private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
        if (sVerbose) {
            Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
                    + ", sessionFinishedState=" + sessionFinishedState);
            Log.v(TAG, "notifyNoFillUi(): sessionFinishedState=" + sessionFinishedState);
        }
        final View anchor = findView(id);
        if (anchor == null) {
            return;
        }

        notifyCallback(sessionId, id, AutofillCallback.EVENT_INPUT_UNAVAILABLE);

        if (sessionFinishedState != STATE_UNKNOWN) {
            // Callback call was "hijacked" to also update the session state.
            setSessionFinished(sessionFinishedState, /* autofillableIds= */ null);
        }
    }

    private void notifyCallback(
            int sessionId, AutofillId id, @AutofillCallback.AutofillEventType int event) {
        if (sVerbose) {
            Log.v(TAG, "notifyCallback(): sessionId=" + sessionId + ", autofillId=" + id
                    + ", event=" + event);
        }
        final View anchor = findView(id);
        if (anchor == null) {
@@ -2607,17 +2625,12 @@ public final class AutofillManager {

        if (callback != null) {
            if (id.isVirtualInt()) {
                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
                        AutofillCallback.EVENT_INPUT_UNAVAILABLE);
                callback.onAutofillEvent(
                        anchor, id.getVirtualChildIntId(), event);
            } else {
                callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
                callback.onAutofillEvent(anchor, event);
            }
        }

        if (sessionFinishedState != STATE_UNKNOWN) {
            // Callback call was "hijacked" to also update the session state.
            setSessionFinished(sessionFinishedState, /* autofillableIds= */ null);
        }
    }

    /**
@@ -3367,6 +3380,26 @@ public final class AutofillManager {
            }
        }

        @Override
        public void notifyFillUiShown(int sessionId, AutofillId id) {
            final AutofillManager afm = mAfm.get();
            if (afm != null) {
                afm.post(
                        () -> afm.notifyCallback(
                                sessionId, id, AutofillCallback.EVENT_INPUT_SHOWN));
            }
        }

        @Override
        public void notifyFillUiHidden(int sessionId, AutofillId id) {
            final AutofillManager afm = mAfm.get();
            if (afm != null) {
                afm.post(
                        () -> afm.notifyCallback(
                                sessionId, id, AutofillCallback.EVENT_INPUT_HIDDEN));
            }
        }

        @Override
        public void notifyDisableAutofill(long disableDuration, ComponentName componentName)
                throws RemoteException {
+12 −0
Original line number Diff line number Diff line
@@ -78,6 +78,18 @@ oneway interface IAutoFillManagerClient {
     */
    void notifyNoFillUi(int sessionId, in AutofillId id, int sessionFinishedState);

    /**
     * Notifies that the fill UI was shown by the system (e.g. as inline chips in the keyboard).
     */
    void notifyFillUiShown(int sessionId, in AutofillId id);

    /**
     * Notifies that the fill UI previously shown by the system has been hidden by the system.
     *
     * @see #notifyFillUiShown
     */
    void notifyFillUiHidden(int sessionId, in AutofillId id);

    /**
     * Dispatches unhandled keyevent from autofill ui. Autofill ui handles DPAD and ENTER events,
     * other unhandled keyevents are dispatched to app's window to filter autofill result.
+6 −5
Original line number Diff line number Diff line
@@ -117,13 +117,14 @@ final class AutofillInlineSessionController {
    }

    /**
     * Permanently delete the current inline fill UI. Notify the IME to hide the suggestions as
     * well.
     * Disables prefix/regex based filtering. Other filtering rules (see {@link
     * android.service.autofill.Dataset}) still apply.
     */
    @GuardedBy("mLock")
    boolean deleteInlineFillUiLocked(@NonNull AutofillId autofillId) {
        mInlineFillUi = null;
        return hideInlineSuggestionsUiLocked(autofillId);
    void disableFilterMatching(@NonNull AutofillId autofillId) {
        if (mInlineFillUi != null && mInlineFillUi.getAutofillId().equals(autofillId)) {
            mInlineFillUi.disableFilterMatching();
        }
    }

    /**
+27 −7
Original line number Diff line number Diff line
@@ -2698,6 +2698,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                // TODO(b/156099633): remove this once framework gets out of business of resending
                // inline suggestions when IME visibility changes.
                mInlineSessionController.hideInlineSuggestionsUiLocked(viewState.id);
                try {
                    mClient.notifyFillUiHidden(this.id, viewState.id);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Error requesting to hide fill UI", e);
                }
                viewState.resetState(ViewState.STATE_CHANGED);
                return;
            } else if ((viewState.id.equals(this.mCurrentViewId))
@@ -2713,20 +2718,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        } else if (viewState.id.equals(this.mCurrentViewId)
                && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
            if ((viewState.getState() & ViewState.STATE_INLINE_DISABLED) != 0) {
                final FillResponse response = viewState.getResponse();
                if (response != null) {
                    response.getDatasets().clear();
                mInlineSessionController.disableFilterMatching(viewState.id);
            }
                mInlineSessionController.deleteInlineFillUiLocked(viewState.id);
            } else {
            mInlineSessionController.filterInlineFillUiLocked(mCurrentViewId, filterText);
            }
        } else if (viewState.id.equals(this.mCurrentViewId)
                && (viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
            if (!TextUtils.isEmpty(filterText)) {
                // TODO: we should be able to replace this with controller#filterInlineFillUiLocked
                // to accomplish filtering for augmented autofill.
                mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
                try {
                    mClient.notifyFillUiHidden(this.id, mCurrentViewId);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Error sending fill UI hidden notification", e);
                }
            }
        }

@@ -2812,6 +2817,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                if (requestShowInlineSuggestionsLocked(response, filterText)) {
                    final ViewState currentView = mViewStates.get(mCurrentViewId);
                    currentView.setState(ViewState.STATE_INLINE_SHOWN);
                    try {
                        mClient.notifyFillUiShown(this.id, mCurrentViewId);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "Error sending fill UI shown notification", e);
                    }
                    //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);
@@ -2882,6 +2892,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                    synchronized (mLock) {
                        mInlineSessionController.hideInlineSuggestionsUiLocked(
                                focusedId);
                        try {
                            mClient.notifyFillUiHidden(this.id, focusedId);
                        } catch (RemoteException e) {
                            Slog.e(TAG, "Error sending fill UI hidden notification", e);
                        }
                    }
                }, remoteRenderService);
        return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
@@ -3393,6 +3408,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                }
                if (mCurrentViewId != null) {
                    mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
                    try {
                        mClient.notifyFillUiHidden(this.id, mCurrentViewId);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "Error sending fill UI hidden notification", e);
                    }
                }
                autoFillApp(dataset);
                return;
+24 −6
Original line number Diff line number Diff line
@@ -83,6 +83,11 @@ public final class InlineFillUi {
    @Nullable
    private String mFilterText;

    /**
     * Whether prefix/regex based filtering is disabled.
     */
    private boolean mFilterMatchingDisabled;

    /**
     * Returns an empty inline autofill UI.
     */
@@ -199,7 +204,7 @@ public final class InlineFillUi {
                continue;
            }
            if (!inlinePresentation.isPinned()  // don't filter pinned suggestions
                    && !includeDataset(dataset, fieldIndex, mFilterText)) {
                    && !includeDataset(dataset, fieldIndex)) {
                continue;
            }
            inlineSuggestions.add(copy(i, mInlineSuggestions.get(i)));
@@ -235,14 +240,13 @@ public final class InlineFillUi {
    }

    // 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) {
    private boolean includeDataset(Dataset dataset, int fieldIndex) {
        // Show everything when the user input is empty.
        if (TextUtils.isEmpty(filterText)) {
        if (TextUtils.isEmpty(mFilterText)) {
            return true;
        }

        final String constraintLowerCase = filterText.toString().toLowerCase();
        final String constraintLowerCase = mFilterText.toString().toLowerCase();

        // Use the filter provided by the service, if available.
        final Dataset.DatasetFieldFilter filter = dataset.getFilter(fieldIndex);
@@ -252,7 +256,10 @@ public final class InlineFillUi {
                if (sVerbose) {
                    Slog.v(TAG, "Explicitly disabling filter for dataset id" + dataset.getId());
                }
                return true;
                return false;
            }
            if (mFilterMatchingDisabled) {
                return false;
            }
            return filterPattern.matcher(constraintLowerCase).matches();
        }
@@ -261,10 +268,21 @@ public final class InlineFillUi {
        if (value == null || !value.isText()) {
            return dataset.getAuthentication() == null;
        }
        if (mFilterMatchingDisabled) {
            return false;
        }
        final String valueText = value.getTextValue().toString().toLowerCase();
        return valueText.toLowerCase().startsWith(constraintLowerCase);
    }

    /**
     * Disables prefix/regex based filtering. Other filtering rules (see {@link
     * android.service.autofill.Dataset}) still apply.
     */
    public void disableFilterMatching() {
        mFilterMatchingDisabled = true;
    }

    /**
     * Callback from the inline suggestion Ui.
     */