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