Skip to content

Commit 974c311

Browse files
committed
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
1 parent 5958828 commit 974c311

5 files changed

Lines changed: 112 additions & 28 deletions

File tree

core/java/android/view/autofill/AutofillManager.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2590,8 +2590,26 @@ void notifyReenableAutofill() {
25902590

25912591
private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
25922592
if (sVerbose) {
2593-
Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
2594-
+ ", sessionFinishedState=" + sessionFinishedState);
2593+
Log.v(TAG, "notifyNoFillUi(): sessionFinishedState=" + sessionFinishedState);
2594+
}
2595+
final View anchor = findView(id);
2596+
if (anchor == null) {
2597+
return;
2598+
}
2599+
2600+
notifyCallback(sessionId, id, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
2601+
2602+
if (sessionFinishedState != STATE_UNKNOWN) {
2603+
// Callback call was "hijacked" to also update the session state.
2604+
setSessionFinished(sessionFinishedState, /* autofillableIds= */ null);
2605+
}
2606+
}
2607+
2608+
private void notifyCallback(
2609+
int sessionId, AutofillId id, @AutofillCallback.AutofillEventType int event) {
2610+
if (sVerbose) {
2611+
Log.v(TAG, "notifyCallback(): sessionId=" + sessionId + ", autofillId=" + id
2612+
+ ", event=" + event);
25952613
}
25962614
final View anchor = findView(id);
25972615
if (anchor == null) {
@@ -2607,17 +2625,12 @@ private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedSta
26072625

26082626
if (callback != null) {
26092627
if (id.isVirtualInt()) {
2610-
callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
2611-
AutofillCallback.EVENT_INPUT_UNAVAILABLE);
2628+
callback.onAutofillEvent(
2629+
anchor, id.getVirtualChildIntId(), event);
26122630
} else {
2613-
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
2631+
callback.onAutofillEvent(anchor, event);
26142632
}
26152633
}
2616-
2617-
if (sessionFinishedState != STATE_UNKNOWN) {
2618-
// Callback call was "hijacked" to also update the session state.
2619-
setSessionFinished(sessionFinishedState, /* autofillableIds= */ null);
2620-
}
26212634
}
26222635

26232636
/**
@@ -3367,6 +3380,26 @@ public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedStat
33673380
}
33683381
}
33693382

3383+
@Override
3384+
public void notifyFillUiShown(int sessionId, AutofillId id) {
3385+
final AutofillManager afm = mAfm.get();
3386+
if (afm != null) {
3387+
afm.post(
3388+
() -> afm.notifyCallback(
3389+
sessionId, id, AutofillCallback.EVENT_INPUT_SHOWN));
3390+
}
3391+
}
3392+
3393+
@Override
3394+
public void notifyFillUiHidden(int sessionId, AutofillId id) {
3395+
final AutofillManager afm = mAfm.get();
3396+
if (afm != null) {
3397+
afm.post(
3398+
() -> afm.notifyCallback(
3399+
sessionId, id, AutofillCallback.EVENT_INPUT_HIDDEN));
3400+
}
3401+
}
3402+
33703403
@Override
33713404
public void notifyDisableAutofill(long disableDuration, ComponentName componentName)
33723405
throws RemoteException {

core/java/android/view/autofill/IAutoFillManagerClient.aidl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ oneway interface IAutoFillManagerClient {
7878
*/
7979
void notifyNoFillUi(int sessionId, in AutofillId id, int sessionFinishedState);
8080

