Loading core/java/android/view/autofill/AutofillManager.java +43 −10 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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); } } /** Loading Loading @@ -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 { Loading core/java/android/view/autofill/IAutoFillManagerClient.aidl +12 −0 Original line number Diff line number Diff line Loading @@ -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. Loading services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java +6 −5 Original line number Diff line number Diff line Loading @@ -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(); } } /** Loading services/autofill/java/com/android/server/autofill/Session.java +27 −7 Original line number Diff line number Diff line Loading @@ -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)) Loading @@ -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); } } } Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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; Loading services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java +24 −6 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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))); Loading Loading @@ -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); Loading @@ -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(); } Loading @@ -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. */ Loading Loading
core/java/android/view/autofill/AutofillManager.java +43 −10 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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); } } /** Loading Loading @@ -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 { Loading
core/java/android/view/autofill/IAutoFillManagerClient.aidl +12 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java +6 −5 Original line number Diff line number Diff line Loading @@ -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(); } } /** Loading
services/autofill/java/com/android/server/autofill/Session.java +27 −7 Original line number Diff line number Diff line Loading @@ -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)) Loading @@ -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); } } } Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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; Loading
services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java +24 −6 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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))); Loading Loading @@ -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); Loading @@ -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(); } Loading @@ -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. */ Loading