diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 553e3c8c2d1b5..3112039c36d8f 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -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 { diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 87d65c223c256..f8ccea5d83566 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -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. diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java index 23bb9d63d6fad..e2330ca6ffe9f 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java @@ -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(); + } } /** diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 37ed6f790c19b..ffdb9c83c8057 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -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.deleteInlineFillUiLocked(viewState.id); - } else { - mInlineSessionController.filterInlineFillUiLocked(mCurrentViewId, filterText); + mInlineSessionController.disableFilterMatching(viewState.id); } + 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; diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java index 2713a01ff3d12..a3d0fb955da42 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java @@ -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. */