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