AutofillManagerServiceImpl.java revision cc684ed41f17ccdce45a056fd4034efc35b213d5
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_SESSION_ID; 20import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS; 21import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; 22import static android.view.autofill.AutofillManager.FLAG_START_SESSION; 23import static android.view.autofill.AutofillManager.NO_SESSION; 24 25import static com.android.server.autofill.Helper.DEBUG; 26import static com.android.server.autofill.Helper.VERBOSE; 27 28import android.annotation.NonNull; 29import android.annotation.Nullable; 30import android.app.Activity; 31import android.app.ActivityManager; 32import android.app.AppGlobals; 33import android.app.assist.AssistStructure; 34import android.content.ComponentName; 35import android.content.Context; 36import android.content.pm.ApplicationInfo; 37import android.content.pm.PackageManager; 38import android.content.pm.ServiceInfo; 39import android.graphics.Rect; 40import android.os.Binder; 41import android.os.Bundle; 42import android.os.IBinder; 43import android.os.Looper; 44import android.os.RemoteCallbackList; 45import android.os.RemoteException; 46import android.os.UserHandle; 47import android.os.UserManager; 48import android.provider.Settings; 49import android.service.autofill.AutofillService; 50import android.service.autofill.AutofillServiceInfo; 51import android.service.autofill.FillEventHistory; 52import android.service.autofill.FillRequest; 53import android.service.autofill.FillResponse; 54import android.service.autofill.IAutoFillService; 55import android.text.TextUtils; 56import android.util.LocalLog; 57import android.util.Log; 58import android.util.PrintWriterPrinter; 59import android.util.Slog; 60import android.util.SparseArray; 61import android.view.autofill.AutofillId; 62import android.view.autofill.AutofillValue; 63import android.view.autofill.IAutoFillManagerClient; 64 65import com.android.internal.R; 66import com.android.internal.annotations.GuardedBy; 67import com.android.internal.os.HandlerCaller; 68import com.android.internal.os.IResultReceiver; 69import com.android.server.autofill.ui.AutoFillUI; 70 71import java.io.PrintWriter; 72import java.util.ArrayList; 73import java.util.Random; 74 75/** 76 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the 77 * app's {@link IAutoFillService} implementation. 78 * 79 */ 80final class AutofillManagerServiceImpl { 81 82 private static final String TAG = "AutofillManagerServiceImpl"; 83 private static final int MAX_SESSION_ID_CREATE_TRIES = 2048; 84 85 static final int MSG_SERVICE_SAVE = 1; 86 87 private final int mUserId; 88 private final Context mContext; 89 private final Object mLock; 90 private final AutoFillUI mUi; 91 92 private RemoteCallbackList<IAutoFillManagerClient> mClients; 93 private AutofillServiceInfo mInfo; 94 95 private static final Random sRandom = new Random(); 96 97 private final LocalLog mRequestsHistory; 98 /** 99 * Whether service was disabled for user due to {@link UserManager} restrictions. 100 */ 101 private boolean mDisabled; 102 103 private final HandlerCaller.Callback mHandlerCallback = (msg) -> { 104 switch (msg.what) { 105 case MSG_SERVICE_SAVE: 106 handleSessionSave(msg.arg1); 107 break; 108 default: 109 Slog.w(TAG, "invalid msg on handler: " + msg); 110 } 111 }; 112 113 private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), 114 mHandlerCallback, true); 115 116 /** 117 * Cache of pending {@link Session}s, keyed by {@code activityToken}. 118 * 119 * <p>They're kept until the {@link AutofillService} finished handling a request, an error 120 * occurs, or the session times out. 121 */ 122 // TODO(b/33197203): need to make sure service is bound while callback is pending and/or 123 // use WeakReference 124 @GuardedBy("mLock") 125 private final SparseArray<Session> mSessions = new SparseArray<>(); 126 127 /** The last selection */ 128 @GuardedBy("mLock") 129 private FillEventHistory mEventHistory; 130 131 /** 132 * Receiver of assist data from the app's {@link Activity}. 133 */ 134 private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { 135 @Override 136 public void send(int resultCode, Bundle resultData) throws RemoteException { 137 if (VERBOSE) { 138 Slog.v(TAG, "resultCode on mAssistReceiver: " + resultCode); 139 } 140 141 final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE); 142 if (structure == null) { 143 Slog.wtf(TAG, "no assist structure for id " + resultCode); 144 return; 145 } 146 147 final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS); 148 if (receiverExtras == null) { 149 Slog.wtf(TAG, "No " + KEY_RECEIVER_EXTRAS + " on receiver"); 150 return; 151 } 152 153 final int sessionId = receiverExtras.getInt(EXTRA_SESSION_ID); 154 final Session session; 155 synchronized (mLock) { 156 session = mSessions.get(sessionId); 157 if (session == null) { 158 Slog.w(TAG, "no server session for " + sessionId); 159 return; 160 } 161 // TODO(b/33197203): since service is fetching the data (to use for save later), 162 // we should optimize what's sent (for example, remove layout containers, 163 // color / font info, etc...) 164 session.setStructureLocked(structure); 165 } 166 167 168 // TODO(b/33197203, b/33269702): Must fetch the data so it's available later on 169 // handleSave(), even if if the activity is gone by then, but structure.ensureData() 170 // gives a ONE_WAY warning because system_service could block on app calls. 171 // We need to change AssistStructure so it provides a "one-way" writeToParcel() 172 // method that sends all the data 173 structure.ensureData(); 174 175 // Sanitize structure before it's sent to service. 176 structure.sanitizeForParceling(true); 177 178 // TODO(b/33197203): Need to pipe the bundle 179 FillRequest request = new FillRequest(structure, null, session.mFlags); 180 session.mRemoteFillService.onFillRequest(request); 181 } 182 }; 183 184 AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, 185 int userId, AutoFillUI ui, boolean disabled) { 186 mContext = context; 187 mLock = lock; 188 mRequestsHistory = requestsHistory; 189 mUserId = userId; 190 mUi = ui; 191 updateLocked(disabled); 192 } 193 194 CharSequence getServiceName() { 195 if (mInfo == null) { 196 return null; 197 } 198 final ComponentName serviceComponent = mInfo.getServiceInfo().getComponentName(); 199 final String packageName = serviceComponent.getPackageName(); 200 201 try { 202 final PackageManager pm = mContext.getPackageManager(); 203 final ApplicationInfo info = pm.getApplicationInfo(packageName, 0); 204 return pm.getApplicationLabel(info); 205 } catch (Exception e) { 206 Slog.e(TAG, "Could not get label for " + packageName + ": " + e); 207 return packageName; 208 } 209 } 210 211 private String getComponentNameFromSettings() { 212 return Settings.Secure.getStringForUser( 213 mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); 214 } 215 216 void updateLocked(boolean disabled) { 217 final boolean wasEnabled = isEnabled(); 218 mDisabled = disabled; 219 ComponentName serviceComponent = null; 220 ServiceInfo serviceInfo = null; 221 final String componentName = getComponentNameFromSettings(); 222 if (!TextUtils.isEmpty(componentName)) { 223 try { 224 serviceComponent = ComponentName.unflattenFromString(componentName); 225 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 226 0, mUserId); 227 } catch (RuntimeException | RemoteException e) { 228 Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e); 229 return; 230 } 231 } 232 try { 233 if (serviceInfo != null) { 234 mInfo = new AutofillServiceInfo(mContext.getPackageManager(), 235 serviceComponent, mUserId); 236 } else { 237 mInfo = null; 238 } 239 if (wasEnabled != isEnabled()) { 240 if (!isEnabled()) { 241 final int sessionCount = mSessions.size(); 242 for (int i = sessionCount - 1; i >= 0; i--) { 243 final Session session = mSessions.valueAt(i); 244 session.removeSelfLocked(); 245 } 246 } 247 sendStateToClients(); 248 } 249 } catch (PackageManager.NameNotFoundException e) { 250 Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e); 251 } 252 } 253 254 /** 255 * Used by {@link AutofillManagerServiceShellCommand} to request save for the current top app. 256 */ 257 void requestSaveForUserLocked(IBinder activityToken) { 258 if (!isEnabled()) { 259 return; 260 } 261 262 final int numSessions = mSessions.size(); 263 for (int i = 0; i < numSessions; i++) { 264 final Session session = mSessions.valueAt(i); 265 if (session.getActivityTokenLocked().equals(activityToken)) { 266 session.callSaveLocked(); 267 return; 268 } 269 } 270 271 Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken); 272 } 273 274 boolean addClientLocked(IAutoFillManagerClient client) { 275 if (mClients == null) { 276 mClients = new RemoteCallbackList<>(); 277 } 278 mClients.register(client); 279 return isEnabled(); 280 } 281 282 void setAuthenticationResultLocked(Bundle data, int sessionId, int uid) { 283 if (!isEnabled()) { 284 return; 285 } 286 final Session session = mSessions.get(sessionId); 287 if (session != null && uid == session.uid) { 288 session.setAuthenticationResultLocked(data); 289 } 290 } 291 292 void setHasCallback(int sessionId, int uid, boolean hasIt) { 293 if (!isEnabled()) { 294 return; 295 } 296 final Session session = mSessions.get(sessionId); 297 if (session != null && uid == session.uid) { 298 session.setHasCallback(hasIt); 299 } 300 } 301 302 int startSessionLocked(@NonNull IBinder activityToken, int uid, @Nullable IBinder windowToken, 303 @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, 304 @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, 305 int flags, @NonNull String packageName) { 306 if (!isEnabled()) { 307 return 0; 308 } 309 310 final Session newSession = createSessionByTokenLocked(activityToken, uid, windowToken, 311 appCallbackToken, hasCallback, flags, packageName); 312 if (newSession == null) { 313 return NO_SESSION; 314 } 315 316 final String historyItem = 317 "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName 318 + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" + 319 hasCallback + " f=" + flags; 320 mRequestsHistory.log(historyItem); 321 322 newSession.updateLocked(autofillId, virtualBounds, value, FLAG_START_SESSION); 323 324 return newSession.id; 325 } 326 327 void finishSessionLocked(int sessionId, int uid) { 328 if (!isEnabled()) { 329 return; 330 } 331 332 final Session session = mSessions.get(sessionId); 333 if (session == null || uid != session.uid) { 334 Slog.w(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")"); 335 return; 336 } 337 338 final boolean finished = session.showSaveLocked(); 339 if (DEBUG) { 340 Log.d(TAG, "finishSessionLocked(): session finished on save? " + finished); 341 } 342 if (finished) { 343 session.removeSelf(); 344 } 345 } 346 347 void cancelSessionLocked(int sessionId, int uid) { 348 if (!isEnabled()) { 349 return; 350 } 351 352 final Session session = mSessions.get(sessionId); 353 if (session == null || uid != session.uid) { 354 Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")"); 355 return; 356 } 357 session.removeSelfLocked(); 358 } 359 360 void disableOwnedAutofillServicesLocked(int uid) { 361 if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid 362 != UserHandle.getAppId(uid)) { 363 return; 364 } 365 final long identity = Binder.clearCallingIdentity(); 366 try { 367 final String autoFillService = getComponentNameFromSettings(); 368 if (mInfo.getServiceInfo().getComponentName().equals( 369 ComponentName.unflattenFromString(autoFillService))) { 370 Settings.Secure.putStringForUser(mContext.getContentResolver(), 371 Settings.Secure.AUTOFILL_SERVICE, null, mUserId); 372 destroySessionsLocked(); 373 } 374 } finally { 375 Binder.restoreCallingIdentity(identity); 376 } 377 } 378 379 private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid, 380 @Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, boolean hasCallback, 381 int flags, @NonNull String packageName) { 382 // use random ids so that one app cannot know that another app creates sessions 383 int sessionId; 384 int tries = 0; 385 do { 386 tries++; 387 if (tries > MAX_SESSION_ID_CREATE_TRIES) { 388 Log.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries"); 389 return null; 390 } 391 392 sessionId = sRandom.nextInt(); 393 } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0); 394 395 final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock, 396 sessionId, uid, activityToken, windowToken, appCallbackToken, hasCallback, flags, 397 mInfo.getServiceInfo().getComponentName(), packageName); 398 mSessions.put(newSession.id, newSession); 399 400 /* 401 * TODO(b/33197203): apply security checks below: 402 * - checks if disabled by secure settings / device policy 403 * - log operation using noteOp() 404 * - check flags 405 * - display disclosure if needed 406 */ 407 try { 408 final Bundle receiverExtras = new Bundle(); 409 receiverExtras.putInt(EXTRA_SESSION_ID, sessionId); 410 final long identity = Binder.clearCallingIdentity(); 411 try { 412 if (!ActivityManager.getService().requestAutofillData(mAssistReceiver, 413 receiverExtras, activityToken)) { 414 Slog.w(TAG, "failed to request autofill data for " + activityToken); 415 } 416 } finally { 417 Binder.restoreCallingIdentity(identity); 418 } 419 } catch (RemoteException e) { 420 // Should not happen, it's a local call. 421 } 422 return newSession; 423 } 424 425 /** 426 * Restores a session after an activity was temporarily destroyed. 427 * 428 * @param sessionId The id of the session to restore 429 * @param uid UID of the process that tries to restore the session 430 * @param activityToken The new instance of the activity 431 * @param appCallback The callbacks to the activity 432 */ 433 boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken, 434 @NonNull IBinder appCallback) { 435 final Session session = mSessions.get(sessionId); 436 437 if (session == null || uid != session.uid) { 438 return false; 439 } else { 440 session.switchActivity(activityToken, appCallback); 441 return true; 442 } 443 } 444 445 /** 446 * Set the window the UI should get attached to 447 * 448 * @param sessionId The id of the session to restore 449 * @param uid UID of the process that tries to restore the session 450 * @param windowToken The window the activity is now in 451 */ 452 boolean setWindow(int sessionId, int uid, @NonNull IBinder windowToken) { 453 final Session session = mSessions.get(sessionId); 454 455 if (session == null || uid != session.uid) { 456 return false; 457 } else { 458 session.switchWindow(windowToken); 459 return true; 460 } 461 } 462 463 void updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds, 464 AutofillValue value, int flags) { 465 final Session session = mSessions.get(sessionId); 466 if (session == null || session.uid != uid) { 467 if (VERBOSE) { 468 Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId + "(" + uid 469 + ")"); 470 } 471 return; 472 } 473 474 session.updateLocked(autofillId, virtualBounds, value, flags); 475 } 476 477 void removeSessionLocked(int sessionId) { 478 mSessions.remove(sessionId); 479 } 480 481 private void handleSessionSave(int sessionId) { 482 synchronized (mLock) { 483 final Session session = mSessions.get(sessionId); 484 if (session == null) { 485 Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId); 486 487 return; 488 } 489 session.callSaveLocked(); 490 } 491 } 492 493 void destroyLocked() { 494 if (VERBOSE) { 495 Slog.v(TAG, "destroyLocked()"); 496 } 497 498 final int numSessions = mSessions.size(); 499 for (int i = 0; i < numSessions; i++) { 500 mSessions.valueAt(i).destroyLocked(); 501 } 502 mSessions.clear(); 503 } 504 505 CharSequence getServiceLabel() { 506 return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager()); 507 } 508 509 /** 510 * Initializes the last fill selection after an autofill service returned a new 511 * {@link FillResponse}. 512 */ 513 void setLastResponse(int serviceUid, @NonNull FillResponse response) { 514 synchronized (mLock) { 515 mEventHistory = new FillEventHistory(serviceUid, response.getClientState()); 516 } 517 } 518 519 /** 520 * Updates the last fill selection when an authentication was selected. 521 */ 522 void setAuthenticationSelected() { 523 synchronized (mLock) { 524 mEventHistory.addEvent( 525 new FillEventHistory.Event(FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED, null)); 526 } 527 } 528 529 /** 530 * Updates the last fill selection when an dataset authentication was selected. 531 */ 532 void setDatasetAuthenticationSelected(@Nullable String selectedDataset) { 533 synchronized (mLock) { 534 mEventHistory.addEvent(new FillEventHistory.Event( 535 FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset)); 536 } 537 } 538 539 /** 540 * Updates the last fill selection when an save Ui is shown. 541 */ 542 void setSaveShown() { 543 synchronized (mLock) { 544 mEventHistory.addEvent(new FillEventHistory.Event(FillEventHistory.Event.TYPE_SAVE_SHOWN, null)); 545 } 546 } 547 548 /** 549 * Updates the last fill response when a dataset was selected. 550 */ 551 void setDatasetSelected(@Nullable String selectedDataset) { 552 synchronized (mLock) { 553 mEventHistory.addEvent( 554 new FillEventHistory.Event(FillEventHistory.Event.TYPE_DATASET_SELECTED, selectedDataset)); 555 } 556 } 557 558 /** 559 * Gets the fill event history. 560 * 561 * @param callingUid The calling uid 562 * 563 * @return The history or {@code null} if there is none. 564 */ 565 FillEventHistory getFillEventHistory(int callingUid) { 566 synchronized (mLock) { 567 if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) { 568 return mEventHistory; 569 } 570 } 571 572 return null; 573 } 574 575 void dumpLocked(String prefix, PrintWriter pw) { 576 final String prefix2 = prefix + " "; 577 578 pw.print(prefix); pw.print("User: "); pw.println(mUserId); 579 pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null 580 ? mInfo.getServiceInfo().getComponentName() : null); 581 pw.print(prefix); pw.print("Component from settings: "); 582 pw.println(getComponentNameFromSettings()); 583 pw.print(prefix); pw.print("Default component: "); 584 pw.println(mContext.getString(R.string.config_defaultAutofillService)); 585 pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled); 586 587 if (VERBOSE && mInfo != null) { 588 // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps) 589 pw.print(prefix); pw.println("ServiceInfo:"); 590 mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix); 591 } 592 593 final int size = mSessions.size(); 594 if (size == 0) { 595 pw.print(prefix); pw.println("No sessions"); 596 } else { 597 pw.print(prefix); pw.print(size); pw.println(" sessions:"); 598 for (int i = 0; i < size; i++) { 599 pw.print(prefix); pw.print("#"); pw.println(i + 1); 600 mSessions.valueAt(i).dumpLocked(prefix2, pw); 601 } 602 } 603 604 if (mEventHistory == null || mEventHistory.getEvents().size() == 0) { 605 pw.print(prefix); pw.println("No event on last fill response"); 606 } else { 607 pw.print(prefix); pw.println("Events of last fill response:"); 608 pw.print(prefix); 609 610 int numEvents = mEventHistory.getEvents().size(); 611 for (int i = 0; i < numEvents; i++) { 612 FillEventHistory.Event event = mEventHistory.getEvents().get(i); 613 pw.println(" " + i + ": eventType=" + event.getType() + " datasetId=" 614 + event.getDatasetId()); 615 } 616 } 617 } 618 619 void destroySessionsLocked() { 620 while (mSessions.size() > 0) { 621 mSessions.valueAt(0).removeSelf(); 622 } 623 } 624 625 void listSessionsLocked(ArrayList<String> output) { 626 final int numSessions = mSessions.size(); 627 for (int i = 0; i < numSessions; i++) { 628 output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName() 629 : null) + ":" + mSessions.keyAt(i)); 630 } 631 } 632 633 private void sendStateToClients() { 634 final RemoteCallbackList<IAutoFillManagerClient> clients; 635 final int userClientCount; 636 synchronized (mLock) { 637 if (mClients == null) { 638 return; 639 } 640 clients = mClients; 641 userClientCount = clients.beginBroadcast(); 642 } 643 try { 644 for (int i = 0; i < userClientCount; i++) { 645 final IAutoFillManagerClient client = clients.getBroadcastItem(i); 646 try { 647 client.setState(isEnabled()); 648 } catch (RemoteException re) { 649 /* ignore */ 650 } 651 } 652 } finally { 653 clients.finishBroadcast(); 654 } 655 } 656 657 private boolean isEnabled() { 658 return mInfo != null && !mDisabled; 659 } 660 661 @Override 662 public String toString() { 663 return "AutofillManagerServiceImpl: [userId=" + mUserId 664 + ", component=" + (mInfo != null 665 ? mInfo.getServiceInfo().getComponentName() : null) + "]"; 666 } 667} 668