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