AutofillManagerServiceImpl.java revision 9668903731c272e51ce610598c052ef411c9d89f
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 17package com.android.server.autofill; 18 19import static android.service.autofill.AutofillService.EXTRA_ACTIVITY_TOKEN; 20import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS; 21import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; 22import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED; 23import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED; 24import static android.view.autofill.AutofillManager.FLAG_START_SESSION; 25import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED; 26 27import static com.android.server.autofill.Helper.DEBUG; 28import static com.android.server.autofill.Helper.VERBOSE; 29import static com.android.server.autofill.Helper.findValue; 30 31import android.annotation.Nullable; 32import android.app.Activity; 33import android.app.ActivityManager; 34import android.app.AppGlobals; 35import android.app.assist.AssistStructure; 36import android.app.assist.AssistStructure.ViewNode; 37import android.app.assist.AssistStructure.WindowNode; 38import android.content.ComponentName; 39import android.content.Context; 40import android.content.Intent; 41import android.content.IntentSender; 42import android.content.pm.ApplicationInfo; 43import android.content.pm.PackageManager; 44import android.content.pm.ServiceInfo; 45import android.graphics.Rect; 46import android.os.Binder; 47import android.os.Bundle; 48import android.os.IBinder; 49import android.os.Looper; 50import android.os.Parcelable; 51import android.os.RemoteCallbackList; 52import android.os.RemoteException; 53import android.provider.Settings; 54import android.service.autofill.AutofillService; 55import android.service.autofill.AutofillServiceInfo; 56import android.service.autofill.Dataset; 57import android.service.autofill.FillResponse; 58import android.service.autofill.IAutoFillService; 59import android.service.autofill.SaveInfo; 60import android.text.TextUtils; 61import android.util.ArrayMap; 62import android.util.LocalLog; 63import android.util.Log; 64import android.util.PrintWriterPrinter; 65import android.util.Slog; 66import android.view.autofill.AutofillId; 67import android.view.autofill.AutofillManager; 68import android.view.autofill.AutofillValue; 69import android.view.autofill.IAutoFillManagerClient; 70import com.android.internal.annotations.GuardedBy; 71import com.android.internal.os.HandlerCaller; 72import com.android.internal.os.IResultReceiver; 73import com.android.server.autofill.ui.AutoFillUI; 74 75import java.io.PrintWriter; 76import java.util.ArrayList; 77import java.util.Map; 78import java.util.Map.Entry; 79 80/** 81 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the 82 * app's {@link IAutoFillService} implementation. 83 * 84 */ 85final class AutofillManagerServiceImpl { 86 87 private static final String TAG = "AutofillManagerServiceImpl"; 88 89 private static final int MSG_SERVICE_SAVE = 1; 90 91 private final int mUserId; 92 private final Context mContext; 93 private final Object mLock; 94 private final AutoFillUI mUi; 95 96 private RemoteCallbackList<IAutoFillManagerClient> mClients; 97 private AutofillServiceInfo mInfo; 98 99 private final LocalLog mRequestsHistory; 100 101 private final HandlerCaller.Callback mHandlerCallback = (msg) -> { 102 switch (msg.what) { 103 case MSG_SERVICE_SAVE: 104 handleSessionSave((IBinder) msg.obj); 105 break; 106 default: 107 Slog.w(TAG, "invalid msg on handler: " + msg); 108 } 109 }; 110 111 private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), 112 mHandlerCallback, true); 113 114 /** 115 * Cache of pending {@link Session}s, keyed by {@code activityToken}. 116 * 117 * <p>They're kept until the {@link AutofillService} finished handling a request, an error 118 * occurs, or the session times out. 119 */ 120 // TODO(b/33197203): need to make sure service is bound while callback is pending and/or 121 // use WeakReference 122 @GuardedBy("mLock") 123 private final ArrayMap<IBinder, Session> mSessions = new ArrayMap<>(); 124 125 /** 126 * Receiver of assist data from the app's {@link Activity}. 127 */ 128 private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { 129 @Override 130 public void send(int resultCode, Bundle resultData) throws RemoteException { 131 if (VERBOSE) { 132 Slog.v(TAG, "resultCode on mAssistReceiver: " + resultCode); 133 } 134 135 final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE); 136 if (structure == null) { 137 Slog.wtf(TAG, "no assist structure for id " + resultCode); 138 return; 139 } 140 141 final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS); 142 if (receiverExtras == null) { 143 Slog.wtf(TAG, "No " + KEY_RECEIVER_EXTRAS + " on receiver"); 144 return; 145 } 146 147 final IBinder activityToken = receiverExtras.getBinder(EXTRA_ACTIVITY_TOKEN); 148 final Session session; 149 synchronized (mLock) { 150 session = mSessions.get(activityToken); 151 if (session == null) { 152 Slog.w(TAG, "no server session for activityToken " + activityToken); 153 return; 154 } 155 // TODO(b/33197203): since service is fetching the data (to use for save later), 156 // we should optimize what's sent (for example, remove layout containers, 157 // color / font info, etc...) 158 session.mStructure = structure; 159 } 160 161 162 // TODO(b/33197203, b/33269702): Must fetch the data so it's available later on 163 // handleSave(), even if if the activity is gone by then, but structure.ensureData() 164 // gives a ONE_WAY warning because system_service could block on app calls. 165 // We need to change AssistStructure so it provides a "one-way" writeToParcel() 166 // method that sends all the data 167 structure.ensureData(); 168 169 // Sanitize structure before it's sent to service. 170 structure.sanitizeForParceling(true); 171 172 // TODO(b/33197203): Need to pipe the bundle 173 session.mRemoteFillService.onFillRequest(structure, null); 174 } 175 }; 176 177 AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, 178 int userId, AutoFillUI ui) { 179 mContext = context; 180 mLock = lock; 181 mRequestsHistory = requestsHistory; 182 mUserId = userId; 183 mUi = ui; 184 updateLocked(); 185 } 186 187 CharSequence getServiceName() { 188 if (mInfo == null) { 189 return null; 190 } 191 final ComponentName serviceComponent = mInfo.getServiceInfo().getComponentName(); 192 final String packageName = serviceComponent.getPackageName(); 193 194 try { 195 final PackageManager pm = mContext.getPackageManager(); 196 final ApplicationInfo info = pm.getApplicationInfo(packageName, 0); 197 return pm.getApplicationLabel(info); 198 } catch (Exception e) { 199 Slog.e(TAG, "Could not get label for " + packageName + ": " + e); 200 return packageName; 201 } 202 } 203 204 void updateLocked() { 205 ComponentName serviceComponent = null; 206 ServiceInfo serviceInfo = null; 207 final String componentName = Settings.Secure.getStringForUser( 208 mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); 209 if (!TextUtils.isEmpty(componentName)) { 210 try { 211 serviceComponent = ComponentName.unflattenFromString(componentName); 212 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 213 0, mUserId); 214 } catch (RuntimeException | RemoteException e) { 215 Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e); 216 return; 217 } 218 } 219 try { 220 final boolean hadService = hasService(); 221 if (serviceInfo != null) { 222 mInfo = new AutofillServiceInfo(mContext.getPackageManager(), 223 serviceComponent, mUserId); 224 } else { 225 mInfo = null; 226 } 227 if (hadService != hasService()) { 228 if (!hasService()) { 229 final int sessionCount = mSessions.size(); 230 for (int i = sessionCount - 1; i >= 0; i--) { 231 Session session = mSessions.valueAt(i); 232 session.destroyLocked(); 233 mSessions.removeAt(i); 234 } 235 } 236 sendStateToClients(); 237 } 238 } catch (PackageManager.NameNotFoundException e) { 239 Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e); 240 } 241 } 242 243 /** 244 * Used by {@link AutofillManagerServiceShellCommand} to request save for the current top app. 245 */ 246 void requestSaveForUserLocked(IBinder activityToken) { 247 if (!hasService()) { 248 return; 249 } 250 final Session session = mSessions.get(activityToken); 251 if (session == null) { 252 Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken); 253 return; 254 } 255 256 session.callSaveLocked(); 257 } 258 259 boolean addClientLocked(IAutoFillManagerClient client) { 260 if (mClients == null) { 261 mClients = new RemoteCallbackList<>(); 262 } 263 mClients.register(client); 264 return hasService(); 265 } 266 267 void setAuthenticationResultLocked(Bundle data, IBinder activityToken) { 268 if (!hasService()) { 269 return; 270 } 271 final Session session = mSessions.get(activityToken); 272 if (session != null) { 273 session.setAuthenticationResultLocked(data); 274 } 275 } 276 277 void setHasCallback(IBinder activityToken, boolean hasIt) { 278 if (!hasService()) { 279 return; 280 } 281 final Session session = mSessions.get(activityToken); 282 if (session != null) { 283 session.setHasCallback(hasIt); 284 } 285 } 286 287 void startSessionLocked(IBinder activityToken, IBinder windowToken, IBinder appCallbackToken, 288 AutofillId autofillId, Rect bounds, AutofillValue value, boolean hasCallback) { 289 if (!hasService()) { 290 return; 291 } 292 293 final String historyItem = "s=" + mInfo.getServiceInfo().packageName 294 + " u=" + mUserId + " a=" + activityToken 295 296 + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback; 297 mRequestsHistory.log(historyItem); 298 299 // TODO(b/33197203): Handle partitioning 300 final Session session = mSessions.get(activityToken); 301 if (session != null) { 302 // Already started... 303 return; 304 } 305 306 final Session newSession = createSessionByTokenLocked(activityToken, 307 windowToken, appCallbackToken, hasCallback); 308 newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION); 309 } 310 311 void finishSessionLocked(IBinder activityToken) { 312 if (!hasService()) { 313 return; 314 } 315 316 final Session session = mSessions.get(activityToken); 317 if (session == null) { 318 Slog.w(TAG, "finishSessionLocked(): no session for " + activityToken); 319 return; 320 } 321 322 session.showSaveLocked(); 323 } 324 325 void cancelSessionLocked(IBinder activityToken) { 326 if (!hasService()) { 327 return; 328 } 329 330 final Session session = mSessions.get(activityToken); 331 if (session == null) { 332 Slog.w(TAG, "cancelSessionLocked(): no session for " + activityToken); 333 return; 334 } 335 336 session.destroyLocked(); 337 } 338 339 private Session createSessionByTokenLocked(IBinder activityToken, IBinder windowToken, 340 IBinder appCallbackToken, boolean hasCallback) { 341 final Session newSession = new Session(mContext, activityToken, 342 windowToken, appCallbackToken, hasCallback); 343 mSessions.put(activityToken, newSession); 344 345 /* 346 * TODO(b/33197203): apply security checks below: 347 * - checks if disabled by secure settings / device policy 348 * - log operation using noteOp() 349 * - check flags 350 * - display disclosure if needed 351 */ 352 try { 353 // TODO(b/33197203): add MetricsLogger call 354 final Bundle receiverExtras = new Bundle(); 355 receiverExtras.putBinder(EXTRA_ACTIVITY_TOKEN, activityToken); 356 final long identity = Binder.clearCallingIdentity(); 357 try { 358 if (!ActivityManager.getService().requestAutofillData(mAssistReceiver, 359 receiverExtras, activityToken)) { 360 Slog.w(TAG, "failed to request autofill data for " + activityToken); 361 } 362 } finally { 363 Binder.restoreCallingIdentity(identity); 364 } 365 } catch (RemoteException e) { 366 // Should not happen, it's a local call. 367 } 368 return newSession; 369 } 370 371 void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect bounds, 372 AutofillValue value, int flags) { 373 // TODO(b/33197203): add MetricsLogger call 374 final Session session = mSessions.get(activityToken); 375 if (session == null) { 376 if (VERBOSE) { 377 Slog.v(TAG, "updateSessionLocked(): session gone for " + activityToken); 378 } 379 return; 380 } 381 382 session.updateLocked(autofillId, bounds, value, flags); 383 } 384 385 private void handleSessionSave(IBinder activityToken) { 386 synchronized (mLock) { 387 final Session session = mSessions.get(activityToken); 388 if (session == null) { 389 Slog.w(TAG, "handleSessionSave(): already gone: " + activityToken); 390 391 return; 392 } 393 session.callSaveLocked(); 394 } 395 } 396 397 void destroyLocked() { 398 if (VERBOSE) { 399 Slog.v(TAG, "destroyLocked()"); 400 } 401 402 for (Session session : mSessions.values()) { 403 session.destroyLocked(); 404 } 405 mSessions.clear(); 406 } 407 408 void dumpLocked(String prefix, PrintWriter pw) { 409 final String prefix2 = prefix + " "; 410 411 pw.print(prefix); pw.print("Component:"); pw.println(mInfo != null 412 ? mInfo.getServiceInfo().getComponentName() : null); 413 414 if (VERBOSE) { 415 // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps) 416 pw.print(prefix); pw.println("ServiceInfo:"); 417 mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix); 418 } 419 420 final int size = mSessions.size(); 421 if (size == 0) { 422 pw.print(prefix); pw.println("No sessions"); 423 } else { 424 pw.print(prefix); pw.print(size); pw.println(" sessions:"); 425 for (int i = 0; i < size; i++) { 426 pw.print(prefix); pw.print("#"); pw.println(i + 1); 427 mSessions.valueAt(i).dumpLocked(prefix2, pw); 428 } 429 } 430 } 431 432 void destroySessionsLocked() { 433 for (Session session : mSessions.values()) { 434 session.removeSelf(); 435 } 436 } 437 438 void listSessionsLocked(ArrayList<String> output) { 439 for (IBinder activityToken : mSessions.keySet()) { 440 output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName() 441 : null) + ":" + activityToken); 442 } 443 } 444 445 private void sendStateToClients() { 446 final RemoteCallbackList<IAutoFillManagerClient> clients; 447 final int userClientCount; 448 synchronized (mLock) { 449 if (mClients == null) { 450 return; 451 } 452 clients = mClients; 453 userClientCount = clients.beginBroadcast(); 454 } 455 try { 456 for (int i = 0; i < userClientCount; i++) { 457 IAutoFillManagerClient client = clients.getBroadcastItem(i); 458 try { 459 client.setState(hasService()); 460 } catch (RemoteException re) { 461 /* ignore */ 462 } 463 } 464 } finally { 465 clients.finishBroadcast(); 466 } 467 } 468 469 private boolean hasService() { 470 return mInfo != null; 471 } 472 473 @Override 474 public String toString() { 475 return "AutofillManagerServiceImpl: [userId=" + mUserId 476 + ", component=" + (mInfo != null 477 ? mInfo.getServiceInfo().getComponentName() : null) + "]"; 478 } 479 480 /** 481 * State for a given view with a AutofillId. 482 * 483 * <p>This class holds state about a view and calls its listener when the fill UI is ready to 484 * be displayed for the view. 485 */ 486 static final class ViewState { 487 interface Listener { 488 /** 489 * Called when the fill UI is ready to be shown for this view. 490 */ 491 void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds, 492 AutofillId focusedId, @Nullable AutofillValue value); 493 } 494 495 final AutofillId mId; 496 private final Listener mListener; 497 // TODO(b/33197203): would not need a reference to response and session if it was an inner 498 // class of Session... 499 private final Session mSession; 500 // TODO(b/33197203): encapsulate access so it's not called by UI 501 FillResponse mResponse; 502 Intent mAuthIntent; 503 504 private AutofillValue mAutofillValue; 505 private Rect mBounds; 506 507 private boolean mValueUpdated; 508 509 ViewState(Session session, AutofillId id, Listener listener) { 510 mSession = session; 511 mId = id; 512 mListener = listener; 513 } 514 515 /** 516 * Response should only be set once. 517 */ 518 void setResponse(FillResponse response) { 519 mResponse = response; 520 maybeCallOnFillReady(); 521 } 522 523 /** 524 * Used when a {@link FillResponse} requires authentication to be unlocked. 525 */ 526 void setResponse(FillResponse response, Intent authIntent) { 527 mAuthIntent = authIntent; 528 setResponse(response); 529 } 530 531 CharSequence getServiceName() { 532 return mSession.getServiceName(); 533 } 534 535 // TODO(b/33197203): need to refactor / rename / document this method to make it clear that 536 // it can change the value and update the UI; similarly, should replace code that 537 // directly sets mAutoFilLValue to use encapsulation. 538 void update(@Nullable AutofillValue autofillValue, @Nullable Rect bounds) { 539 if (autofillValue != null) { 540 mAutofillValue = autofillValue; 541 } 542 if (bounds != null) { 543 mBounds = bounds; 544 } 545 546 maybeCallOnFillReady(); 547 } 548 549 /** 550 * Calls {@link 551 * Listener#onFillReady(ViewState, FillResponse, Rect, AutofillId, AutofillValue)} if the 552 * fill UI is ready to be displayed (i.e. when response and bounds are set). 553 */ 554 void maybeCallOnFillReady() { 555 if (mResponse != null && (mResponse.getAuthentication() != null 556 || mResponse.getDatasets() != null) && mBounds != null) { 557 mListener.onFillReady(this, mResponse, mBounds, mId, mAutofillValue); 558 } 559 } 560 561 @Override 562 public String toString() { 563 return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mBounds 564 + ", updated = " + mValueUpdated + "]"; 565 } 566 567 void dump(String prefix, PrintWriter pw) { 568 pw.print(prefix); pw.print("id:" ); pw.println(mId); 569 pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue); 570 pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated); 571 pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds); 572 pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent); 573 } 574 } 575 576 /** 577 * A session for a given activity. 578 * 579 * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track 580 * of the current {@link ViewState} to display the appropriate UI. 581 * 582 * <p>Although the autofill requests and callbacks are stateless from the service's point of 583 * view, we need to keep state in the framework side for cases such as authentication. For 584 * example, when service return a {@link FillResponse} that contains all the fields needed 585 * to fill the activity but it requires authentication first, that response need to be held 586 * until the user authenticates or it times out. 587 */ 588 // TODO(b/33197203): make sure sessions are removed (and tested by CTS): 589 // - On all authentication scenarios. 590 // - When user does not interact back after a while. 591 // - When service is unbound. 592 final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, 593 AutoFillUI.AutoFillUiCallback { 594 private final IBinder mActivityToken; 595 private final IBinder mWindowToken; 596 597 @GuardedBy("mLock") 598 private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>(); 599 600 @GuardedBy("mLock") 601 @Nullable 602 private ViewState mCurrentViewState; 603 604 private final IAutoFillManagerClient mClient; 605 606 @GuardedBy("mLock") 607 RemoteFillService mRemoteFillService; 608 609 // TODO(b/33197203): Get a response per view instead of per activity. 610 @GuardedBy("mLock") 611 private FillResponse mCurrentResponse; 612 613 /** 614 * Used to remember which {@link Dataset} filled the session. 615 */ 616 // TODO(b/33197203): might need more than one once we support partitions 617 @GuardedBy("mLock") 618 private Dataset mAutoFilledDataset; 619 620 /** 621 * Assist structure sent by the app; it will be updated (sanitized, change values for save) 622 * before sent to {@link AutofillService}. 623 */ 624 @GuardedBy("mLock") 625 private AssistStructure mStructure; 626 627 /** 628 * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. 629 */ 630 private boolean mHasCallback; 631 632 private Session(Context context, IBinder activityToken, IBinder windowToken, 633 IBinder client, boolean hasCallback) { 634 mRemoteFillService = new RemoteFillService(context, 635 mInfo.getServiceInfo().getComponentName(), mUserId, this); 636 mActivityToken = activityToken; 637 mWindowToken = windowToken; 638 mHasCallback = hasCallback; 639 640 mClient = IAutoFillManagerClient.Stub.asInterface(client); 641 try { 642 client.linkToDeath(() -> { 643 if (DEBUG) { 644 Slog.d(TAG, "app binder died"); 645 } 646 647 removeSelf(); 648 }, 0); 649 } catch (RemoteException e) { 650 Slog.w(TAG, "linkToDeath() on mClient failed: " + e); 651 } 652 } 653 654 // FillServiceCallbacks 655 @Override 656 public void onFillRequestSuccess(FillResponse response) { 657 // TODO(b/33197203): add MetricsLogger call 658 if (response == null) { 659 removeSelf(); 660 return; 661 } 662 synchronized (mLock) { 663 processResponseLocked(response); 664 } 665 } 666 667 // FillServiceCallbacks 668 @Override 669 public void onFillRequestFailure(CharSequence message) { 670 // TODO(b/33197203): add MetricsLogger call 671 getUiForShowing().showError(message); 672 removeSelf(); 673 } 674 675 // FillServiceCallbacks 676 @Override 677 public void onSaveRequestSuccess() { 678 // TODO(b/33197203): add MetricsLogger call 679 // Nothing left to do... 680 removeSelf(); 681 } 682 683 // FillServiceCallbacks 684 @Override 685 public void onSaveRequestFailure(CharSequence message) { 686 // TODO(b/33197203): add MetricsLogger call 687 getUiForShowing().showError(message); 688 removeSelf(); 689 } 690 691 // FillServiceCallbacks 692 @Override 693 public void authenticate(IntentSender intent) { 694 final Intent fillInIntent; 695 synchronized (mLock) { 696 fillInIntent = createAuthFillInIntent(mStructure); 697 } 698 mHandlerCaller.getHandler().post(() -> { 699 startAuthentication(intent, fillInIntent); 700 }); 701 } 702 703 // FillServiceCallbacks 704 @Override 705 public void onDisableSelf() { 706 final long identity = Binder.clearCallingIdentity(); 707 try { 708 final String autoFillService = Settings.Secure.getStringForUser( 709 mContext.getContentResolver(), 710 Settings.Secure.AUTOFILL_SERVICE, mUserId); 711 if (mInfo.getServiceInfo().getComponentName().equals( 712 ComponentName.unflattenFromString(autoFillService))) { 713 Settings.Secure.putStringForUser(mContext.getContentResolver(), 714 Settings.Secure.AUTOFILL_SERVICE, null, mUserId); 715 } 716 } finally { 717 Binder.restoreCallingIdentity(identity); 718 } 719 synchronized (mLock) { 720 destroyLocked(); 721 mSessions.remove(this); 722 } 723 } 724 725 // FillServiceCallbacks 726 @Override 727 public void onServiceDied(RemoteFillService service) { 728 // TODO(b/33197203): implement 729 } 730 731 // AutoFillUiCallback 732 @Override 733 public void fill(Dataset dataset) { 734 mHandlerCaller.getHandler().post(() -> { 735 autoFill(dataset); 736 }); 737 } 738 739 // AutoFillUiCallback 740 @Override 741 public void save() { 742 mHandlerCaller.getHandler().obtainMessage(MSG_SERVICE_SAVE, mActivityToken) 743 .sendToTarget(); 744 } 745 746 // AutoFillUiCallback 747 @Override 748 public void cancelSave() { 749 mHandlerCaller.getHandler().post(() -> { 750 removeSelf(); 751 }); 752 } 753 754 // AutoFillUiCallback 755 @Override 756 public void onEvent(AutofillId id, int event) { 757 mHandlerCaller.getHandler().post(() -> { 758 notifyChangeToClient(id, event); 759 }); 760 } 761 762 public void setAuthenticationResultLocked(Bundle data) { 763 if (mCurrentResponse == null || data == null) { 764 removeSelf(); 765 } else { 766 Parcelable result = data.getParcelable( 767 AutofillManager.EXTRA_AUTHENTICATION_RESULT); 768 if (result instanceof FillResponse) { 769 mCurrentResponse = (FillResponse) result; 770 processResponseLocked(mCurrentResponse); 771 } else if (result instanceof Dataset) { 772 Dataset dataset = (Dataset) result; 773 mCurrentResponse.getDatasets().remove(mAutoFilledDataset); 774 mCurrentResponse.getDatasets().add(dataset); 775 mAutoFilledDataset = dataset; 776 processResponseLocked(mCurrentResponse); 777 } 778 } 779 } 780 781 public void setHasCallback(boolean hasIt) { 782 mHasCallback = hasIt; 783 } 784 785 /** 786 * Show the save UI, when session can be saved. 787 */ 788 public void showSaveLocked() { 789 if (mStructure == null) { 790 Slog.wtf(TAG, "showSaveLocked(): no mStructure"); 791 return; 792 } 793 if (mCurrentResponse == null) { 794 // Happens when the activity / session was finished before the service replied, or 795 // when the service cannot autofill it (and returned a null response). 796 if (DEBUG) { 797 Slog.d(TAG, "showSaveLocked(): no mCurrentResponse"); 798 } 799 return; 800 } 801 final SaveInfo saveInfo = mCurrentResponse.getSaveInfo(); 802 if (DEBUG) { 803 Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo); 804 } 805 806 if (saveInfo == null || saveInfo.getSavableIds() == null 807 || saveInfo.getSavableIds().isEmpty()) { 808 return; 809 } 810 811 final int size = saveInfo.getSavableIds().size(); 812 for (int i = 0; i < size; i++) { 813 final AutofillId id = saveInfo.getSavableIds().valueAt(i); 814 final ViewState state = mViewStates.get(id); 815 if (state != null && state.mValueUpdated) { 816 final AutofillValue filledValue = findValue(mAutoFilledDataset, id); 817 if (state.mAutofillValue == null || state.mAutofillValue.equals(filledValue)) { 818 continue; 819 } 820 if (DEBUG) { 821 Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": " 822 + state.mAutofillValue); 823 } 824 getUiForShowing().showSaveUi( 825 mInfo.getServiceInfo().loadLabel(mContext.getPackageManager()), 826 saveInfo); 827 return; 828 } 829 } 830 831 // Nothing changed... 832 if (DEBUG) { 833 Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities"); 834 } 835 } 836 837 /** 838 * Calls service when user requested save. 839 */ 840 private void callSaveLocked() { 841 if (DEBUG) { 842 Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates); 843 } 844 845 final Bundle extras = this.mCurrentResponse.getExtras(); 846 847 for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { 848 final AutofillValue value = entry.getValue().mAutofillValue; 849 if (value == null) { 850 if (VERBOSE) { 851 Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey()); 852 } 853 continue; 854 } 855 final AutofillId id = entry.getKey(); 856 final ViewNode node = findViewNodeByIdLocked(id); 857 if (node == null) { 858 Slog.w(TAG, "callSaveLocked(): did not find node with id " + id); 859 continue; 860 } 861 if (VERBOSE) { 862 Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value); 863 } 864 865 node.updateAutofillValue(value); 866 } 867 868 // Sanitize structure before it's sent to service. 869 mStructure.sanitizeForParceling(false); 870 871 if (VERBOSE) { 872 Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()"); 873 mStructure.dump(); 874 } 875 876 mRemoteFillService.onSaveRequest(mStructure, extras); 877 } 878 879 void updateLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) { 880 if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) { 881 // TODO(b/33197203): ignoring because we don't support partitions yet 882 Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled"); 883 return; 884 } 885 886 ViewState viewState = mViewStates.get(id); 887 if (viewState == null) { 888 viewState = new ViewState(this, id, this); 889 mViewStates.put(id, viewState); 890 } 891 892 if ((flags & FLAG_START_SESSION) != 0) { 893 // View is triggering autofill. 894 mCurrentViewState = viewState; 895 viewState.update(value, bounds); 896 return; 897 } 898 899 if ((flags & FLAG_VALUE_CHANGED) != 0) { 900 if (value != null && !value.equals(viewState.mAutofillValue)) { 901 viewState.mValueUpdated = true; 902 903 // Must check if this update was caused by autofilling the view, in which 904 // case we just update the value, but not the UI. 905 if (mAutoFilledDataset != null) { 906 final AutofillValue filledValue = findValue(mAutoFilledDataset, id); 907 if (value.equals(filledValue)) { 908 viewState.mAutofillValue = value; 909 return; 910 } 911 } 912 913 // Change value 914 viewState.mAutofillValue = value; 915 916 // Update the chooser UI 917 getUiForShowing().filterFillUi(value.coerceToString()); 918 } 919 920 return; 921 } 922 923 if ((flags & FLAG_VIEW_ENTERED) != 0) { 924 // Remove the UI if the ViewState has changed. 925 if (mCurrentViewState != viewState) { 926 mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null); 927 mCurrentViewState = viewState; 928 } 929 930 // If the ViewState is ready to be displayed, onReady() will be called. 931 viewState.update(value, bounds); 932 933 // TODO(b/33197203): Remove when there is a response per activity. 934 if (mCurrentResponse != null) { 935 viewState.setResponse(mCurrentResponse); 936 } 937 938 return; 939 } 940 941 if ((flags & FLAG_VIEW_EXITED) != 0) { 942 if (mCurrentViewState == viewState) { 943 mUi.hideFillUi(viewState.mId); 944 mCurrentViewState = null; 945 } 946 return; 947 } 948 949 Slog.w(TAG, "updateLocked(): unknown flags " + flags); 950 } 951 952 @Override 953 public void onFillReady(ViewState viewState, FillResponse response, Rect bounds, 954 AutofillId filledId, @Nullable AutofillValue value) { 955 String filterText = ""; 956 if (value != null) { 957 // TODO(b/33197203): Handle other AutofillValue types 958 if (value.isText()) { 959 filterText = value.getTextValue().toString(); 960 } else { 961 Log.w(TAG, value + " could not be autofilled into " + this); 962 } 963 } 964 965 getUiForShowing().showFillUi(filledId, response, bounds, filterText); 966 } 967 968 private void notifyChangeToClient(AutofillId id, int event) { 969 if (!mHasCallback) return; 970 try { 971 mClient.onAutofillEvent(mWindowToken, id, event); 972 } catch (RemoteException e) { 973 Slog.e(TAG, "Error notifying client on change: id=" + id + ", event=" + event, e); 974 } 975 } 976 977 private void processResponseLocked(FillResponse response) { 978 if (DEBUG) { 979 Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication() 980 + "):" + response); 981 } 982 983 // TODO(b/33197203): add MetricsLogger calls 984 985 if (mCurrentViewState == null) { 986 // TODO(b/33197203): temporary sanity check; should never happen 987 Slog.w(TAG, "processResponseLocked(): mCurrentResponse is null"); 988 return; 989 } 990 991 mCurrentResponse = response; 992 993 if (mCurrentResponse.getAuthentication() != null) { 994 // Handle authentication. 995 final Intent fillInIntent = createAuthFillInIntent(mStructure); 996 mCurrentViewState.setResponse(mCurrentResponse, fillInIntent); 997 return; 998 } 999 1000 mCurrentViewState.setResponse(mCurrentResponse); 1001 } 1002 1003 void autoFill(Dataset dataset) { 1004 synchronized (mLock) { 1005 mAutoFilledDataset = dataset; 1006 1007 // Autofill it directly... 1008 if (dataset.getAuthentication() == null) { 1009 autoFillApp(dataset); 1010 return; 1011 } 1012 1013 // ...or handle authentication. 1014 Intent fillInIntent = createAuthFillInIntent(mStructure); 1015 startAuthentication(dataset.getAuthentication(), fillInIntent); 1016 } 1017 } 1018 1019 CharSequence getServiceName() { 1020 return AutofillManagerServiceImpl.this.getServiceName(); 1021 } 1022 1023 private Intent createAuthFillInIntent(AssistStructure structure) { 1024 Intent fillInIntent = new Intent(); 1025 fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure); 1026 return fillInIntent; 1027 } 1028 1029 private void startAuthentication(IntentSender intent, Intent fillInIntent) { 1030 try { 1031 mClient.authenticate(intent, fillInIntent); 1032 } catch (RemoteException e) { 1033 Slog.e(TAG, "Error launching auth intent", e); 1034 } 1035 } 1036 1037 void dumpLocked(String prefix, PrintWriter pw) { 1038 pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); 1039 pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse); 1040 pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset); 1041 pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState); 1042 pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size()); 1043 final String prefix2 = prefix + " "; 1044 for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { 1045 pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey()); 1046 entry.getValue().dump(prefix2, pw); 1047 } 1048 if (VERBOSE) { 1049 pw.print(prefix); pw.print("mStructure: " ); 1050 // TODO(b/33197203): add method do dump AssistStructure on pw 1051 if (mStructure != null) { 1052 pw.println("look at logcat" ); 1053 mStructure.dump(); // dumps to logcat 1054 } else { 1055 pw.println("null"); 1056 } 1057 } 1058 pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); 1059 mRemoteFillService.dump(prefix, pw); 1060 } 1061 1062 void autoFillApp(Dataset dataset) { 1063 synchronized (mLock) { 1064 try { 1065 if (DEBUG) { 1066 Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); 1067 } 1068 mClient.autofill(dataset.getFieldIds(), dataset.getFieldValues()); 1069 } catch (RemoteException e) { 1070 Slog.w(TAG, "Error autofilling activity: " + e); 1071 } 1072 } 1073 } 1074 1075 private AutoFillUI getUiForShowing() { 1076 synchronized (mLock) { 1077 mUi.setCallback(this, mWindowToken); 1078 return mUi; 1079 } 1080 } 1081 1082 private ViewNode findViewNodeByIdLocked(AutofillId id) { 1083 final int size = mStructure.getWindowNodeCount(); 1084 for (int i = 0; i < size; i++) { 1085 final WindowNode window = mStructure.getWindowNodeAt(i); 1086 final ViewNode root = window.getRootViewNode(); 1087 if (id.equals(root.getAutofillId())) { 1088 return root; 1089 } 1090 final ViewNode child = findViewNodeByIdLocked(root, id); 1091 if (child != null) { 1092 return child; 1093 } 1094 } 1095 return null; 1096 } 1097 1098 private ViewNode findViewNodeByIdLocked(ViewNode parent, AutofillId id) { 1099 final int childrenSize = parent.getChildCount(); 1100 if (childrenSize > 0) { 1101 for (int i = 0; i < childrenSize; i++) { 1102 final ViewNode child = parent.getChildAt(i); 1103 if (id.equals(child.getAutofillId())) { 1104 return child; 1105 } 1106 final ViewNode grandChild = findViewNodeByIdLocked(child, id); 1107 if (grandChild != null && id.equals(grandChild.getAutofillId())) { 1108 return grandChild; 1109 } 1110 } 1111 } 1112 return null; 1113 } 1114 1115 private void destroyLocked() { 1116 mRemoteFillService.destroy(); 1117 mUi.setCallback(null, null); 1118 } 1119 1120 private void removeSelf() { 1121 if (VERBOSE) { 1122 Slog.v(TAG, "removeSelf()"); 1123 } 1124 1125 synchronized (mLock) { 1126 destroyLocked(); 1127 mSessions.remove(mActivityToken); 1128 } 1129 } 1130 } 1131} 1132