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