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