Session.java revision fd31f85d0c6f19138d06d565d2b30b59e241a960
1/* 2 * Copyright (C) 2017 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 17package com.android.server.autofill; 18 19import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; 20import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; 21import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS; 22import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; 23import static android.view.autofill.AutofillManager.FLAG_START_SESSION; 24import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED; 25import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED; 26import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED; 27 28import static com.android.server.autofill.Helper.DEBUG; 29import static com.android.server.autofill.Helper.VERBOSE; 30import static com.android.server.autofill.Helper.findViewNodeById; 31 32import android.annotation.NonNull; 33import android.annotation.Nullable; 34import android.app.Activity; 35import android.app.ActivityManager; 36import android.app.assist.AssistStructure; 37import android.app.assist.AssistStructure.AutofillOverlay; 38import android.app.assist.AssistStructure.ViewNode; 39import android.content.ComponentName; 40import android.content.Context; 41import android.content.Intent; 42import android.content.IntentSender; 43import android.graphics.Rect; 44import android.metrics.LogMaker; 45import android.os.Binder; 46import android.os.Bundle; 47import android.os.IBinder; 48import android.os.Parcelable; 49import android.os.RemoteException; 50import android.service.autofill.AutofillService; 51import android.service.autofill.Dataset; 52import android.service.autofill.FillContext; 53import android.service.autofill.FillRequest; 54import android.service.autofill.FillResponse; 55import android.service.autofill.SaveInfo; 56import android.service.autofill.SaveRequest; 57import android.util.ArrayMap; 58import android.util.DebugUtils; 59import android.util.Slog; 60import android.util.SparseArray; 61import android.view.autofill.AutofillId; 62import android.view.autofill.AutofillManager; 63import android.view.autofill.AutofillValue; 64import android.view.autofill.IAutoFillManagerClient; 65import android.view.autofill.IAutofillWindowPresenter; 66 67import com.android.internal.R; 68import com.android.internal.annotations.GuardedBy; 69import com.android.internal.logging.MetricsLogger; 70import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 71import com.android.internal.os.HandlerCaller; 72import com.android.internal.os.IResultReceiver; 73import com.android.internal.util.ArrayUtils; 74import com.android.server.autofill.ui.AutoFillUI; 75 76import java.io.PrintWriter; 77import java.util.ArrayList; 78import java.util.Collections; 79import java.util.Map; 80import java.util.Map.Entry; 81import java.util.concurrent.atomic.AtomicInteger; 82 83/** 84 * A session for a given activity. 85 * 86 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track 87 * of the current {@link ViewState} to display the appropriate UI. 88 * 89 * <p>Although the autofill requests and callbacks are stateless from the service's point of 90 * view, we need to keep state in the framework side for cases such as authentication. For 91 * example, when service return a {@link FillResponse} that contains all the fields needed 92 * to fill the activity but it requires authentication first, that response need to be held 93 * until the user authenticates or it times out. 94 */ 95final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, 96 AutoFillUI.AutoFillUiCallback { 97 private static final String TAG = "AutofillSession"; 98 99 private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; 100 101 private final AutofillManagerServiceImpl mService; 102 private final HandlerCaller mHandlerCaller; 103 private final Object mLock; 104 private final AutoFillUI mUi; 105 106 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 107 108 private static AtomicInteger sIdCounter = new AtomicInteger(); 109 110 /** Id of the session */ 111 private final int mId; 112 113 /** uid the session is for */ 114 public final int uid; 115 116 @GuardedBy("mLock") 117 @NonNull private IBinder mActivityToken; 118 119 @GuardedBy("mLock") 120 @NonNull private IBinder mWindowToken; 121 122 /** Package name of the app that is auto-filled */ 123 @NonNull private final String mPackageName; 124 125 @GuardedBy("mLock") 126 private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>(); 127 128 /** 129 * Id of the View currently being displayed. 130 */ 131 @GuardedBy("mLock") 132 @Nullable private AutofillId mCurrentViewId; 133 134 @GuardedBy("mLock") 135 private IAutoFillManagerClient mClient; 136 137 private final RemoteFillService mRemoteFillService; 138 139 @GuardedBy("mLock") 140 private SparseArray<FillResponse> mResponses; 141 142 /** 143 * Response that requires a service authentitcation request. 144 */ 145 @GuardedBy("mLock") 146 private FillResponse mResponseWaitingAuth; 147 148 /** 149 * Dataset that when tapped launched a service authentication request. 150 */ 151 @GuardedBy("mLock") 152 private Dataset mDatasetWaitingAuth; 153 154 /** 155 * Contexts read from the app; they will be updated (sanitized, change values for save) before 156 * sent to {@link AutofillService}. Ordered by the time they we read. 157 */ 158 @GuardedBy("mLock") 159 private ArrayList<FillContext> mContexts; 160 161 /** 162 * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. 163 */ 164 private boolean mHasCallback; 165 166 /** 167 * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved 168 * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls. 169 */ 170 @GuardedBy("mLock") 171 private Bundle mClientState; 172 173 @GuardedBy("mLock") 174 private boolean mDestroyed; 175 176 /** 177 * Flags used to start the session. 178 */ 179 private final int mFlags; 180 181 /** 182 * Receiver of assist data from the app's {@link Activity}. 183 */ 184 private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { 185 @Override 186 public void send(int resultCode, Bundle resultData) throws RemoteException { 187 if (VERBOSE) { 188 Slog.v(TAG, "resultCode on mAssistReceiver: " + resultCode); 189 } 190 191 final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE); 192 if (structure == null) { 193 Slog.wtf(TAG, "no assist structure for id " + resultCode); 194 return; 195 } 196 197 final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS); 198 if (receiverExtras == null) { 199 Slog.wtf(TAG, "No " + KEY_RECEIVER_EXTRAS + " on receiver"); 200 return; 201 } 202 203 final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); 204 205 if (DEBUG) { 206 Slog.d(TAG, "New structure for requestId " + requestId + ": " + structure); 207 } 208 209 final FillRequest request; 210 synchronized (mLock) { 211 // TODO(b/35708678): Must fetch the data so it's available later on handleSave(), 212 // even if if the activity is gone by then, but structure .ensureData() gives a 213 // ONE_WAY warning because system_service could block on app calls. We need to 214 // change AssistStructure so it provides a "one-way" writeToParcel() method that 215 // sends all the data 216 structure.ensureData(); 217 218 // Sanitize structure before it's sent to service. 219 structure.sanitizeForParceling(true); 220 221 if (mContexts == null) { 222 mContexts = new ArrayList<>(1); 223 } 224 mContexts.add(new FillContext(requestId, structure)); 225 226 cancelCurrentRequestLocked(); 227 228 final int numContexts = mContexts.size(); 229 for (int i = 0; i < numContexts; i++) { 230 fillStructureWithAllowedValues(mContexts.get(i).getStructure()); 231 } 232 233 request = new FillRequest(requestId, mContexts, mClientState, mFlags); 234 } 235 236 mRemoteFillService.onFillRequest(request); 237 } 238 }; 239 240 /** 241 * Updates values of the nodes in the structure so that: 242 * - proper node is focused 243 * - autofillValue is sent back to service when it was previously autofilled 244 * 245 * @param structure The structure to be filled 246 */ 247 private void fillStructureWithAllowedValues(@NonNull AssistStructure structure) { 248 final int numViewStates = mViewStates.size(); 249 for (int i = 0; i < numViewStates; i++) { 250 final ViewState viewState = mViewStates.valueAt(i); 251 252 final ViewNode node = findViewNodeById(structure, viewState.id); 253 if (node == null) { 254 if (DEBUG) { 255 Slog.w(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id); 256 } 257 continue; 258 } 259 260 final AutofillValue initialValue = viewState.getInitialValue(); 261 final AutofillValue filledValue = viewState.getAutofilledValue(); 262 final AutofillOverlay overlay = new AutofillOverlay(); 263 if (filledValue != null && !filledValue.equals(initialValue)) { 264 overlay.value = filledValue; 265 } 266 if (mCurrentViewId != null) { 267 overlay.focused = mCurrentViewId.equals(viewState.id); 268 } 269 270 node.setAutofillOverlay(overlay); 271 } 272 } 273 274 /** 275 * Cancels the last request sent to the {@link #mRemoteFillService}. 276 */ 277 private void cancelCurrentRequestLocked() { 278 int canceledRequest = mRemoteFillService.cancelCurrentRequest(); 279 280 // Remove the FillContext as there will never be a response for the service 281 if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { 282 int numContexts = mContexts.size(); 283 284 // It is most likely the last context, hence search backwards 285 for (int i = numContexts - 1; i >= 0; i--) { 286 if (mContexts.get(i).getRequestId() == canceledRequest) { 287 mContexts.remove(i); 288 break; 289 } 290 } 291 } 292 293 } 294 295 /** 296 * Reads a new structure and then request a new fill response from the fill service. 297 */ 298 private void requestNewFillResponseLocked() { 299 int requestId; 300 301 do { 302 requestId = sIdCounter.getAndIncrement(); 303 } while (requestId == INVALID_REQUEST_ID); 304 305 if (DEBUG) { 306 Slog.d(TAG, "Requesting structure for requestId " + requestId); 307 } 308 309 // If the focus changes very quickly before the first request is returned each focus change 310 // triggers a new partition and we end up with many duplicate partitions. This is 311 // enhanced as the focus change can be much faster than the taking of the assist structure. 312 // Hence remove the currently queued request and replace it with the one queued after the 313 // structure is taken. This causes only one fill request per bust of focus changes. 314 cancelCurrentRequestLocked(); 315 316 try { 317 final Bundle receiverExtras = new Bundle(); 318 receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); 319 final long identity = Binder.clearCallingIdentity(); 320 try { 321 if (!ActivityManager.getService().requestAutofillData(mAssistReceiver, 322 receiverExtras, mActivityToken, mFlags)) { 323 Slog.w(TAG, "failed to request autofill data for " + mActivityToken); 324 } 325 } finally { 326 Binder.restoreCallingIdentity(identity); 327 } 328 } catch (RemoteException e) { 329 // Should not happen, it's a local call. 330 } 331 } 332 333 Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui, 334 @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId, 335 @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken, 336 @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback, 337 int flags, @NonNull ComponentName componentName, @NonNull String packageName) { 338 mId = sessionId; 339 this.uid = uid; 340 mService = service; 341 mLock = lock; 342 mUi = ui; 343 mHandlerCaller = handlerCaller; 344 mRemoteFillService = new RemoteFillService(context, componentName, userId, this); 345 mActivityToken = activityToken; 346 mWindowToken = windowToken; 347 mHasCallback = hasCallback; 348 mPackageName = packageName; 349 mFlags = flags; 350 mClient = IAutoFillManagerClient.Stub.asInterface(client); 351 352 mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName); 353 } 354 355 /** 356 * Gets the currently registered activity token 357 * 358 * @return The activity token 359 */ 360 IBinder getActivityTokenLocked() { 361 return mActivityToken; 362 } 363 364 /** 365 * Sets new window for this session. 366 * 367 * @param newWindow The window the Ui should be attached to. Can be {@code null} if no 368 * further UI is needed. 369 */ 370 void switchWindow(@NonNull IBinder newWindow) { 371 synchronized (mLock) { 372 if (mDestroyed) { 373 Slog.w(TAG, "Call to Session#switchWindow() rejected - session: " 374 + mId + " destroyed"); 375 return; 376 } 377 mWindowToken = newWindow; 378 } 379 } 380 381 /** 382 * Sets new activity and client for this session. 383 * 384 * @param newActivity The token of the new activity 385 * @param newClient The client receiving autofill callbacks 386 */ 387 void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) { 388 synchronized (mLock) { 389 if (mDestroyed) { 390 Slog.w(TAG, "Call to Session#switchActivity() rejected - session: " 391 + mId + " destroyed"); 392 return; 393 } 394 mActivityToken = newActivity; 395 mClient = IAutoFillManagerClient.Stub.asInterface(newClient); 396 397 // The tracked id are not persisted in the client, hence update them 398 updateTrackedIdsLocked(); 399 } 400 } 401 402 // FillServiceCallbacks 403 @Override 404 public void onFillRequestSuccess(@Nullable FillResponse response, int serviceUid, 405 @NonNull String servicePackageName) { 406 synchronized (mLock) { 407 if (mDestroyed) { 408 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " 409 + mId + " destroyed"); 410 return; 411 } 412 } 413 if (response == null) { 414 if ((mFlags & FLAG_MANUAL_REQUEST) != 0) { 415 getUiForShowing().showError(R.string.autofill_error_cannot_autofill); 416 } 417 // Nothing to be done, but need to notify client. 418 notifyUnavailableToClient(); 419 removeSelf(); 420 return; 421 } 422 423 mService.setLastResponse(serviceUid, response); 424 425 if ((response.getDatasets() == null || response.getDatasets().isEmpty()) 426 && response.getAuthentication() == null) { 427 // Response is "empty" from an UI point of view, need to notify client. 428 notifyUnavailableToClient(); 429 } 430 synchronized (mLock) { 431 if (response.getAuthentication() != null) { 432 // TODO(b/37424539): proper implementation 433 mResponseWaitingAuth = response; 434 } 435 processResponseLocked(response); 436 } 437 438 final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) 439 .setType(MetricsEvent.TYPE_SUCCESS) 440 .setPackageName(mPackageName) 441 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, 442 response.getDatasets() == null ? 0 : response.getDatasets().size()) 443 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, 444 servicePackageName); 445 mMetricsLogger.write(log); 446 } 447 448 // FillServiceCallbacks 449 @Override 450 public void onFillRequestFailure(@Nullable CharSequence message, 451 @NonNull String servicePackageName) { 452 synchronized (mLock) { 453 if (mDestroyed) { 454 Slog.w(TAG, "Call to Session#onFillRequestFailure() rejected - session: " 455 + mId + " destroyed"); 456 return; 457 } 458 } 459 LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) 460 .setType(MetricsEvent.TYPE_FAILURE) 461 .setPackageName(mPackageName) 462 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName); 463 mMetricsLogger.write(log); 464 465 getUiForShowing().showError(message); 466 removeSelf(); 467 } 468 469 // FillServiceCallbacks 470 @Override 471 public void onSaveRequestSuccess(@NonNull String servicePackageName) { 472 synchronized (mLock) { 473 if (mDestroyed) { 474 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " 475 + mId + " destroyed"); 476 return; 477 } 478 } 479 LogMaker log = (new LogMaker( 480 MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST)) 481 .setType(MetricsEvent.TYPE_SUCCESS) 482 .setPackageName(mPackageName) 483 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName); 484 mMetricsLogger.write(log); 485 486 // Nothing left to do... 487 removeSelf(); 488 } 489 490 // FillServiceCallbacks 491 @Override 492 public void onSaveRequestFailure(@Nullable CharSequence message, 493 @NonNull String servicePackageName) { 494 synchronized (mLock) { 495 if (mDestroyed) { 496 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " 497 + mId + " destroyed"); 498 return; 499 } 500 } 501 LogMaker log = (new LogMaker( 502 MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST)) 503 .setType(MetricsEvent.TYPE_FAILURE) 504 .setPackageName(mPackageName) 505 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName); 506 mMetricsLogger.write(log); 507 508 getUiForShowing().showError(message); 509 removeSelf(); 510 } 511 512 /** 513 * Gets the {@link FillContext} for a request. 514 * 515 * @param requestId The id of the request 516 * 517 * @return The context or {@code null} if there is no context 518 */ 519 @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) { 520 if (mContexts == null) { 521 return null; 522 } 523 524 int numContexts = mContexts.size(); 525 for (int i = 0; i < numContexts; i++) { 526 FillContext context = mContexts.get(i); 527 528 if (context.getRequestId() == requestId) { 529 return context; 530 } 531 } 532 533 return null; 534 } 535 536 // FillServiceCallbacks 537 @Override 538 public void authenticate(int requestId, IntentSender intent, Bundle extras) { 539 final Intent fillInIntent; 540 synchronized (mLock) { 541 synchronized (mLock) { 542 if (mDestroyed) { 543 Slog.w(TAG, "Call to Session#authenticate() rejected - session: " 544 + mId + " destroyed"); 545 return; 546 } 547 } 548 fillInIntent = createAuthFillInIntent( 549 getFillContextByRequestIdLocked(requestId).getStructure(), extras); 550 } 551 552 mService.setAuthenticationSelected(); 553 554 mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent)); 555 } 556 557 // FillServiceCallbacks 558 @Override 559 public void onServiceDied(RemoteFillService service) { 560 // TODO(b/337565347): implement 561 } 562 563 // AutoFillUiCallback 564 @Override 565 public void fill(int requestId, Dataset dataset) { 566 mHandlerCaller.getHandler().post(() -> autoFill(requestId, dataset)); 567 synchronized (mLock) { 568 if (mDestroyed) { 569 Slog.w(TAG, "Call to Session#fill() rejected - session: " 570 + mId + " destroyed"); 571 return; 572 } 573 } 574 mHandlerCaller.getHandler().post(() -> autoFill(requestId, dataset)); 575 } 576 577 // AutoFillUiCallback 578 @Override 579 public void save() { 580 synchronized (mLock) { 581 if (mDestroyed) { 582 Slog.w(TAG, "Call to Session#save() rejected - session: " 583 + mId + " destroyed"); 584 return; 585 } 586 } 587 mHandlerCaller.getHandler() 588 .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, mId, 0) 589 .sendToTarget(); 590 } 591 592 // AutoFillUiCallback 593 @Override 594 public void cancelSave() { 595 synchronized (mLock) { 596 if (mDestroyed) { 597 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " 598 + mId + " destroyed"); 599 return; 600 } 601 } 602 mHandlerCaller.getHandler().post(() -> removeSelf()); 603 } 604 605 // AutoFillUiCallback 606 @Override 607 public void requestShowFillUi(AutofillId id, int width, int height, 608 IAutofillWindowPresenter presenter) { 609 synchronized (mLock) { 610 if (mDestroyed) { 611 Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: " 612 + mId + " destroyed"); 613 return; 614 } 615 if (id.equals(mCurrentViewId)) { 616 try { 617 final ViewState view = mViewStates.get(id); 618 mClient.requestShowFillUi(mId, mWindowToken, id, width, height, 619 view.getVirtualBounds(), presenter); 620 } catch (RemoteException e) { 621 Slog.e(TAG, "Error requesting to show fill UI", e); 622 } 623 } else { 624 if (DEBUG) { 625 Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view (" 626 + mCurrentViewId + ") anymore"); 627 } 628 } 629 } 630 } 631 632 // AutoFillUiCallback 633 @Override 634 public void requestHideFillUi(AutofillId id) { 635 synchronized (mLock) { 636 // NOTE: We allow this call in a destroyed state as the UI is 637 // asked to go away after we get destroyed, so let it do that. 638 try { 639 mClient.requestHideFillUi(mId, mWindowToken, id); 640 } catch (RemoteException e) { 641 Slog.e(TAG, "Error requesting to hide fill UI", e); 642 } 643 } 644 } 645 646 // AutoFillUiCallback 647 @Override 648 public void startIntentSender(IntentSender intentSender) { 649 synchronized (mLock) { 650 if (mDestroyed) { 651 Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: " 652 + mId + " destroyed"); 653 return; 654 } 655 removeSelfLocked(); 656 } 657 mHandlerCaller.getHandler().post(() -> { 658 try { 659 synchronized (mLock) { 660 mClient.startIntentSender(intentSender); 661 } 662 } catch (RemoteException e) { 663 Slog.e(TAG, "Error launching auth intent", e); 664 } 665 }); 666 } 667 668 void setAuthenticationResultLocked(Bundle data) { 669 if (mDestroyed) { 670 Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: " 671 + mId + " destroyed"); 672 return; 673 } 674 if ((mResponseWaitingAuth == null && mDatasetWaitingAuth == null) || data == null) { 675 removeSelf(); 676 } else { 677 final Parcelable result = data.getParcelable( 678 AutofillManager.EXTRA_AUTHENTICATION_RESULT); 679 if (result instanceof FillResponse) { 680 FillResponse response = (FillResponse) result; 681 682 mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName); 683 final int requestIndex = mResponses.indexOfValue(mResponseWaitingAuth); 684 mResponseWaitingAuth = null; 685 if (requestIndex >= 0) { 686 response.setRequestId(mResponses.keyAt(requestIndex)); 687 processResponseLocked(response); 688 } else { 689 Slog.e(TAG, "Error cannot find id for auth response"); 690 } 691 } else if (result instanceof Dataset) { 692 final Dataset dataset = (Dataset) result; 693 for (int i = 0; i < mResponses.size(); i++) { 694 final FillResponse response = mResponses.valueAt(i); 695 final int index = response.getDatasets().indexOf(mDatasetWaitingAuth); 696 if (index >= 0) { 697 response.getDatasets().set(index, dataset); 698 mDatasetWaitingAuth = null; 699 autoFill(mResponses.keyAt(i), dataset); 700 resetViewStatesLocked(dataset, ViewState.STATE_WAITING_DATASET_AUTH); 701 return; 702 } 703 } 704 } 705 } 706 } 707 708 void setHasCallbackLocked(boolean hasIt) { 709 if (mDestroyed) { 710 Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " 711 + mId + " destroyed"); 712 return; 713 } 714 mHasCallback = hasIt; 715 } 716 717 /** 718 * Shows the save UI, when session can be saved. 719 * 720 * @return {@code true} if session is done, or {@code false} if it's pending user action. 721 */ 722 public boolean showSaveLocked() { 723 if (mDestroyed) { 724 Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: " 725 + mId + " destroyed"); 726 return false; 727 } 728 if (mContexts == null) { 729 Slog.d(TAG, "showSaveLocked(): no contexts"); 730 return true; 731 } 732 if (mResponses == null) { 733 // Happens when the activity / session was finished before the service replied, or 734 // when the service cannot autofill it (and returned a null response). 735 if (DEBUG) { 736 Slog.d(TAG, "showSaveLocked(): no responses on session"); 737 } 738 return true; 739 } 740 741 final int lastResponseIdx = getLastResponseIndex(); 742 if (lastResponseIdx < 0) { 743 Slog.d(TAG, "showSaveLocked(): mResponses=" + mResponses 744 + ", mViewStates=" + mViewStates); 745 return true; 746 } 747 748 final FillResponse response = mResponses.valueAt(lastResponseIdx); 749 final SaveInfo saveInfo = response.getSaveInfo(); 750 if (DEBUG) { 751 Slog.d(TAG, "showSaveLocked(): mResponses=" + mResponses + ", mContexts=" + mContexts 752 + ", mViewStates=" + mViewStates); 753 } 754 755 /* 756 * The Save dialog is only shown if all conditions below are met: 757 * 758 * - saveInfo is not null 759 * - autofillValue of all required ids is not null 760 * - autofillValue of at least one id (required or optional) has changed. 761 */ 762 763 if (saveInfo == null) { 764 return true; 765 } 766 767 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 768 if (requiredIds == null || requiredIds.length == 0) { 769 Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo"); 770 return true; 771 } 772 773 boolean allRequiredAreNotEmpty = true; 774 boolean atLeastOneChanged = false; 775 for (int i = 0; i < requiredIds.length; i++) { 776 final AutofillId id = requiredIds[i]; 777 final ViewState viewState = mViewStates.get(id); 778 if (viewState == null) { 779 Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id); 780 allRequiredAreNotEmpty = false; 781 break; 782 } 783 784 final AutofillValue currentValue = viewState.getCurrentValue(); 785 if (currentValue == null || currentValue.isEmpty()) { 786 if (DEBUG) { 787 Slog.d(TAG, "showSaveLocked(): empty value for required " + id ); 788 } 789 allRequiredAreNotEmpty = false; 790 break; 791 } 792 final AutofillValue filledValue = viewState.getAutofilledValue(); 793 794 if (!currentValue.equals(filledValue)) { 795 if (DEBUG) { 796 Slog.d(TAG, "showSaveLocked(): found a change on required " + id + ": " 797 + filledValue + " => " + currentValue); 798 } 799 atLeastOneChanged = true; 800 } 801 } 802 803 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 804 if (allRequiredAreNotEmpty) { 805 if (!atLeastOneChanged && optionalIds != null) { 806 // No change on required ids yet, look for changes on optional ids. 807 for (int i = 0; i < optionalIds.length; i++) { 808 final AutofillId id = optionalIds[i]; 809 final ViewState viewState = mViewStates.get(id); 810 if (viewState == null) { 811 Slog.w(TAG, "showSaveLocked(): no ViewState for optional " + id); 812 continue; 813 } 814 if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) { 815 final AutofillValue currentValue = viewState.getCurrentValue(); 816 final AutofillValue filledValue = viewState.getAutofilledValue(); 817 if (currentValue != null && !currentValue.equals(filledValue)) { 818 if (DEBUG) { 819 Slog.d(TAG, "finishSessionLocked(): found a change on optional " 820 + id + ": " + filledValue + " => " + currentValue); 821 } 822 atLeastOneChanged = true; 823 break; 824 } 825 } 826 } 827 } 828 if (atLeastOneChanged) { 829 mService.setSaveShown(); 830 getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName); 831 return false; 832 } 833 } 834 // Nothing changed... 835 if (DEBUG) { 836 Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities." 837 + "allRequiredAreNotNull=" + allRequiredAreNotEmpty 838 + ", atLeastOneChanged=" + atLeastOneChanged); 839 } 840 return true; 841 } 842 843 /** 844 * Calls service when user requested save. 845 */ 846 void callSaveLocked() { 847 if (mDestroyed) { 848 Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: " 849 + mId + " destroyed"); 850 return; 851 } 852 853 if (DEBUG) { 854 Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates); 855 } 856 857 int numContexts = mContexts.size(); 858 859 for (int i = 0; i < numContexts; i++) { 860 FillContext context = mContexts.get(i); 861 862 if (VERBOSE) { 863 Slog.v(TAG, "callSaveLocked(): updating " + context); 864 } 865 866 for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { 867 final AutofillValue value = entry.getValue().getCurrentValue(); 868 if (value == null) { 869 if (VERBOSE) { 870 Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey()); 871 } 872 continue; 873 } 874 final AutofillId id = entry.getKey(); 875 final ViewNode node = findViewNodeById(context.getStructure(), id); 876 if (node == null) { 877 Slog.w(TAG, "callSaveLocked(): did not find node with id " + id); 878 continue; 879 } 880 if (VERBOSE) { 881 Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value); 882 } 883 884 node.updateAutofillValue(value); 885 } 886 887 // Sanitize structure before it's sent to service. 888 context.getStructure().sanitizeForParceling(false); 889 890 if (VERBOSE) { 891 Slog.v(TAG, "Dumping structure of " + context + " before calling service.save()"); 892 context.getStructure().dump(); 893 } 894 } 895 896 // Remove pending fill requests as the session is finished. 897 cancelCurrentRequestLocked(); 898 899 final SaveRequest saveRequest = new SaveRequest(mContexts, mClientState); 900 mRemoteFillService.onSaveRequest(saveRequest); 901 } 902 903 /** 904 * Determines if a new partition should be started for an id. 905 * 906 * @param id The id of the view that is entered 907 * 908 * @return {@code true} iff a new partition should be started 909 */ 910 private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) { 911 if (mResponses == null) { 912 return true; 913 } 914 915 final int numResponses = mResponses.size(); 916 for (int responseNum = 0; responseNum < numResponses; responseNum++) { 917 final FillResponse response = mResponses.valueAt(responseNum); 918 919 if (ArrayUtils.contains(response.getIgnoredIds(), id)) { 920 return false; 921 } 922 923 final SaveInfo saveInfo = response.getSaveInfo(); 924 if (saveInfo != null) { 925 if (ArrayUtils.contains(saveInfo.getOptionalIds(), id) 926 || ArrayUtils.contains(saveInfo.getRequiredIds(), id)) { 927 return false; 928 } 929 } 930 931 final ArrayList<Dataset> datasets = response.getDatasets(); 932 if (datasets != null) { 933 final int numDatasets = datasets.size(); 934 935 for (int dataSetNum = 0; dataSetNum < numDatasets; dataSetNum++) { 936 final ArrayList fields = datasets.get(dataSetNum).getFieldIds(); 937 938 if (fields != null && fields.contains(id)) { 939 return false; 940 } 941 } 942 } 943 } 944 945 return true; 946 } 947 948 void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) { 949 if (mDestroyed) { 950 Slog.w(TAG, "Call to Session#updateLocked() rejected - session: " 951 + mId + " destroyed"); 952 return; 953 } 954 ViewState viewState = mViewStates.get(id); 955 956 if (viewState == null) { 957 if ((flags & (FLAG_START_SESSION | FLAG_VALUE_CHANGED | FLAG_VIEW_ENTERED)) != 0) { 958 if (DEBUG) { 959 Slog.d(TAG, "Creating viewState for " + id + " on " + getFlagAsString(flags)); 960 } 961 viewState = new ViewState(this, id, value, this, ViewState.STATE_INITIAL); 962 mViewStates.put(id, viewState); 963 } else { 964 if (VERBOSE) Slog.v(TAG, "Ignored " + getFlagAsString(flags) + " for " + id); 965 return; 966 } 967 } 968 969 if ((flags & FLAG_START_SESSION) != 0) { 970 // View is triggering autofill. 971 mCurrentViewId = viewState.id; 972 viewState.update(value, virtualBounds); 973 viewState.setState(ViewState.STATE_STARTED_SESSION); 974 requestNewFillResponseLocked(); 975 return; 976 } 977 978 if ((flags & FLAG_VALUE_CHANGED) != 0) { 979 if (value != null && !value.equals(viewState.getCurrentValue())) { 980 // Always update the internal state. 981 viewState.setCurrentValue(value); 982 983 // Must check if this update was caused by autofilling the view, in which 984 // case we just update the value, but not the UI. 985 final AutofillValue filledValue = viewState.getAutofilledValue(); 986 if (value.equals(filledValue)) { 987 return; 988 } 989 // Update the internal state... 990 viewState.setState(ViewState.STATE_CHANGED); 991 992 //..and the UI 993 if (value.isText()) { 994 getUiForShowing().filterFillUi(value.getTextValue().toString()); 995 } else { 996 getUiForShowing().filterFillUi(null); 997 } 998 } 999 1000 return; 1001 } 1002 1003 if ((flags & FLAG_VIEW_ENTERED) != 0) { 1004 if (shouldStartNewPartitionLocked(id)) { 1005 // TODO(b/37424539): proper implementation 1006 if (mResponseWaitingAuth != null && ((flags & FLAG_START_SESSION) == 0)) { 1007 viewState.setState(ViewState.STATE_WAITING_RESPONSE_AUTH); 1008 } else if ((flags & FLAG_START_SESSION) == 0){ 1009 if (DEBUG) { 1010 Slog.d(TAG, "Starting partition for view id " + viewState.id); 1011 } 1012 viewState.setState(ViewState.STATE_STARTED_PARTITION); 1013 requestNewFillResponseLocked(); 1014 } 1015 } 1016 1017 // Remove the UI if the ViewState has changed. 1018 if (mCurrentViewId != viewState.id) { 1019 mUi.hideFillUi(mCurrentViewId != null ? mCurrentViewId : null); 1020 mCurrentViewId = viewState.id; 1021 } 1022 1023 // If the ViewState is ready to be displayed, onReady() will be called. 1024 viewState.update(value, virtualBounds); 1025 1026 return; 1027 } 1028 1029 if ((flags & FLAG_VIEW_EXITED) != 0) { 1030 if (mCurrentViewId == viewState.id) { 1031 mUi.hideFillUi(viewState.id); 1032 mCurrentViewId = null; 1033 } 1034 return; 1035 } 1036 1037 Slog.w(TAG, "updateLocked(): unknown flags " + flags + ": " + getFlagAsString(flags)); 1038 } 1039 1040 @Override 1041 public void onFillReady(FillResponse response, AutofillId filledId, 1042 @Nullable AutofillValue value) { 1043 synchronized (mLock) { 1044 if (mDestroyed) { 1045 Slog.w(TAG, "Call to Session#onFillReady() rejected - session: " 1046 + mId + " destroyed"); 1047 return; 1048 } 1049 } 1050 1051 String filterText = null; 1052 if (value != null && value.isText()) { 1053 filterText = value.getTextValue().toString(); 1054 } 1055 1056 getUiForShowing().showFillUi(filledId, response, filterText, mPackageName); 1057 } 1058 1059 static String getFlagAsString(int flag) { 1060 return DebugUtils.flagsToString(AutofillManager.class, "FLAG_", flag); 1061 } 1062 1063 int getId() { 1064 return mId; 1065 } 1066 1067 boolean isDestroyed() { 1068 synchronized (mLock) { 1069 return mDestroyed; 1070 } 1071 } 1072 1073 IAutoFillManagerClient getClient() { 1074 synchronized (mLock) { 1075 return mClient; 1076 } 1077 } 1078 1079 private void notifyUnavailableToClient() { 1080 synchronized (mLock) { 1081 if (!mHasCallback) return; 1082 try { 1083 mClient.notifyNoFillUi(mId, mWindowToken, mCurrentViewId); 1084 } catch (RemoteException e) { 1085 Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken 1086 + " id=" + mCurrentViewId, e); 1087 } 1088 } 1089 } 1090 1091 private void updateTrackedIdsLocked() { 1092 if (mResponses == null || mResponses.size() == 0) { 1093 return; 1094 } 1095 1096 // Only track the views of the last response as only those are reported back to the 1097 // service, see #showSaveLocked 1098 ArrayList<AutofillId> trackedViews = new ArrayList<>(); 1099 boolean saveOnAllViewsInvisible = false; 1100 SaveInfo saveInfo = mResponses.valueAt(getLastResponseIndex()).getSaveInfo(); 1101 if (saveInfo != null) { 1102 saveOnAllViewsInvisible = 1103 (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; 1104 1105 // We only need to track views if we want to save once they become invisible. 1106 if (saveOnAllViewsInvisible) { 1107 if (saveInfo.getRequiredIds() != null) { 1108 Collections.addAll(trackedViews, saveInfo.getRequiredIds()); 1109 } 1110 1111 if (saveInfo.getOptionalIds() != null) { 1112 Collections.addAll(trackedViews, saveInfo.getOptionalIds()); 1113 } 1114 } 1115 } 1116 1117 try { 1118 mClient.setTrackedViews(mId, trackedViews, saveOnAllViewsInvisible); 1119 } catch (RemoteException e) { 1120 Slog.w(TAG, "Cannot set tracked ids", e); 1121 } 1122 } 1123 1124 private void processResponseLocked(@NonNull FillResponse response) { 1125 if (DEBUG) { 1126 Slog.d(TAG, "processResponseLocked(mCurrentViewId=" + mCurrentViewId + "):" + response); 1127 } 1128 1129 if (mResponses == null) { 1130 mResponses = new SparseArray<>(4); 1131 } 1132 mResponses.put(response.getRequestId(), response); 1133 mClientState = response.getClientState(); 1134 1135 setViewStatesLocked(response, ViewState.STATE_FILLABLE); 1136 updateTrackedIdsLocked(); 1137 1138 if (mCurrentViewId == null) { 1139 return; 1140 } 1141 1142 if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null 1143 && response.getDatasets().size() == 1) { 1144 Slog.d(TAG, "autofilling manual request directly"); 1145 autoFill(response.getRequestId(), response.getDatasets().get(0)); 1146 return; 1147 } 1148 1149 // Updates the UI, if necessary. 1150 final ViewState currentView = mViewStates.get(mCurrentViewId); 1151 currentView.maybeCallOnFillReady(); 1152 } 1153 1154 /** 1155 * Sets the state of all views in the given response. 1156 */ 1157 private void setViewStatesLocked(FillResponse response, int state) { 1158 final ArrayList<Dataset> datasets = response.getDatasets(); 1159 if (datasets != null) { 1160 for (int i = 0; i < datasets.size(); i++) { 1161 final Dataset dataset = datasets.get(i); 1162 if (dataset == null) { 1163 Slog.w(TAG, "Ignoring null dataset on " + datasets); 1164 continue; 1165 } 1166 setViewStatesLocked(response, dataset, state); 1167 } 1168 } 1169 final SaveInfo saveInfo = response.getSaveInfo(); 1170 if (saveInfo != null) { 1171 final AutofillId[] requiredIds = saveInfo.getRequiredIds(); 1172 for (int i = 0; i < requiredIds.length; i++) { 1173 final AutofillId id = requiredIds[i]; 1174 createOrUpdateViewStateLocked(id, state, null); 1175 } 1176 final AutofillId[] optionalIds = saveInfo.getOptionalIds(); 1177 if (optionalIds != null) { 1178 for (int i = 0; i < optionalIds.length; i++) { 1179 final AutofillId id = optionalIds[i]; 1180 createOrUpdateViewStateLocked(id, state, null); 1181 } 1182 } 1183 } 1184 } 1185 1186 /** 1187 * Sets the state of all views in the given dataset and response. 1188 */ 1189 private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset, 1190 int state) { 1191 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 1192 final ArrayList<AutofillValue> values = dataset.getFieldValues(); 1193 for (int j = 0; j < ids.size(); j++) { 1194 final AutofillId id = ids.get(j); 1195 final AutofillValue value = values.get(j); 1196 final ViewState viewState = createOrUpdateViewStateLocked(id, state, value); 1197 if (response != null) { 1198 viewState.setResponse(response); 1199 } 1200 } 1201 } 1202 1203 private ViewState createOrUpdateViewStateLocked(AutofillId id, int state,AutofillValue value) { 1204 ViewState viewState = mViewStates.get(id); 1205 if (viewState != null) { 1206 viewState.setState(state); 1207 } else { 1208 viewState = new ViewState(this, id, null, this, state); 1209 if (DEBUG) { // TODO(b/33197203): change to VERBOSE once stable 1210 Slog.d(TAG, "Adding autofillable view with id " + id + " and state " + state); 1211 } 1212 mViewStates.put(id, viewState); 1213 } 1214 if ((state & ViewState.STATE_AUTOFILLED) != 0) { 1215 viewState.setAutofilledValue(value); 1216 } 1217 return viewState; 1218 } 1219 1220 /** 1221 * Resets the given state from all existing views in the given dataset. 1222 */ 1223 private void resetViewStatesLocked(@NonNull Dataset dataset, int state) { 1224 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 1225 for (int j = 0; j < ids.size(); j++) { 1226 final AutofillId id = ids.get(j); 1227 final ViewState viewState = mViewStates.get(id); 1228 if (viewState != null) { 1229 viewState.resetState(state); 1230 } 1231 } 1232 } 1233 1234 void autoFill(int requestId, Dataset dataset) { 1235 synchronized (mLock) { 1236 if (mDestroyed) { 1237 Slog.w(TAG, "Call to Session#autoFill() rejected - session: " 1238 + mId + " destroyed"); 1239 return; 1240 } 1241 // Autofill it directly... 1242 if (dataset.getAuthentication() == null) { 1243 mService.setDatasetSelected(dataset.getId()); 1244 1245 autoFillApp(dataset); 1246 return; 1247 } 1248 1249 // ...or handle authentication. 1250 // TODO(b/37424539): proper implementation 1251 mService.setDatasetAuthenticationSelected(dataset.getId()); 1252 mDatasetWaitingAuth = dataset; 1253 setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH); 1254 final Intent fillInIntent = createAuthFillInIntent( 1255 getFillContextByRequestIdLocked(requestId).getStructure(), null); 1256 startAuthentication(dataset.getAuthentication(), fillInIntent); 1257 } 1258 } 1259 1260 CharSequence getServiceName() { 1261 synchronized (mLock) { 1262 return mService.getServiceName(); 1263 } 1264 } 1265 1266 FillResponse getResponseWaitingAuth() { 1267 synchronized (mLock) { 1268 return mResponseWaitingAuth; 1269 } 1270 } 1271 1272 private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) { 1273 final Intent fillInIntent = new Intent(); 1274 fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure); 1275 if (extras != null) { 1276 fillInIntent.putExtra(AutofillManager.EXTRA_DATA_EXTRAS, extras); 1277 } 1278 return fillInIntent; 1279 } 1280 1281 private void startAuthentication(IntentSender intent, Intent fillInIntent) { 1282 try { 1283 synchronized (mLock) { 1284 mClient.authenticate(mId, intent, fillInIntent); 1285 } 1286 } catch (RemoteException e) { 1287 Slog.e(TAG, "Error launching auth intent", e); 1288 } 1289 } 1290 1291 void dumpLocked(String prefix, PrintWriter pw) { 1292 pw.print(prefix); pw.print("id: "); pw.println(mId); 1293 pw.print(prefix); pw.print("uid: "); pw.println(uid); 1294 pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); 1295 pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags); 1296 pw.print(prefix); pw.print("mResponses: "); pw.println(mResponses); 1297 pw.print(prefix); pw.print("mResponseWaitingAuth: "); pw.println(mResponseWaitingAuth); 1298 pw.print(prefix); pw.print("mDatasetWaitingAuth: "); pw.println(mDatasetWaitingAuth); 1299 pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); 1300 pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); 1301 pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); 1302 final String prefix2 = prefix + " "; 1303 for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { 1304 pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey()); 1305 entry.getValue().dump(prefix2, pw); 1306 } 1307 1308 pw.print(prefix); pw.print("mContexts: " ); 1309 if (mContexts != null) { 1310 int numContexts = mContexts.size(); 1311 for (int i = 0; i < numContexts; i++) { 1312 FillContext context = mContexts.get(i); 1313 1314 pw.print(prefix2); pw.print(context); 1315 if (VERBOSE) { 1316 pw.println(context.getStructure() + " (look at logcat)"); 1317 1318 // TODO: add method on AssistStructure to dump on pw 1319 context.getStructure().dump(); 1320 } 1321 } 1322 } else { 1323 pw.println("null"); 1324 } 1325 1326 pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); 1327 pw.print(prefix); pw.print("mClientState: "); pw.println( 1328 Helper.bundleToString(mClientState)); 1329 mRemoteFillService.dump(prefix, pw); 1330 } 1331 1332 void autoFillApp(Dataset dataset) { 1333 synchronized (mLock) { 1334 if (mDestroyed) { 1335 Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: " 1336 + mId + " destroyed"); 1337 return; 1338 } 1339 try { 1340 if (DEBUG) { 1341 Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); 1342 } 1343 mClient.autofill(mId, mWindowToken, dataset.getFieldIds(), 1344 dataset.getFieldValues()); 1345 setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED); 1346 } catch (RemoteException e) { 1347 Slog.w(TAG, "Error autofilling activity: " + e); 1348 } 1349 } 1350 } 1351 1352 private AutoFillUI getUiForShowing() { 1353 synchronized (mLock) { 1354 mUi.setCallback(this); 1355 return mUi; 1356 } 1357 } 1358 1359 void destroyLocked() { 1360 if (mDestroyed) { 1361 return; 1362 } 1363 mRemoteFillService.destroy(); 1364 mUi.hideAll(); 1365 mUi.setCallback(null); 1366 mDestroyed = true; 1367 mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName); 1368 } 1369 1370 private void removeSelf() { 1371 synchronized (mLock) { 1372 removeSelfLocked(); 1373 } 1374 } 1375 1376 void removeSelfLocked() { 1377 if (VERBOSE) { 1378 Slog.v(TAG, "removeSelfLocked()"); 1379 } 1380 if (mDestroyed) { 1381 Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: " 1382 + mId + " destroyed"); 1383 return; 1384 } 1385 destroyLocked(); 1386 mService.removeSessionLocked(mId); 1387 } 1388 1389 private int getLastResponseIndex() { 1390 // The response ids are monotonically increasing so 1391 // we just find the largest id which is the last. We 1392 // do not rely on the internal ordering in sparse 1393 // array to avoid - wow this stopped working!? 1394 int lastResponseIdx = -1; 1395 int lastResponseId = -1; 1396 final int responseCount = mResponses.size(); 1397 for (int i = 0; i < responseCount; i++) { 1398 if (mResponses.keyAt(i) > lastResponseId) { 1399 lastResponseIdx = i; 1400 } 1401 } 1402 return lastResponseIdx; 1403 } 1404} 1405