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, int datasetSize)105         void onShown(int uiType, int datasetSize);
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      * @param maxInputLengthForAutofill max user input to provide suggestion
208      */
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, int maxInputLengthForAutofill)209     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
210             @Nullable String filterText, @Nullable String servicePackageName,
211             @NonNull ComponentName componentName, @NonNull CharSequence serviceLabel,
212             @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback,
213             @NonNull Context context, int sessionId, boolean compatMode,
214             int maxInputLengthForAutofill) {
215         if (sDebug) {
216             final int size = filterText == null ? 0 : filterText.length();
217             Slogf.d(TAG, "showFillUi(): id=%s, filter=%d chars, displayId=%d", focusedId, size,
218                     context.getDisplayId());
219         }
220         final LogMaker log = Helper
221                 .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
222                         sessionId, compatMode)
223                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
224                         filterText == null ? 0 : filterText.length())
225                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
226                         response.getDatasets() == null ? 0 : response.getDatasets().size());
227 
228         final Runnable createFillUiRunnable = () -> {
229             if (callback != mCallback) {
230                 return;
231             }
232             hideAllUiThread(callback);
233             mFillUi = new FillUi(context, response, focusedId, filterText, mOverlayControl,
234                     serviceLabel, serviceIcon, mUiModeMgr.isNightMode(), maxInputLengthForAutofill,
235                     new FillUi.Callback() {
236                 @Override
237                 public void onResponsePicked(FillResponse response) {
238                     log.setType(MetricsEvent.TYPE_DETAIL);
239                     hideFillUiUiThread(callback, true);
240                     if (mCallback != null) {
241                         mCallback.authenticate(response.getRequestId(),
242                                 AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
243                                 response.getAuthentication(), response.getClientState(),
244                                 UI_TYPE_MENU);
245                     }
246                 }
247 
248                 @Override
249                 public void onShown(int datasetSize) {
250                     if (mCallback != null) {
251                         mCallback.onShown(UI_TYPE_MENU, datasetSize);
252                     }
253                 }
254 
255                 @Override
256                 public void onDatasetPicked(Dataset dataset) {
257                     log.setType(MetricsEvent.TYPE_ACTION);
258                     hideFillUiUiThread(callback, true);
259                     if (mCallback != null) {
260                         final int datasetIndex = response.getDatasets().indexOf(dataset);
261                         mCallback.fill(response.getRequestId(), datasetIndex,
262                                 dataset, UI_TYPE_MENU);
263                     }
264                 }
265 
266                 @Override
267                 public void onCanceled() {
268                     log.setType(MetricsEvent.TYPE_DISMISS);
269                     hideFillUiUiThread(callback, true);
270                 }
271 
272                 @Override
273                 public void onDestroy() {
274                     if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
275                         log.setType(MetricsEvent.TYPE_CLOSE);
276                     }
277                     mMetricsLogger.write(log);
278                 }
279 
280                 @Override
281                 public void requestShowFillUi(int width, int height,
282                         IAutofillWindowPresenter windowPresenter) {
283                     if (mCallback != null) {
284                         mCallback.requestShowFillUi(focusedId, width, height, windowPresenter);
285                     }
286                 }
287 
288                 @Override
289                 public void requestHideFillUi() {
290                     if (mCallback != null) {
291                         mCallback.requestHideFillUi(focusedId);
292                     }
293                 }
294 
295                 @Override
296                 public void requestHideFillUiWhenDestroyed() {
297                     if (mCallback != null) {
298                         mCallback.requestHideFillUiWhenDestroyed(focusedId);
299                     }
300                 }
301 
302                 @Override
303                 public void startIntentSender(IntentSender intentSender) {
304                     if (mCallback != null) {
305                         mCallback.startIntentSenderAndFinishSession(intentSender);
306                     }
307                 }
308 
309                 @Override
310                 public void dispatchUnhandledKey(KeyEvent keyEvent) {
311                     if (mCallback != null) {
312                         mCallback.dispatchUnhandledKey(focusedId, keyEvent);
313                     }
314                 }
315 
316                 @Override
317                 public void cancelSession() {
318                     if (mCallback != null) {
319                         mCallback.cancelSession();
320                     }
321                 }
322             });
323         };
324 
325         if (isSaveUiShowing()) {
326             // postpone creating the fill UI for showing the save UI
327             if (sDebug) Slog.d(TAG, "postpone fill UI request..");
328             mCreateFillUiRunnable = createFillUiRunnable;
329         } else {
330             mHandler.post(createFillUiRunnable);
331         }
332     }
333 
334     /**
335      * Shows the UI asking the user to save for autofill.
336      */
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)337     public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
338             @Nullable String servicePackageName, @NonNull SaveInfo info,
339             @NonNull ValueFinder valueFinder, @NonNull ComponentName componentName,
340             @NonNull AutoFillUiCallback callback, @NonNull Context context,
341             @NonNull PendingUi pendingSaveUi, boolean isUpdate, boolean compatMode,
342             boolean showServiceIcon, @Nullable SaveEventLogger mSaveEventLogger) {
343         if (sVerbose) {
344             Slogf.v(TAG, "showSaveUi(update=%b) for %s and display %d: %s", isUpdate,
345                     componentName.toShortString(), context.getDisplayId(), info);
346         }
347         int numIds = 0;
348         numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
349         numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
350 
351         final LogMaker log = Helper
352                 .newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, componentName, servicePackageName,
353                         pendingSaveUi.sessionId, compatMode)
354                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
355         if (isUpdate) {
356             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_UPDATE, 1);
357         }
358 
359         mHandler.post(() -> {
360             if (callback != mCallback) {
361                 return;
362             }
363             hideAllUiThread(callback);
364             mSaveUiCallback = callback;
365             mSaveUi = new SaveUi(context, pendingSaveUi, serviceLabel, serviceIcon,
366                     servicePackageName, componentName, info, valueFinder, mOverlayControl,
367                     new SaveUi.OnSaveListener() {
368                 @Override
369                 public void onSave() {
370                     log.setType(MetricsEvent.TYPE_ACTION);
371                     if (mSaveEventLogger != null) {
372                         mSaveEventLogger.maybeSetSaveButtonClicked(true);
373                     }
374                     hideSaveUiUiThread(callback);
375                     callback.save();
376                     destroySaveUiUiThread(pendingSaveUi, true);
377                 }
378 
379                 @Override
380                 public void onCancel(IntentSender listener) {
381                     log.setType(MetricsEvent.TYPE_DISMISS);
382                     if (mSaveEventLogger != null) {
383                         mSaveEventLogger.maybeSetCancelButtonClicked(true);
384                     }
385                     hideSaveUiUiThread(callback);
386                     if (listener != null) {
387                         try {
388                             listener.sendIntent(mContext, 0, null, null, null);
389                         } catch (IntentSender.SendIntentException e) {
390                             Slog.e(TAG, "Error starting negative action listener: "
391                                     + listener, e);
392                         }
393                     }
394                     callback.cancelSave();
395                     destroySaveUiUiThread(pendingSaveUi, true);
396                 }
397 
398                 @Override
399                 public void onDestroy() {
400                     if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
401                         log.setType(MetricsEvent.TYPE_CLOSE);
402 
403                         callback.cancelSave();
404                     }
405                     mMetricsLogger.write(log);
406                     if (mSaveEventLogger != null) {
407                         mSaveEventLogger.maybeSetDialogDismissed(true);
408                     }
409                 }
410 
411                 @Override
412                 public void startIntentSender(IntentSender intentSender, Intent intent) {
413                     callback.startIntentSender(intentSender, intent);
414                 }
415             }, mUiModeMgr.isNightMode(), isUpdate, compatMode, showServiceIcon);
416 
417             mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis();
418         });
419     }
420 
421     /**
422      * Shows the UI asking the user to choose for autofill.
423      */
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 presentationStatsEventLogger, @NonNull Object sessionLock)424     public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
425             @Nullable String filterText, @Nullable String servicePackageName,
426             @NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
427             @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode,
428             @Nullable PresentationStatsEventLogger presentationStatsEventLogger,
429             @NonNull Object sessionLock) {
430         if (sVerbose) {
431             Slog.v(TAG, "showFillDialog for "
432                     + componentName.toShortString() + ": " + response);
433         }
434 
435         final LogMaker log = Helper
436                 .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
437                         sessionId, compatMode)
438                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
439                         filterText == null ? 0 : filterText.length())
440                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
441                         response.getDatasets() == null ? 0 : response.getDatasets().size());
442 
443         mHandler.post(() -> {
444             if (callback != mCallback) {
445                 return;
446             }
447             hideAllUiThread(callback);
448             mFillDialog = new DialogFillUi(mContext, response, focusedId, filterText,
449                     serviceIcon, servicePackageName, componentName, mOverlayControl,
450                     mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
451                         @Override
452                         public void onResponsePicked(FillResponse response) {
453                             log(MetricsEvent.TYPE_DETAIL);
454                             hideFillDialogUiThread(callback);
455                             if (mCallback != null) {
456                                 mCallback.authenticate(response.getRequestId(),
457                                         AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
458                                         response.getAuthentication(), response.getClientState(),
459                                         UI_TYPE_DIALOG);
460                             }
461                         }
462 
463                         @Override
464                         public void onShown() {
465                             mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
466                         }
467 
468                         @Override
469                         public void onDatasetPicked(Dataset dataset) {
470                             log(MetricsEvent.TYPE_ACTION);
471                             synchronized (sessionLock) {
472                                 if (presentationStatsEventLogger != null) {
473                                     presentationStatsEventLogger.maybeSetPositiveCtaButtonClicked(
474                                             true);
475                                 }
476                             }
477                             hideFillDialogUiThread(callback);
478                             if (mCallback != null) {
479                                 final int datasetIndex = response.getDatasets().indexOf(dataset);
480                                 mCallback.fill(response.getRequestId(), datasetIndex, dataset,
481                                         UI_TYPE_DIALOG);
482                             }
483                         }
484 
485                         @Override
486                         public void onDismissed() {
487                             log(MetricsEvent.TYPE_DISMISS);
488                             synchronized (sessionLock) {
489                                 if (presentationStatsEventLogger != null) {
490                                     presentationStatsEventLogger.maybeSetDialogDismissed(true);
491                                 }
492                             }
493                             hideFillDialogUiThread(callback);
494                             callback.requestShowSoftInput(focusedId);
495                             callback.requestFallbackFromFillDialog();
496                         }
497 
498                         @Override
499                         public void onCanceled() {
500                             log(MetricsEvent.TYPE_CLOSE);
501                             synchronized (sessionLock) {
502                                 if (presentationStatsEventLogger != null) {
503                                     presentationStatsEventLogger.maybeSetNegativeCtaButtonClicked(
504                                             true);
505                                 }
506                             }
507                             hideFillDialogUiThread(callback);
508                             callback.requestShowSoftInput(focusedId);
509                             callback.requestFallbackFromFillDialog();
510                         }
511 
512                         @Override
513                         public void startIntentSender(IntentSender intentSender) {
514                             mCallback.startIntentSenderAndFinishSession(intentSender);
515                         }
516 
517                         private void log(int type) {
518                             log.setType(type);
519                             mMetricsLogger.write(log);
520                         }
521                     });
522         });
523     }
524 
525     /**
526      * Executes an operation in the pending save UI, if any.
527      */
onPendingSaveUi(int operation, @NonNull IBinder token)528     public void onPendingSaveUi(int operation, @NonNull IBinder token) {
529         mHandler.post(() -> {
530             if (mSaveUi != null) {
531                 mSaveUi.onPendingUi(operation, token);
532             } else {
533                 Slog.w(TAG, "onPendingSaveUi(" + operation + "): no save ui");
534             }
535         });
536     }
537 
538     /**
539      * Hides all autofill UIs.
540      */
hideAll(@ullable AutoFillUiCallback callback)541     public void hideAll(@Nullable AutoFillUiCallback callback) {
542         mHandler.post(() -> hideAllUiThread(callback));
543     }
544 
545     /**
546      * Destroy all autofill UIs.
547      */
destroyAll(@ullable PendingUi pendingSaveUi, @Nullable AutoFillUiCallback callback, boolean notifyClient)548     public void destroyAll(@Nullable PendingUi pendingSaveUi,
549             @Nullable AutoFillUiCallback callback, boolean notifyClient) {
550         mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback, notifyClient));
551     }
552 
isSaveUiShowing()553     public boolean isSaveUiShowing() {
554         return mSaveUi == null ? false : mSaveUi.isShowing();
555     }
556 
isFillDialogShowing()557     public boolean isFillDialogShowing() {
558         return mFillDialog == null ? false : mFillDialog.isShowing();
559     }
560 
dump(PrintWriter pw)561     public void dump(PrintWriter pw) {
562         pw.println("Autofill UI");
563         final String prefix = "  ";
564         final String prefix2 = "    ";
565         pw.print(prefix); pw.print("Night mode: "); pw.println(mUiModeMgr.isNightMode());
566         if (mFillUi != null) {
567             pw.print(prefix); pw.println("showsFillUi: true");
568             mFillUi.dump(pw, prefix2);
569         } else {
570             pw.print(prefix); pw.println("showsFillUi: false");
571         }
572         if (mSaveUi != null) {
573             pw.print(prefix); pw.println("showsSaveUi: true");
574             mSaveUi.dump(pw, prefix2);
575         } else {
576             pw.print(prefix); pw.println("showsSaveUi: false");
577         }
578         if (mFillDialog != null) {
579             pw.print(prefix); pw.println("showsFillDialog: true");
580             mFillDialog.dump(pw, prefix2);
581         } else {
582             pw.print(prefix); pw.println("showsFillDialog: false");
583         }
584     }
585 
586     @android.annotation.UiThread
hideFillUiUiThread(@ullable AutoFillUiCallback callback, boolean notifyClient)587     private void hideFillUiUiThread(@Nullable AutoFillUiCallback callback, boolean notifyClient) {
588         if (mFillUi != null && (callback == null || callback == mCallback)) {
589             mFillUi.destroy(notifyClient);
590             mFillUi = null;
591         }
592     }
593 
594     @android.annotation.UiThread
595     @Nullable
hideSaveUiUiThread(@ullable AutoFillUiCallback callback)596     private PendingUi hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) {
597         if (sVerbose) {
598             Slog.v(TAG, "hideSaveUiUiThread(): mSaveUi=" + mSaveUi + ", callback=" + callback
599                     + ", mCallback=" + mCallback);
600         }
601 
602         if (mSaveUi != null && mSaveUiCallback == callback) {
603             return mSaveUi.hide();
604         }
605         return null;
606     }
607 
608     @android.annotation.UiThread
hideFillDialogUiThread(@ullable AutoFillUiCallback callback)609     private void hideFillDialogUiThread(@Nullable AutoFillUiCallback callback) {
610         if (mFillDialog != null && (callback == null || callback == mCallback)) {
611             mFillDialog.destroy();
612             mFillDialog = null;
613         }
614     }
615 
616     @android.annotation.UiThread
destroySaveUiUiThread(@ullable PendingUi pendingSaveUi, boolean notifyClient)617     private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
618         if (mSaveUi == null) {
619             // Calling destroySaveUiUiThread() twice is normal - it usually happens when the
620             // first call is made after the SaveUI is hidden and the second when the session is
621             // finished.
622             if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): already destroyed");
623             return;
624         }
625 
626         if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): " + pendingSaveUi);
627         mSaveUi.destroy();
628         mSaveUi = null;
629         mSaveUiCallback = null;
630         if (pendingSaveUi != null && notifyClient) {
631             try {
632                 if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): notifying client");
633                 pendingSaveUi.client.setSaveUiState(pendingSaveUi.sessionId, false);
634             } catch (RemoteException e) {
635                 Slog.e(TAG, "Error notifying client to set save UI state to hidden: " + e);
636             }
637         }
638 
639         if (mCreateFillUiRunnable != null) {
640             if (sDebug) Slog.d(TAG, "start the pending fill UI request..");
641             mHandler.post(mCreateFillUiRunnable);
642             mCreateFillUiRunnable = null;
643         }
644     }
645 
646     @android.annotation.UiThread
destroyAllUiThread(@ullable PendingUi pendingSaveUi, @Nullable AutoFillUiCallback callback, boolean notifyClient)647     private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
648             @Nullable AutoFillUiCallback callback, boolean notifyClient) {
649         hideFillUiUiThread(callback, notifyClient);
650         hideFillDialogUiThread(callback);
651         destroySaveUiUiThread(pendingSaveUi, notifyClient);
652     }
653 
654     @android.annotation.UiThread
hideAllUiThread(@ullable AutoFillUiCallback callback)655     private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
656         hideFillUiUiThread(callback, true);
657         hideFillDialogUiThread(callback);
658         final PendingUi pendingSaveUi = hideSaveUiUiThread(callback);
659         if (pendingSaveUi != null && pendingSaveUi.getState() == PendingUi.STATE_FINISHED) {
660             if (sDebug) {
661                 Slog.d(TAG, "hideAllUiThread(): "
662                         + "destroying Save UI because pending restoration is finished");
663             }
664             destroySaveUiUiThread(pendingSaveUi, true);
665         }
666     }
667 }
668