1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.autofill.ui;
17 
18 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
19 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU;
20 
21 import static com.android.server.autofill.Helper.sDebug;
22 import static com.android.server.autofill.Helper.sVerbose;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentSender;
30 import android.graphics.drawable.Drawable;
31 import android.metrics.LogMaker;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.os.RemoteException;
36 import android.service.autofill.Dataset;
37 import android.service.autofill.FillEventHistory;
38 import android.service.autofill.FillResponse;
39 import android.service.autofill.SaveInfo;
40 import android.service.autofill.ValueFinder;
41 import android.text.TextUtils;
42 import android.util.Slog;
43 import android.view.KeyEvent;
44 import android.view.autofill.AutofillId;
45 import android.view.autofill.AutofillManager;
46 import android.view.autofill.IAutofillWindowPresenter;
47 import android.widget.Toast;
48 
49 import com.android.internal.logging.MetricsLogger;
50 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
51 import com.android.server.LocalServices;
52 import com.android.server.UiModeManagerInternal;
53 import com.android.server.UiThread;
54 import com.android.server.autofill.Helper;
55 import com.android.server.autofill.PresentationStatsEventLogger;
56 import com.android.server.autofill.SaveEventLogger;
57 import com.android.server.utils.Slogf;
58 
59 import java.io.PrintWriter;
60 
61 /**
62  * Handles all autofill related UI tasks. The UI has two components:
63  * fill UI that shows a popup style window anchored at the focused
64  * input field for choosing a dataset to fill or trigger the response
65  * authentication flow; save UI that shows a toast style window for
66  * managing saving of user edits.
67  */
68 public final class AutoFillUI {
69     private static final String TAG = "AutofillUI";
70 
71     private final Handler mHandler = UiThread.getHandler();
72     private final @NonNull Context mContext;
73 
74     private @Nullable FillUi mFillUi;
75     private @Nullable SaveUi mSaveUi;
76     private @Nullable DialogFillUi mFillDialog;
77 
78     private @Nullable AutoFillUiCallback mCallback;
79 
80     private final MetricsLogger mMetricsLogger = new MetricsLogger();
81 
82     private final @NonNull OverlayControl mOverlayControl;
83     private final @NonNull UiModeManagerInternal mUiModeMgr;
84 
85     private @Nullable Runnable mCreateFillUiRunnable;
86     private @Nullable AutoFillUiCallback mSaveUiCallback;
87 
88     public interface AutoFillUiCallback {
authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent, @Nullable Bundle extras, int uiType)89         void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent,
90                 @Nullable Bundle extras, int uiType);
fill(int requestId, int datasetIndex, @NonNull Dataset dataset, @FillEventHistory.Event.UiType int uiType)91         void fill(int requestId, int datasetIndex, @NonNull Dataset dataset,
92                 @FillEventHistory.Event.UiType int uiType);
save()93         void save();
cancelSave()94         void cancelSave();
requestShowFillUi(AutofillId id, int width, int height, IAutofillWindowPresenter presenter)95         void requestShowFillUi(AutofillId id, int width, int height,
96                 IAutofillWindowPresenter presenter);
requestHideFillUi(AutofillId id)97         void requestHideFillUi(AutofillId id);
requestHideFillUiWhenDestroyed(AutofillId id)98         void requestHideFillUiWhenDestroyed(AutofillId id);
startIntentSenderAndFinishSession(IntentSender intentSender)99         void startIntentSenderAndFinishSession(IntentSender intentSender);
startIntentSender(IntentSender intentSender, Intent intent)100         void startIntentSender(IntentSender intentSender, Intent intent);
dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent)101         void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
cancelSession()102         void cancelSession();
requestShowSoftInput(AutofillId id)103         void requestShowSoftInput(AutofillId id);
requestFallbackFromFillDialog()104         void requestFallbackFromFillDialog();
onShown(int uiType)105         void onShown(int uiType);
106     }
107 
AutoFillUI(@onNull Context context)108     public AutoFillUI(@NonNull Context context) {
109         mContext = context;
110         mOverlayControl = new OverlayControl(context);
111         mUiModeMgr = LocalServices.getService(UiModeManagerInternal.class);
112     }
113 
setCallback(@onNull AutoFillUiCallback callback)114     public void setCallback(@NonNull AutoFillUiCallback callback) {
115         mHandler.post(() -> {
116             if (mCallback != callback) {
117                 if (mCallback != null) {
118                     if (isSaveUiShowing()) {
119                         // keeps showing the save UI
120                         hideFillUiUiThread(callback, true);
121                     } else {
122                         hideAllUiThread(mCallback);
123                     }
124                 }
125                 mCallback = callback;
126             }
127         });
128     }
129 
clearCallback(@onNull AutoFillUiCallback callback)130     public void clearCallback(@NonNull AutoFillUiCallback callback) {
131         mHandler.post(() -> {
132             if (mCallback == callback) {
133                 hideAllUiThread(callback);
134                 mCallback = null;
135             }
136         });
137     }
138 
139     /**
140      * Displays an error message to the user.
141      */
showError(int resId, @NonNull AutoFillUiCallback callback)142     public void showError(int resId, @NonNull AutoFillUiCallback callback) {
143         showError(mContext.getString(resId), callback);
144     }
145 
146     /**
147      * Displays an error message to the user.
148      */
showError(@ullable CharSequence message, @NonNull AutoFillUiCallback callback)149     public void showError(@Nullable CharSequence message, @NonNull AutoFillUiCallback callback) {
150         Slog.w(TAG, "showError(): " + message);
151 
152         mHandler.post(() -> {
153             if (mCallback != callback) {
154                 return;
155             }
156             hideAllUiThread(callback);
157             if (!TextUtils.isEmpty(message)) {
158                 Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
159             }
160         });
161     }
162 
163     /**
164      * Hides the fill UI.
165      */
hideFillUi(@onNull AutoFillUiCallback callback)166     public void hideFillUi(@NonNull AutoFillUiCallback callback) {
167         mHandler.post(() -> hideFillUiUiThread(callback, true));
168     }
169 
170     /**
171      * Hides the fill UI.
172      */
hideFillDialog(@onNull AutoFillUiCallback callback)173     public void hideFillDialog(@NonNull AutoFillUiCallback callback) {
174         mHandler.post(() -> hideFillDialogUiThread(callback));
175     }
176     /**
177      * Filters the options in the fill UI.
178      *
179      * @param filterText The filter prefix.
180      */
filterFillUi(@ullable String filterText, @NonNull AutoFillUiCallback callback)181     public void filterFillUi(@Nullable String filterText, @NonNull AutoFillUiCallback callback) {
182         mHandler.post(() -> {
183             if (callback != mCallback) {
184                 return;
185             }
186             if (mFillUi != null) {
187                 mFillUi.setFilterText(filterText);
188             }
189         });
190     }
191 
192     /**
193      * Shows the fill UI, removing the previous fill UI if the has changed.
194      *
195      * @param focusedId the currently focused field
196      * @param response the current fill response
197      * @param filterText text of the view to be filled
198      * @param servicePackageName package name of the autofill service filling the activity
199      * @param componentName component name of the activity that is filled
200      * @param serviceLabel label of autofill service
201      * @param serviceIcon icon of autofill service
202      * @param callback identifier for the caller
203      * @param userId the user associated wit the session
204      * @param context context with the proper state (like display id) to show the UI
205      * @param sessionId id of the autofill session
206      * @param compatMode whether the app is being autofilled in compatibility mode.
207      */
showFillUi(@onNull AutofillId focusedId, @NonNull FillResponse response, @Nullable String filterText, @Nullable String servicePackageName, @NonNull ComponentName componentName, @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, @NonNull Context context, int sessionId, boolean compatMode)208     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
209             @Nullable String filterText, @Nullable String servicePackageName,
210             @NonNull ComponentName componentName, @NonNull CharSequence serviceLabel,
211             @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback,
212             @NonNull Context context, int sessionId, boolean compatMode) {
213         if (sDebug) {
214             final int size = filterText == null ? 0 : filterText.length();
215             Slogf.d(TAG, "showFillUi(): id=%s, filter=%d chars, displayId=%d", focusedId, size,
216                     context.getDisplayId());
217         }
218         final LogMaker log = Helper
219                 .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
220                         sessionId, compatMode)
221                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
222                         filterText == null ? 0 : filterText.length())
223                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
224                         response.getDatasets() == null ? 0 : response.getDatasets().size());
225 
226         final Runnable createFillUiRunnable = () -> {
227             if (callback != mCallback) {
228                 return;
229             }
230             hideAllUiThread(callback);
231             mFillUi = new FillUi(context, response, focusedId, filterText, mOverlayControl,
232                     serviceLabel, serviceIcon, mUiModeMgr.isNightMode(), new FillUi.Callback() {
233                 @Override
234                 public void onResponsePicked(FillResponse response) {
235                     log.setType(MetricsEvent.TYPE_DETAIL);
236                     hideFillUiUiThread(callback, true);
237                     if (mCallback != null) {
238                         mCallback.authenticate(response.getRequestId(),
239                                 AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
240                                 response.getAuthentication(), response.getClientState(),
241                                 UI_TYPE_MENU);
242                     }
243                 }
244 
245                 @Override
246                 public void onShown() {
247                     if (mCallback != null) {
248                         mCallback.onShown(UI_TYPE_MENU);
249                     }
250                 }
251 
252                 @Override
253                 public void onDatasetPicked(Dataset dataset) {
254                     log.setType(MetricsEvent.TYPE_ACTION);
255                     hideFillUiUiThread(callback, true);
256                     if (mCallback != null) {
257                         final int datasetIndex = response.getDatasets().indexOf(dataset);
258                         mCallback.fill(response.getRequestId(), datasetIndex,
259                                 dataset, UI_TYPE_MENU);
260                     }
261                 }
262 
263                 @Override
264                 public void onCanceled() {
265                     log.setType(MetricsEvent.TYPE_DISMISS);
266                     hideFillUiUiThread(callback, true);
267                 }
268 
269                 @Override
270                 public void onDestroy() {
271                     if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
272                         log.setType(MetricsEvent.TYPE_CLOSE);
273                     }
274                     mMetricsLogger.write(log);
275                 }
276 
277                 @Override
278                 public void requestShowFillUi(int width, int height,
279                         IAutofillWindowPresenter windowPresenter) {
280                     if (mCallback != null) {
281                         mCallback.requestShowFillUi(focusedId, width, height, windowPresenter);
282                     }
283                 }
284 
285                 @Override
286                 public void requestHideFillUi() {
287                     if (mCallback != null) {
288                         mCallback.requestHideFillUi(focusedId);
289                     }
290                 }
291 
292                 @Override
293                 public void requestHideFillUiWhenDestroyed() {
294                     if (mCallback != null) {
295                         mCallback.requestHideFillUiWhenDestroyed(focusedId);
296                     }
297                 }
298 
299                 @Override
300                 public void startIntentSender(IntentSender intentSender) {
301                     if (mCallback != null) {
302                         mCallback.startIntentSenderAndFinishSession(intentSender);
303                     }
304                 }
305 
306                 @Override
307                 public void dispatchUnhandledKey(KeyEvent keyEvent) {
308                     if (mCallback != null) {
309                         mCallback.dispatchUnhandledKey(focusedId, keyEvent);
310                     }
311                 }
312 
313                 @Override
314                 public void cancelSession() {
315                     if (mCallback != null) {
316                         mCallback.cancelSession();
317                     }
318                 }
319             });
320         };
321 
322         if (isSaveUiShowing()) {
323             // postpone creating the fill UI for showing the save UI
324             if (sDebug) Slog.d(TAG, "postpone fill UI request..");
325             mCreateFillUiRunnable = createFillUiRunnable;
326         } else {
327             mHandler.post(createFillUiRunnable);
328         }
329     }
330 
331     /**
332      * Shows the UI asking the user to save for autofill.
333      */
showSaveUi(@onNull CharSequence serviceLabel, @NonNull Drawable serviceIcon, @Nullable String servicePackageName, @NonNull SaveInfo info, @NonNull ValueFinder valueFinder, @NonNull ComponentName componentName, @NonNull AutoFillUiCallback callback, @NonNull Context context, @NonNull PendingUi pendingSaveUi, boolean isUpdate, boolean compatMode, boolean showServiceIcon, @Nullable SaveEventLogger mSaveEventLogger)334     public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
335             @Nullable String servicePackageName, @NonNull SaveInfo info,
336             @NonNull ValueFinder valueFinder, @NonNull ComponentName componentName,
337             @NonNull AutoFillUiCallback callback, @NonNull Context context,
338             @NonNull PendingUi pendingSaveUi, boolean isUpdate, boolean compatMode,
339             boolean showServiceIcon, @Nullable SaveEventLogger mSaveEventLogger) {
340         if (sVerbose) {
341             Slogf.v(TAG, "showSaveUi(update=%b) for %s and display %d: %s", isUpdate,
342                     componentName.toShortString(), context.getDisplayId(), info);
343         }
344         int numIds = 0;
345         numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
346         numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
347 
348         final LogMaker log = Helper
349                 .newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, componentName, servicePackageName,
350                         pendingSaveUi.sessionId, compatMode)
351                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
352         if (isUpdate) {
353             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_UPDATE, 1);
354         }
355 
356         mHandler.post(() -> {
357             if (callback != mCallback) {
358                 return;
359             }
360             hideAllUiThread(callback);
361             mSaveUiCallback = callback;
362             mSaveUi = new SaveUi(context, pendingSaveUi, serviceLabel, serviceIcon,
363                     servicePackageName, componentName, info, valueFinder, mOverlayControl,
364                     new SaveUi.OnSaveListener() {
365                 @Override
366                 public void onSave() {
367                     log.setType(MetricsEvent.TYPE_ACTION);
368                     if (mSaveEventLogger != null) {
369                         mSaveEventLogger.maybeSetSaveButtonClicked(true);
370                     }
371                     hideSaveUiUiThread(callback);
372                     callback.save();
373                     destroySaveUiUiThread(pendingSaveUi, true);
374                 }
375 
376                 @Override
377                 public void onCancel(IntentSender listener) {
378                     log.setType(MetricsEvent.TYPE_DISMISS);
379                     if (mSaveEventLogger != null) {
380                         mSaveEventLogger.maybeSetCancelButtonClicked(true);
381                     }
382                     hideSaveUiUiThread(callback);
383                     if (listener != null) {
384                         try {
385                             listener.sendIntent(mContext, 0, null, null, null);
386                         } catch (IntentSender.SendIntentException e) {
387                             Slog.e(TAG, "Error starting negative action listener: "
388                                     + listener, e);
389                         }
390                     }
391                     callback.cancelSave();
392                     destroySaveUiUiThread(pendingSaveUi, true);
393                 }
394 
395                 @Override
396                 public void onDestroy() {
397                     if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
398                         log.setType(MetricsEvent.TYPE_CLOSE);
399 
400                         callback.cancelSave();
401                     }
402                     mMetricsLogger.write(log);
403                     if (mSaveEventLogger != null) {
404                         mSaveEventLogger.maybeSetDialogDismissed(true);
405                     }
406                 }
407 
408                 @Override
409                 public void startIntentSender(IntentSender intentSender, Intent intent) {
410                     callback.startIntentSender(intentSender, intent);
411                 }
412             }, mUiModeMgr.isNightMode(), isUpdate, compatMode, showServiceIcon);
413         });
414     }
415 
416     /**
417      * Shows the UI asking the user to choose for autofill.
418      */
showFillDialog(@onNull AutofillId focusedId, @NonNull FillResponse response, @Nullable String filterText, @Nullable String servicePackageName, @NonNull ComponentName componentName, @Nullable Drawable serviceIcon, @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode, @Nullable PresentationStatsEventLogger mPresentationStatsEventLogger)419     public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
420             @Nullable String filterText, @Nullable String servicePackageName,
421             @NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
422             @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode,
423             @Nullable PresentationStatsEventLogger mPresentationStatsEventLogger) {
424         if (sVerbose) {
425             Slog.v(TAG, "showFillDialog for "
426                     + componentName.toShortString() + ": " + response);
427         }
428 
429         final LogMaker log = Helper
430                 .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
431                         sessionId, compatMode)
432                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
433                         filterText == null ? 0 : filterText.length())
434                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
435                         response.getDatasets() == null ? 0 : response.getDatasets().size());
436 
437         mHandler.post(() -> {
438             if (callback != mCallback) {
439                 return;
440             }
441             hideAllUiThread(callback);
442             mFillDialog = new DialogFillUi(mContext, response, focusedId, filterText,
443                     serviceIcon, servicePackageName, componentName, mOverlayControl,
444                     mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
445                         @Override
446                         public void onResponsePicked(FillResponse response) {
447                             log(MetricsEvent.TYPE_DETAIL);
448                             hideFillDialogUiThread(callback);
449                             if (mCallback != null) {
450                                 mCallback.authenticate(response.getRequestId(),
451                                         AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
452                                         response.getAuthentication(), response.getClientState(),
453                                         UI_TYPE_DIALOG);
454                             }
455                         }
456 
457                         @Override
458                         public void onShown() {
459                             callback.onShown(UI_TYPE_DIALOG);
460                         }
461 
462                         @Override
463                         public void onDatasetPicked(Dataset dataset) {
464                             log(MetricsEvent.TYPE_ACTION);
465                             if (mPresentationStatsEventLogger != null) {
466                                 mPresentationStatsEventLogger.maybeSetPositiveCtaButtonClicked(
467                                     true);
468                             }
469                             hideFillDialogUiThread(callback);
470                             if (mCallback != null) {
471                                 final int datasetIndex = response.getDatasets().indexOf(dataset);
472                                 mCallback.fill(response.getRequestId(), datasetIndex, dataset,
473                                         UI_TYPE_DIALOG);
474                             }
475                         }
476 
477                         @Override
478                         public void onDismissed() {
479                             log(MetricsEvent.TYPE_DISMISS);
480                             if (mPresentationStatsEventLogger != null) {
481                                 mPresentationStatsEventLogger.maybeSetDialogDismissed(true);
482                             }
483                             hideFillDialogUiThread(callback);
484                             callback.requestShowSoftInput(focusedId);
485                             callback.requestFallbackFromFillDialog();
486                         }
487 
488                         @Override
489                         public void onCanceled() {
490                             log(MetricsEvent.TYPE_CLOSE);
491                             if (mPresentationStatsEventLogger != null) {
492                                 mPresentationStatsEventLogger.maybeSetNegativeCtaButtonClicked(
493                                     true);
494                             }
495                             hideFillDialogUiThread(callback);
496                             callback.requestShowSoftInput(focusedId);
497                             callback.requestFallbackFromFillDialog();
498                         }
499 
500                         @Override
501                         public void startIntentSender(IntentSender intentSender) {
502                             mCallback.startIntentSenderAndFinishSession(intentSender);
503                         }
504 
505                         private void log(int type) {
506                             log.setType(type);
507                             mMetricsLogger.write(log);
508                         }
509                     });
510         });
511     }
512 
513     /**
514      * Executes an operation in the pending save UI, if any.
515      */
onPendingSaveUi(int operation, @NonNull IBinder token)516     public void onPendingSaveUi(int operation, @NonNull IBinder token) {
517         mHandler.post(() -> {
518             if (mSaveUi != null) {
519                 mSaveUi.onPendingUi(operation, token);
520             } else {
521                 Slog.w(TAG, "onPendingSaveUi(" + operation + "): no save ui");
522             }
523         });
524     }
525 
526     /**
527      * Hides all autofill UIs.
528      */
hideAll(@ullable AutoFillUiCallback callback)529     public void hideAll(@Nullable AutoFillUiCallback callback) {
530         mHandler.post(() -> hideAllUiThread(callback));
531     }
532 
533     /**
534      * Destroy all autofill UIs.
535      */
destroyAll(@ullable PendingUi pendingSaveUi, @Nullable AutoFillUiCallback callback, boolean notifyClient)536     public void destroyAll(@Nullable PendingUi pendingSaveUi,
537             @Nullable AutoFillUiCallback callback, boolean notifyClient) {
538         mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback, notifyClient));
539     }
540 
isSaveUiShowing()541     public boolean isSaveUiShowing() {
542         return mSaveUi == null ? false : mSaveUi.isShowing();
543     }
544 
isFillDialogShowing()545     public boolean isFillDialogShowing() {
546         return mFillDialog == null ? false : mFillDialog.isShowing();
547     }
548 
dump(PrintWriter pw)549     public void dump(PrintWriter pw) {
550         pw.println("Autofill UI");
551         final String prefix = "  ";
552         final String prefix2 = "    ";
553         pw.print(prefix); pw.print("Night mode: "); pw.println(mUiModeMgr.isNightMode());
554         if (mFillUi != null) {
555             pw.print(prefix); pw.println("showsFillUi: true");
556             mFillUi.dump(pw, prefix2);
557         } else {
558             pw.print(prefix); pw.println("showsFillUi: false");
559         }
560         if (mSaveUi != null) {
561             pw.print(prefix); pw.println("showsSaveUi: true");
562             mSaveUi.dump(pw, prefix2);
563         } else {
564             pw.print(prefix); pw.println("showsSaveUi: false");
565         }
566         if (mFillDialog != null) {
567             pw.print(prefix); pw.println("showsFillDialog: true");
568             mFillDialog.dump(pw, prefix2);
569         } else {
570             pw.print(prefix); pw.println("showsFillDialog: false");
571         }
572     }
573 
574     @android.annotation.UiThread
hideFillUiUiThread(@ullable AutoFillUiCallback callback, boolean notifyClient)575     private void hideFillUiUiThread(@Nullable AutoFillUiCallback callback, boolean notifyClient) {
576         if (mFillUi != null && (callback == null || callback == mCallback)) {
577             mFillUi.destroy(notifyClient);
578             mFillUi = null;
579         }
580     }
581 
582     @android.annotation.UiThread
583     @Nullable
hideSaveUiUiThread(@ullable AutoFillUiCallback callback)584     private PendingUi hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) {
585         if (sVerbose) {
586             Slog.v(TAG, "hideSaveUiUiThread(): mSaveUi=" + mSaveUi + ", callback=" + callback
587                     + ", mCallback=" + mCallback);
588         }
589 
590         if (mSaveUi != null && mSaveUiCallback == callback) {
591             return mSaveUi.hide();
592         }
593         return null;
594     }
595 
596     @android.annotation.UiThread
hideFillDialogUiThread(@ullable AutoFillUiCallback callback)597     private void hideFillDialogUiThread(@Nullable AutoFillUiCallback callback) {
598         if (mFillDialog != null && (callback == null || callback == mCallback)) {
599             mFillDialog.destroy();
600             mFillDialog = null;
601         }
602     }
603 
604     @android.annotation.UiThread
destroySaveUiUiThread(@ullable PendingUi pendingSaveUi, boolean notifyClient)605     private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
606         if (mSaveUi == null) {
607             // Calling destroySaveUiUiThread() twice is normal - it usually happens when the
608             // first call is made after the SaveUI is hidden and the second when the session is
609             // finished.
610             if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): already destroyed");
611             return;
612         }
613 
614         if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): " + pendingSaveUi);
615         mSaveUi.destroy();
616         mSaveUi = null;
617         mSaveUiCallback = null;
618         if (pendingSaveUi != null && notifyClient) {
619             try {
620                 if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): notifying client");
621                 pendingSaveUi.client.setSaveUiState(pendingSaveUi.sessionId, false);
622             } catch (RemoteException e) {
623                 Slog.e(TAG, "Error notifying client to set save UI state to hidden: " + e);
624             }
625         }
626 
627         if (mCreateFillUiRunnable != null) {
628             if (sDebug) Slog.d(TAG, "start the pending fill UI request..");
629             mHandler.post(mCreateFillUiRunnable);
630             mCreateFillUiRunnable = null;
631         }
632     }
633 
634     @android.annotation.UiThread
destroyAllUiThread(@ullable PendingUi pendingSaveUi, @Nullable AutoFillUiCallback callback, boolean notifyClient)635     private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
636             @Nullable AutoFillUiCallback callback, boolean notifyClient) {
637         hideFillUiUiThread(callback, notifyClient);
638         hideFillDialogUiThread(callback);
639         destroySaveUiUiThread(pendingSaveUi, notifyClient);
640     }
641 
642     @android.annotation.UiThread
hideAllUiThread(@ullable AutoFillUiCallback callback)643     private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
644         hideFillUiUiThread(callback, true);
645         hideFillDialogUiThread(callback);
646         final PendingUi pendingSaveUi = hideSaveUiUiThread(callback);
647         if (pendingSaveUi != null && pendingSaveUi.getState() == PendingUi.STATE_FINISHED) {
648             if (sDebug) {
649                 Slog.d(TAG, "hideAllUiThread(): "
650                         + "destroying Save UI because pending restoration is finished");
651             }
652             destroySaveUiUiThread(pendingSaveUi, true);
653         }
654     }
655 }
656