AutofillManagerServiceImpl.java revision f60da8690f7db73246fca677cb74358322170fe8
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.FillRequest.FLAG_MANUAL_REQUEST; 20import static android.view.autofill.AutofillManager.ACTION_START_SESSION; 21import static android.view.autofill.AutofillManager.NO_SESSION; 22 23import static com.android.server.autofill.Helper.sDebug; 24import static com.android.server.autofill.Helper.sVerbose; 25 26import android.annotation.NonNull; 27import android.annotation.Nullable; 28import android.app.ActivityManager; 29import android.app.AppGlobals; 30import android.app.IActivityManager; 31import android.content.ComponentName; 32import android.content.Context; 33import android.content.pm.ApplicationInfo; 34import android.content.pm.PackageManager; 35import android.content.pm.ServiceInfo; 36import android.graphics.Rect; 37import android.graphics.drawable.Drawable; 38import android.os.AsyncTask; 39import android.os.Binder; 40import android.os.Bundle; 41import android.os.IBinder; 42import android.os.Looper; 43import android.os.RemoteCallbackList; 44import android.os.RemoteException; 45import android.os.UserManager; 46import android.provider.Settings; 47import android.service.autofill.AutofillService; 48import android.service.autofill.AutofillServiceInfo; 49import android.service.autofill.FillEventHistory; 50import android.service.autofill.FillEventHistory.Event; 51import android.service.autofill.FillResponse; 52import android.service.autofill.IAutoFillService; 53import android.text.TextUtils; 54import android.util.ArraySet; 55import android.util.DebugUtils; 56import android.util.LocalLog; 57import android.util.Slog; 58import android.util.SparseArray; 59import android.view.autofill.AutofillId; 60import android.view.autofill.AutofillManager; 61import android.view.autofill.AutofillValue; 62import android.view.autofill.IAutoFillManagerClient; 63 64import com.android.internal.R; 65import com.android.internal.annotations.GuardedBy; 66import com.android.internal.os.HandlerCaller; 67import com.android.server.autofill.ui.AutoFillUI; 68 69import java.io.PrintWriter; 70import java.util.ArrayList; 71import java.util.Random; 72 73/** 74 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the 75 * app's {@link IAutoFillService} implementation. 76 * 77 */ 78final class AutofillManagerServiceImpl { 79 80 private static final String TAG = "AutofillManagerServiceImpl"; 81 private static final int MAX_SESSION_ID_CREATE_TRIES = 2048; 82 83 /** Minimum interval to prune abandoned sessions */ 84 private static final int MAX_ABANDONED_SESSION_MILLIS = 30000; 85 86 static final int MSG_SERVICE_SAVE = 1; 87 88 private final int mUserId; 89 private final Context mContext; 90 private final Object mLock; 91 private final AutoFillUI mUi; 92 93 private RemoteCallbackList<IAutoFillManagerClient> mClients; 94 private AutofillServiceInfo mInfo; 95 96 private static final Random sRandom = new Random(); 97 98 private final LocalLog mRequestsHistory; 99 /** 100 * Whether service was disabled for user due to {@link UserManager} restrictions. 101 */ 102 private boolean mDisabled; 103 104 /** 105 * Caches whether the setup completed for the current user. 106 */ 107 @GuardedBy("mLock") 108 private boolean mSetupComplete; 109 110 private final HandlerCaller.Callback mHandlerCallback = (msg) -> { 111 switch (msg.what) { 112 case MSG_SERVICE_SAVE: 113 handleSessionSave(msg.arg1); 114 break; 115 default: 116 Slog.w(TAG, "invalid msg on handler: " + msg); 117 } 118 }; 119 120 private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), 121 mHandlerCallback, true); 122 123 /** 124 * Cache of pending {@link Session}s, keyed by sessionId. 125 * 126 * <p>They're kept until the {@link AutofillService} finished handling a request, an error 127 * occurs, or the session is abandoned. 128 */ 129 @GuardedBy("mLock") 130 private final SparseArray<Session> mSessions = new SparseArray<>(); 131 132 /** The last selection */ 133 @GuardedBy("mLock") 134 private FillEventHistory mEventHistory; 135 136 /** When was {@link PruneTask} last executed? */ 137 private long mLastPrune = 0; 138 139 AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, 140 int userId, AutoFillUI ui, boolean disabled) { 141 mContext = context; 142 mLock = lock; 143 mRequestsHistory = requestsHistory; 144 mUserId = userId; 145 mUi = ui; 146 updateLocked(disabled); 147 } 148 149 @Nullable 150 CharSequence getServiceName() { 151 final String packageName = getServicePackageName(); 152 if (packageName == null) { 153 return null; 154 } 155 156 try { 157 final PackageManager pm = mContext.getPackageManager(); 158 final ApplicationInfo info = pm.getApplicationInfo(packageName, 0); 159 return pm.getApplicationLabel(info); 160 } catch (Exception e) { 161 Slog.e(TAG, "Could not get label for " + packageName + ": " + e); 162 return packageName; 163 } 164 } 165 166 @Nullable 167 String getServicePackageName() { 168 final ComponentName serviceComponent = getServiceComponentName(); 169 if (serviceComponent != null) { 170 return serviceComponent.getPackageName(); 171 } 172 return null; 173 } 174 175 ComponentName getServiceComponentName() { 176 synchronized (mLock) { 177 if (mInfo == null) { 178 return null; 179 } 180 return mInfo.getServiceInfo().getComponentName(); 181 } 182 } 183 184 private boolean isSetupCompletedLocked() { 185 final String setupComplete = Settings.Secure.getStringForUser( 186 mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId); 187 return "1".equals(setupComplete); 188 } 189 190 private String getComponentNameFromSettings() { 191 return Settings.Secure.getStringForUser( 192 mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); 193 } 194 195 void updateLocked(boolean disabled) { 196 final boolean wasEnabled = isEnabled(); 197 if (sVerbose) { 198 Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled 199 + ", mSetupComplete= " + mSetupComplete 200 + ", disabled=" + disabled + ", mDisabled=" + mDisabled); 201 } 202 mSetupComplete = isSetupCompletedLocked(); 203 mDisabled = disabled; 204 ComponentName serviceComponent = null; 205 ServiceInfo serviceInfo = null; 206 final String componentName = getComponentNameFromSettings(); 207 if (!TextUtils.isEmpty(componentName)) { 208 try { 209 serviceComponent = ComponentName.unflattenFromString(componentName); 210 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 211 0, mUserId); 212 } catch (RuntimeException | RemoteException e) { 213 Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e); 214 return; 215 } 216 } 217 try { 218 if (serviceInfo != null) { 219 mInfo = new AutofillServiceInfo(mContext.getPackageManager(), 220 serviceComponent, mUserId); 221 } else { 222 mInfo = null; 223 } 224 final boolean isEnabled = isEnabled(); 225 if (wasEnabled != isEnabled) { 226 if (!isEnabled) { 227 final int sessionCount = mSessions.size(); 228 for (int i = sessionCount - 1; i >= 0; i--) { 229 final Session session = mSessions.valueAt(i); 230 session.removeSelfLocked(); 231 } 232 } 233 sendStateToClients(false); 234 } 235 } catch (Exception e) { 236 Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e); 237 } 238 } 239 240 boolean addClientLocked(IAutoFillManagerClient client) { 241 if (mClients == null) { 242 mClients = new RemoteCallbackList<>(); 243 } 244 mClients.register(client); 245 return isEnabled(); 246 } 247 248 void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) { 249 if (!isEnabled()) { 250 return; 251 } 252 final Session session = mSessions.get(sessionId); 253 if (session != null && uid == session.uid) { 254 session.setAuthenticationResultLocked(data, authenticationId); 255 } 256 } 257 258 void setHasCallback(int sessionId, int uid, boolean hasIt) { 259 if (!isEnabled()) { 260 return; 261 } 262 final Session session = mSessions.get(sessionId); 263 if (session != null && uid == session.uid) { 264 synchronized (mLock) { 265 session.setHasCallbackLocked(hasIt); 266 } 267 } 268 } 269 270 int startSessionLocked(@NonNull IBinder activityToken, int uid, 271 @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, 272 @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, 273 int flags, @NonNull String packageName) { 274 if (!isEnabled()) { 275 return 0; 276 } 277 if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags); 278 279 // Occasionally clean up abandoned sessions 280 pruneAbandonedSessionsLocked(); 281 282 final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken, 283 hasCallback, packageName); 284 if (newSession == null) { 285 return NO_SESSION; 286 } 287 288 final String historyItem = 289 "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName 290 + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" + 291 hasCallback + " f=" + flags; 292 mRequestsHistory.log(historyItem); 293 294 newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); 295 296 return newSession.id; 297 } 298 299 /** 300 * Remove abandoned sessions if needed. 301 */ 302 private void pruneAbandonedSessionsLocked() { 303 long now = System.currentTimeMillis(); 304 if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) { 305 mLastPrune = now; 306 307 if (mSessions.size() > 0) { 308 (new PruneTask()).execute(); 309 } 310 } 311 } 312 313 void finishSessionLocked(int sessionId, int uid) { 314 if (!isEnabled()) { 315 return; 316 } 317 318 final Session session = mSessions.get(sessionId); 319 if (session == null || uid != session.uid) { 320 if (sVerbose) { 321 Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")"); 322 } 323 return; 324 } 325 326 final boolean finished = session.showSaveLocked(); 327 if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished); 328 329 if (finished) { 330 session.removeSelfLocked(); 331 } 332 } 333 334 void cancelSessionLocked(int sessionId, int uid) { 335 if (!isEnabled()) { 336 return; 337 } 338 339 final Session session = mSessions.get(sessionId); 340 if (session == null || uid != session.uid) { 341 Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")"); 342 return; 343 } 344 session.removeSelfLocked(); 345 } 346 347 void disableOwnedAutofillServicesLocked(int uid) { 348 if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid != uid) { 349 return; 350 } 351 final long identity = Binder.clearCallingIdentity(); 352 try { 353 final String autoFillService = getComponentNameFromSettings(); 354 if (mInfo.getServiceInfo().getComponentName().equals( 355 ComponentName.unflattenFromString(autoFillService))) { 356 Settings.Secure.putStringForUser(mContext.getContentResolver(), 357 Settings.Secure.AUTOFILL_SERVICE, null, mUserId); 358 destroySessionsLocked(); 359 } 360 } finally { 361 Binder.restoreCallingIdentity(identity); 362 } 363 } 364 365 private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid, 366 @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) { 367 // use random ids so that one app cannot know that another app creates sessions 368 int sessionId; 369 int tries = 0; 370 do { 371 tries++; 372 if (tries > MAX_SESSION_ID_CREATE_TRIES) { 373 Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries"); 374 return null; 375 } 376 377 sessionId = sRandom.nextInt(); 378 } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0); 379 380 final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock, 381 sessionId, uid, activityToken, appCallbackToken, hasCallback, 382 mInfo.getServiceInfo().getComponentName(), packageName); 383 mSessions.put(newSession.id, newSession); 384 385 return newSession; 386 } 387 388 /** 389 * Restores a session after an activity was temporarily destroyed. 390 * 391 * @param sessionId The id of the session to restore 392 * @param uid UID of the process that tries to restore the session 393 * @param activityToken The new instance of the activity 394 * @param appCallback The callbacks to the activity 395 */ 396 boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken, 397 @NonNull IBinder appCallback) { 398 final Session session = mSessions.get(sessionId); 399 400 if (session == null || uid != session.uid) { 401 return false; 402 } else { 403 session.switchActivity(activityToken, appCallback); 404 return true; 405 } 406 } 407 408 /** 409 * Updates a session and returns whether it should be restarted. 410 */ 411 boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds, 412 AutofillValue value, int action, int flags) { 413 final Session session = mSessions.get(sessionId); 414 if (session == null || session.uid != uid) { 415 if ((flags & FLAG_MANUAL_REQUEST) != 0) { 416 if (sDebug) { 417 Slog.d(TAG, "restarting session " + sessionId + " due to manual request on " 418 + autofillId); 419 } 420 return true; 421 } 422 if (sVerbose) { 423 Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId 424 + "(" + uid + ")"); 425 } 426 return false; 427 } 428 429 session.updateLocked(autofillId, virtualBounds, value, action, flags); 430 return false; 431 } 432 433 void removeSessionLocked(int sessionId) { 434 mSessions.remove(sessionId); 435 } 436 437 private void handleSessionSave(int sessionId) { 438 synchronized (mLock) { 439 final Session session = mSessions.get(sessionId); 440 if (session == null) { 441 Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId); 442 443 return; 444 } 445 session.callSaveLocked(); 446 } 447 } 448 449 void onPendingSaveUi(int operation, @NonNull IBinder token) { 450 if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token); 451 synchronized (mLock) { 452 final int sessionCount = mSessions.size(); 453 for (int i = sessionCount - 1; i >= 0; i--) { 454 final Session session = mSessions.valueAt(i); 455 if (session.isSaveUiPendingForToken(token)) { 456 session.onPendingSaveUi(operation, token); 457 return; 458 } 459 } 460 } 461 if (sDebug) { 462 Slog.d(TAG, "No pending Save UI for token " + token + " and operation " 463 + DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_", 464 operation)); 465 } 466 } 467 468 void destroyLocked() { 469 if (sVerbose) Slog.v(TAG, "destroyLocked()"); 470 471 final int numSessions = mSessions.size(); 472 final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions); 473 for (int i = 0; i < numSessions; i++) { 474 final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked(); 475 if (remoteFillService != null) { 476 remoteFillServices.add(remoteFillService); 477 } 478 } 479 mSessions.clear(); 480 for (int i = 0; i < remoteFillServices.size(); i++) { 481 remoteFillServices.valueAt(i).destroy(); 482 } 483 484 sendStateToClients(true); 485 } 486 487 @NonNull 488 CharSequence getServiceLabel() { 489 return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager()); 490 } 491 492 @NonNull 493 Drawable getServiceIcon() { 494 return mInfo.getServiceInfo().loadIcon(mContext.getPackageManager()); 495 } 496 497 /** 498 * Initializes the last fill selection after an autofill service returned a new 499 * {@link FillResponse}. 500 */ 501 void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) { 502 synchronized (mLock) { 503 mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState()); 504 } 505 } 506 507 /** 508 * Resets the last fill selection. 509 */ 510 void resetLastResponse() { 511 synchronized (mLock) { 512 mEventHistory = null; 513 } 514 } 515 516 private boolean isValidEventLocked(String method, int sessionId) { 517 if (mEventHistory == null) { 518 Slog.w(TAG, method + ": not logging event because history is null"); 519 return false; 520 } 521 if (sessionId != mEventHistory.getSessionId()) { 522 if (sDebug) { 523 Slog.d(TAG, method + ": not logging event for session " + sessionId 524 + " because tracked session is " + mEventHistory.getSessionId()); 525 } 526 return false; 527 } 528 return true; 529 } 530 531 /** 532 * Updates the last fill selection when an authentication was selected. 533 */ 534 void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) { 535 synchronized (mLock) { 536 if (isValidEventLocked("setAuthenticationSelected()", sessionId)) { 537 mEventHistory 538 .addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState)); 539 } 540 } 541 } 542 543 /** 544 * Updates the last fill selection when an dataset authentication was selected. 545 */ 546 void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId, 547 @Nullable Bundle clientState) { 548 synchronized (mLock) { 549 if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) { 550 mEventHistory.addEvent( 551 new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, 552 clientState)); 553 } 554 } 555 } 556 557 /** 558 * Updates the last fill selection when an save Ui is shown. 559 */ 560 void logSaveShown(int sessionId, @Nullable Bundle clientState) { 561 synchronized (mLock) { 562 if (isValidEventLocked("logSaveShown()", sessionId)) { 563 mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState)); 564 } 565 } 566 } 567 568 /** 569 * Updates the last fill response when a dataset was selected. 570 */ 571 void logDatasetSelected(@Nullable String selectedDataset, int sessionId, 572 @Nullable Bundle clientState) { 573 synchronized (mLock) { 574 if (isValidEventLocked("logDatasetSelected()", sessionId)) { 575 mEventHistory.addEvent( 576 new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState)); 577 } 578 } 579 } 580 581 /** 582 * Gets the fill event history. 583 * 584 * @param callingUid The calling uid 585 * 586 * @return The history or {@code null} if there is none. 587 */ 588 FillEventHistory getFillEventHistory(int callingUid) { 589 synchronized (mLock) { 590 if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) { 591 return mEventHistory; 592 } 593 } 594 595 return null; 596 } 597 598 void dumpLocked(String prefix, PrintWriter pw) { 599 final String prefix2 = prefix + " "; 600 601 pw.print(prefix); pw.print("User: "); pw.println(mUserId); 602 pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null 603 ? mInfo.getServiceInfo().getComponentName() : null); 604 pw.print(prefix); pw.print("Component from settings: "); 605 pw.println(getComponentNameFromSettings()); 606 pw.print(prefix); pw.print("Default component: "); 607 pw.println(mContext.getString(R.string.config_defaultAutofillService)); 608 pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled); 609 pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete); 610 pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); 611 612 final int size = mSessions.size(); 613 if (size == 0) { 614 pw.print(prefix); pw.println("No sessions"); 615 } else { 616 pw.print(prefix); pw.print(size); pw.println(" sessions:"); 617 for (int i = 0; i < size; i++) { 618 pw.print(prefix); pw.print("#"); pw.println(i + 1); 619 mSessions.valueAt(i).dumpLocked(prefix2, pw); 620 } 621 } 622 623 if (mEventHistory == null || mEventHistory.getEvents() == null 624 || mEventHistory.getEvents().size() == 0) { 625 pw.print(prefix); pw.println("No event on last fill response"); 626 } else { 627 pw.print(prefix); pw.println("Events of last fill response:"); 628 pw.print(prefix); 629 630 int numEvents = mEventHistory.getEvents().size(); 631 for (int i = 0; i < numEvents; i++) { 632 final Event event = mEventHistory.getEvents().get(i); 633 pw.println(" " + i + ": eventType=" + event.getType() + " datasetId=" 634 + event.getDatasetId()); 635 } 636 } 637 } 638 639 void destroySessionsLocked() { 640 if (mSessions.size() == 0) { 641 mUi.destroyAll(null, null); 642 return; 643 } 644 while (mSessions.size() > 0) { 645 mSessions.valueAt(0).forceRemoveSelfLocked(); 646 } 647 } 648 649 void listSessionsLocked(ArrayList<String> output) { 650 final int numSessions = mSessions.size(); 651 for (int i = 0; i < numSessions; i++) { 652 output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName() 653 : null) + ":" + mSessions.keyAt(i)); 654 } 655 } 656 657 private void sendStateToClients(boolean resetClient) { 658 final RemoteCallbackList<IAutoFillManagerClient> clients; 659 final int userClientCount; 660 synchronized (mLock) { 661 if (mClients == null) { 662 return; 663 } 664 clients = mClients; 665 userClientCount = clients.beginBroadcast(); 666 } 667 try { 668 for (int i = 0; i < userClientCount; i++) { 669 final IAutoFillManagerClient client = clients.getBroadcastItem(i); 670 try { 671 final boolean resetSession; 672 synchronized (mLock) { 673 resetSession = resetClient || isClientSessionDestroyedLocked(client); 674 } 675 client.setState(isEnabled(), resetSession, resetClient); 676 } catch (RemoteException re) { 677 /* ignore */ 678 } 679 } 680 } finally { 681 clients.finishBroadcast(); 682 } 683 } 684 685 private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) { 686 final int sessionCount = mSessions.size(); 687 for (int i = 0; i < sessionCount; i++) { 688 final Session session = mSessions.valueAt(i); 689 if (session.getClient().equals(client)) { 690 return session.isDestroyed(); 691 } 692 } 693 return true; 694 } 695 696 boolean isEnabled() { 697 return mSetupComplete && mInfo != null && !mDisabled; 698 } 699 700 @Override 701 public String toString() { 702 return "AutofillManagerServiceImpl: [userId=" + mUserId 703 + ", component=" + (mInfo != null 704 ? mInfo.getServiceInfo().getComponentName() : null) + "]"; 705 } 706 707 /** Task used to prune abandoned session */ 708 private class PruneTask extends AsyncTask<Void, Void, Void> { 709 @Override 710 protected Void doInBackground(Void... ignored) { 711 int numSessionsToRemove; 712 713 SparseArray<IBinder> sessionsToRemove; 714 715 synchronized (mLock) { 716 numSessionsToRemove = mSessions.size(); 717 sessionsToRemove = new SparseArray<>(numSessionsToRemove); 718 719 for (int i = 0; i < numSessionsToRemove; i++) { 720 Session session = mSessions.valueAt(i); 721 722 sessionsToRemove.put(session.id, session.getActivityTokenLocked()); 723 } 724 } 725 726 IActivityManager am = ActivityManager.getService(); 727 728 // Only remove sessions which's activities are not known to the activity manager anymore 729 for (int i = 0; i < numSessionsToRemove; i++) { 730 try { 731 // The activity manager cannot resolve activities that have been removed 732 if (am.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) { 733 sessionsToRemove.removeAt(i); 734 i--; 735 numSessionsToRemove--; 736 } 737 } catch (RemoteException e) { 738 Slog.w(TAG, "Cannot figure out if activity is finished", e); 739 } 740 } 741 742 synchronized (mLock) { 743 for (int i = 0; i < numSessionsToRemove; i++) { 744 Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i)); 745 746 if (sessionToRemove != null && sessionsToRemove.valueAt(i) 747 == sessionToRemove.getActivityTokenLocked()) { 748 if (sessionToRemove.isSavingLocked()) { 749 if (sVerbose) { 750 Slog.v(TAG, "Session " + sessionToRemove.id + " is saving"); 751 } 752 } else { 753 if (sDebug) { 754 Slog.i(TAG, "Prune session " + sessionToRemove.id + " (" 755 + sessionToRemove.getActivityTokenLocked() + ")"); 756 } 757 sessionToRemove.removeSelfLocked(); 758 } 759 } 760 } 761 } 762 763 return null; 764 } 765 } 766} 767