81+
/**
82+
* Notifies that the fill UI was shown by the system (e.g. as inline chips in the keyboard).
83+
*/
84+
void notifyFillUiShown(int sessionId, in AutofillId id);
85+
86+
/**
87+
* Notifies that the fill UI previously shown by the system has been hidden by the system.
88+
*
89+
* @see #notifyFillUiShown
90+
*/
91+
void notifyFillUiHidden(int sessionId, in AutofillId id);
92+
8193
/**
8294
* Dispatches unhandled keyevent from autofill ui. Autofill ui handles DPAD and ENTER events,
8395
* other unhandled keyevents are dispatched to app's window to filter autofill result.

services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,14 @@ boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) {
117117
}
118118

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

129130
/**

services/autofill/java/com/android/server/autofill/Session.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,6 +2698,11 @@ private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillVal
26982698
// TODO(b/156099633): remove this once framework gets out of business of resending
26992699
// inline suggestions when IME visibility changes.
27002700
mInlineSessionController.hideInlineSuggestionsUiLocked(viewState.id);
2701+
try {
2702+
mClient.notifyFillUiHidden(this.id, viewState.id);
2703+
} catch (RemoteException e) {
2704+
Slog.e(TAG, "Error requesting to hide fill UI", e);
2705+
}
27012706
viewState.resetState(ViewState.STATE_CHANGED);
27022707
return;
27032708
} else if ((viewState.id.equals(this.mCurrentViewId))
@@ -2713,20 +2718,20 @@ private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillVal
27132718
} else if (viewState.id.equals(this.mCurrentViewId)
27142719
&& (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
27152720
if ((viewState.getState() & ViewState.STATE_INLINE_DISABLED) != 0) {
2716-
final FillResponse response = viewState.getResponse();
2717-
if (response != null) {
2718-
response.getDatasets().clear();
2719-
}
2720-
mInlineSessionController.deleteInlineFillUiLocked(viewState.id);
2721-
} else {
2722-
mInlineSessionController.filterInlineFillUiLocked(mCurrentViewId, filterText);
2721+
mInlineSessionController.disableFilterMatching(viewState.id);
27232722
}
2723+
mInlineSessionController.filterInlineFillUiLocked(mCurrentViewId, filterText);
27242724
} else if (viewState.id.equals(this.mCurrentViewId)
27252725
&& (viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
27262726
if (!TextUtils.isEmpty(filterText)) {
27272727
// TODO: we should be able to replace this with controller#filterInlineFillUiLocked
27282728
// to accomplish filtering for augmented autofill.
27292729
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
2730+
try {
2731+
mClient.notifyFillUiHidden(this.id, mCurrentViewId);
2732+
} catch (RemoteException e) {
2733+
Slog.e(TAG, "Error sending fill UI hidden notification", e);
2734+
}
27302735
}
27312736
}
27322737

@@ -2812,6 +2817,11 @@ public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId fill
28122817
if (requestShowInlineSuggestionsLocked(response, filterText)) {
28132818
final ViewState currentView = mViewStates.get(mCurrentViewId);
28142819
currentView.setState(ViewState.STATE_INLINE_SHOWN);
2820+
try {
2821+
mClient.notifyFillUiShown(this.id, mCurrentViewId);
2822+
} catch (RemoteException e) {
2823+
Slog.e(TAG, "Error sending fill UI shown notification", e);
2824+
}
28152825
//TODO(b/137800469): Fix it to log showed only when IME asks for inflation,
28162826
// rather than here where framework sends back the response.
28172827
mService.logDatasetShown(id, mClientState);
@@ -2882,6 +2892,11 @@ private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse respons
28822892
synchronized (mLock) {
28832893
mInlineSessionController.hideInlineSuggestionsUiLocked(
28842894
focusedId);
2895+
try {
2896+
mClient.notifyFillUiHidden(this.id, focusedId);
2897+
} catch (RemoteException e) {
2898+
Slog.e(TAG, "Error sending fill UI hidden notification", e);
2899+
}
28852900
}
28862901
}, remoteRenderService);
28872902
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
@@ -3393,6 +3408,11 @@ void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generate
33933408
}
33943409
if (mCurrentViewId != null) {
33953410
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
3411+
try {
3412+
mClient.notifyFillUiHidden(this.id, mCurrentViewId);
3413+
} catch (RemoteException e) {
3414+
Slog.e(TAG, "Error sending fill UI hidden notification", e);
3415+
}
33963416
}
33973417
autoFillApp(dataset);
33983418
return;

services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ public final class InlineFillUi {
8383
@Nullable
8484
private String mFilterText;
8585

86+
/**
87+
* Whether prefix/regex based filtering is disabled.
88+
*/
89+
private boolean mFilterMatchingDisabled;
90+
8691
/**
8792
* Returns an empty inline autofill UI.
8893
*/
@@ -199,7 +204,7 @@ public InlineSuggestionsResponse getInlineSuggestionsResponse() {
199204
continue;
200205
}
201206
if (!inlinePresentation.isPinned() // don't filter pinned suggestions
202-
&& !includeDataset(dataset, fieldIndex, mFilterText)) {
207+
&& !includeDataset(dataset, fieldIndex)) {
203208
continue;
204209
}
205210
inlineSuggestions.add(copy(i, mInlineSuggestions.get(i)));
@@ -235,14 +240,13 @@ private InlineSuggestion copy(int index, @NonNull InlineSuggestion inlineSuggest
235240
}
236241

237242
// TODO: Extract the shared filtering logic here and in FillUi to a common method.
238-
private static boolean includeDataset(Dataset dataset, int fieldIndex,
239-
@Nullable String filterText) {
243+
private boolean includeDataset(Dataset dataset, int fieldIndex) {
240244
// Show everything when the user input is empty.
241-
if (TextUtils.isEmpty(filterText)) {
245+
if (TextUtils.isEmpty(mFilterText)) {
242246
return true;
243247
}
244248

245-
final String constraintLowerCase = filterText.toString().toLowerCase();
249+
final String constraintLowerCase = mFilterText.toString().toLowerCase();
246250

247251
// Use the filter provided by the service, if available.
248252
final Dataset.DatasetFieldFilter filter = dataset.getFilter(fieldIndex);
@@ -252,7 +256,10 @@ private static boolean includeDataset(Dataset dataset, int fieldIndex,
252256
if (sVerbose) {
253257
Slog.v(TAG, "Explicitly disabling filter for dataset id" + dataset.getId());
254258
}
255-
return true;
259+
return false;
260+
}
261+
if (mFilterMatchingDisabled) {
262+
return false;
256263
}
257264
return filterPattern.matcher(constraintLowerCase).matches();
258265
}
@@ -261,10 +268,21 @@ private static boolean includeDataset(Dataset dataset, int fieldIndex,
261268
if (value == null || !value.isText()) {
262269
return dataset.getAuthentication() == null;
263270
}
271+
if (mFilterMatchingDisabled) {
272+
return false;
273+
}
264274
final String valueText = value.getTextValue().toString().toLowerCase();
265275
return valueText.toLowerCase().startsWith(constraintLowerCase);
266276
}
267277

278+
/**
279+
* Disables prefix/regex based filtering. Other filtering rules (see {@link
280+
* android.service.autofill.Dataset}) still apply.
281+
*/
282+
public void disableFilterMatching() {
283+
mFilterMatchingDisabled = true;
284+
}
285+
268286
/**
269287
* Callback from the inline suggestion Ui.
270288
*/

0 commit comments

Comments
 (0)