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