1 /* 2 * Copyright (C) 2014 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 17 package com.android.server.am; 18 19 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 20 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 21 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 22 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; 23 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; 24 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; 25 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS; 26 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 27 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 28 import static com.android.server.am.TaskRecord.INVALID_TASK_ID; 29 30 import com.google.android.collect.Sets; 31 32 import android.app.ActivityManager; 33 import android.app.AppGlobals; 34 import android.content.ComponentName; 35 import android.content.Intent; 36 import android.content.pm.ActivityInfo; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.IPackageManager; 39 import android.content.pm.PackageManager; 40 import android.graphics.Bitmap; 41 import android.os.Environment; 42 import android.os.RemoteException; 43 import android.os.UserHandle; 44 import android.util.ArraySet; 45 import android.util.Slog; 46 import android.util.SparseArray; 47 import android.util.SparseBooleanArray; 48 49 import java.io.File; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.Collections; 53 import java.util.Comparator; 54 import java.util.HashMap; 55 import java.util.Set; 56 57 /** 58 * Class for managing the recent tasks list. 59 */ 60 class RecentTasks extends ArrayList<TaskRecord> { 61 private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM; 62 private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; 63 private static final String TAG_TASKS = TAG + POSTFIX_TASKS; 64 65 // Maximum number recent bitmaps to keep in memory. 66 private static final int MAX_RECENT_BITMAPS = 3; 67 private static final int DEFAULT_INITIAL_CAPACITY = 5; 68 69 // Whether or not to move all affiliated tasks to the front when one of the tasks is launched 70 private static final boolean MOVE_AFFILIATED_TASKS_TO_FRONT = false; 71 72 /** 73 * Save recent tasks information across reboots. 74 */ 75 private final TaskPersister mTaskPersister; 76 private final ActivityManagerService mService; 77 private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray( 78 DEFAULT_INITIAL_CAPACITY); 79 80 /** 81 * Stores for each user task ids that are taken by tasks residing in persistent storage. These 82 * tasks may or may not currently be in memory. 83 */ 84 final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>( 85 DEFAULT_INITIAL_CAPACITY); 86 87 // Mainly to avoid object recreation on multiple calls. 88 private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>(); 89 private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>(); 90 private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>(); 91 private final ActivityInfo mTmpActivityInfo = new ActivityInfo(); 92 private final ApplicationInfo mTmpAppInfo = new ApplicationInfo(); 93 94 RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) { 95 File systemDir = Environment.getDataSystemDirectory(); 96 mService = service; 97 mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this); 98 mStackSupervisor.setRecentTasks(this); 99 } 100 101 /** 102 * Loads the persistent recentTasks for {@code userId} into this list from persistent storage. 103 * Does nothing if they are already loaded. 104 * 105 * @param userId the user Id 106 */ 107 void loadUserRecentsLocked(int userId) { 108 if (mUsersWithRecentsLoaded.get(userId)) { 109 return; 110 } 111 112 // Load the task ids if not loaded. 113 loadPersistedTaskIdsForUserLocked(userId); 114 115 // Check if any tasks are added before recents is loaded 116 final SparseBooleanArray preaddedTasks = new SparseBooleanArray(); 117 for (final TaskRecord task : this) { 118 if (task.userId == userId && shouldPersistTaskLocked(task)) { 119 preaddedTasks.put(task.taskId, true); 120 } 121 } 122 123 Slog.i(TAG, "Loading recents for user " + userId + " into memory."); 124 addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks)); 125 cleanupLocked(userId); 126 mUsersWithRecentsLoaded.put(userId, true); 127 128 // If we have tasks added before loading recents, we need to update persistent task IDs. 129 if (preaddedTasks.size() > 0) { 130 syncPersistentTaskIdsLocked(); 131 } 132 } 133 134 private void loadPersistedTaskIdsForUserLocked(int userId) { 135 // An empty instead of a null set here means that no persistent taskIds were present 136 // on file when we loaded them. 137 if (mPersistedTaskIds.get(userId) == null) { 138 mPersistedTaskIds.put(userId, mTaskPersister.loadPersistedTaskIdsForUser(userId)); 139 Slog.i(TAG, "Loaded persisted task ids for user " + userId); 140 } 141 } 142 143 boolean taskIdTakenForUserLocked(int taskId, int userId) { 144 loadPersistedTaskIdsForUserLocked(userId); 145 return mPersistedTaskIds.get(userId).get(taskId); 146 } 147 148 void notifyTaskPersisterLocked(TaskRecord task, boolean flush) { 149 final ActivityStack stack = task != null ? task.getStack() : null; 150 if (stack != null && stack.isHomeOrRecentsStack()) { 151 // Never persist the home or recents stack. 152 return; 153 } 154 syncPersistentTaskIdsLocked(); 155 mTaskPersister.wakeup(task, flush); 156 } 157 158 private void syncPersistentTaskIdsLocked() { 159 for (int i = mPersistedTaskIds.size() - 1; i >= 0; i--) { 160 int userId = mPersistedTaskIds.keyAt(i); 161 if (mUsersWithRecentsLoaded.get(userId)) { 162 // Recents are loaded only after task ids are loaded. Therefore, the set of taskids 163 // referenced here should not be null. 164 mPersistedTaskIds.valueAt(i).clear(); 165 } 166 } 167 for (int i = size() - 1; i >= 0; i--) { 168 final TaskRecord task = get(i); 169 if (shouldPersistTaskLocked(task)) { 170 // Set of persisted taskIds for task.userId should not be null here 171 // TODO Investigate why it can happen. For now initialize with an empty set 172 if (mPersistedTaskIds.get(task.userId) == null) { 173 Slog.wtf(TAG, "No task ids found for userId " + task.userId + ". task=" + task 174 + " mPersistedTaskIds=" + mPersistedTaskIds); 175 mPersistedTaskIds.put(task.userId, new SparseBooleanArray()); 176 } 177 mPersistedTaskIds.get(task.userId).put(task.taskId, true); 178 } 179 } 180 } 181 182 private static boolean shouldPersistTaskLocked(TaskRecord task) { 183 final ActivityStack<?> stack = task.getStack(); 184 return task.isPersistable && (stack == null || !stack.isHomeOrRecentsStack()); 185 } 186 187 void onSystemReadyLocked() { 188 clear(); 189 mTaskPersister.startPersisting(); 190 } 191 192 Bitmap getTaskDescriptionIcon(String path) { 193 return mTaskPersister.getTaskDescriptionIcon(path); 194 } 195 196 Bitmap getImageFromWriteQueue(String path) { 197 return mTaskPersister.getImageFromWriteQueue(path); 198 } 199 200 void saveImage(Bitmap image, String path) { 201 mTaskPersister.saveImage(image, path); 202 } 203 204 void flush() { 205 synchronized (mService) { 206 syncPersistentTaskIdsLocked(); 207 } 208 mTaskPersister.flush(); 209 } 210 211 /** 212 * Returns all userIds for which recents from persistent storage are loaded into this list. 213 * 214 * @return an array of userIds. 215 */ 216 int[] usersWithRecentsLoadedLocked() { 217 int[] usersWithRecentsLoaded = new int[mUsersWithRecentsLoaded.size()]; 218 int len = 0; 219 for (int i = 0; i < usersWithRecentsLoaded.length; i++) { 220 int userId = mUsersWithRecentsLoaded.keyAt(i); 221 if (mUsersWithRecentsLoaded.valueAt(i)) { 222 usersWithRecentsLoaded[len++] = userId; 223 } 224 } 225 if (len < usersWithRecentsLoaded.length) { 226 // should never happen. 227 return Arrays.copyOf(usersWithRecentsLoaded, len); 228 } 229 return usersWithRecentsLoaded; 230 } 231 232 private void unloadUserRecentsLocked(int userId) { 233 if (mUsersWithRecentsLoaded.get(userId)) { 234 Slog.i(TAG, "Unloading recents for user " + userId + " from memory."); 235 mUsersWithRecentsLoaded.delete(userId); 236 removeTasksForUserLocked(userId); 237 } 238 } 239 240 /** 241 * Removes recent tasks and any other state kept in memory for the passed in user. Does not 242 * touch the information present on persistent storage. 243 * 244 * @param userId the id of the user 245 */ 246 void unloadUserDataFromMemoryLocked(int userId) { 247 unloadUserRecentsLocked(userId); 248 mPersistedTaskIds.delete(userId); 249 mTaskPersister.unloadUserDataFromMemory(userId); 250 } 251 252 TaskRecord taskForIdLocked(int id) { 253 final int recentsCount = size(); 254 for (int i = 0; i < recentsCount; i++) { 255 TaskRecord tr = get(i); 256 if (tr.taskId == id) { 257 return tr; 258 } 259 } 260 return null; 261 } 262 263 /** Remove recent tasks for a user. */ 264 void removeTasksForUserLocked(int userId) { 265 if(userId <= 0) { 266 Slog.i(TAG, "Can't remove recent task on user " + userId); 267 return; 268 } 269 270 for (int i = size() - 1; i >= 0; --i) { 271 TaskRecord tr = get(i); 272 if (tr.userId == userId) { 273 if(DEBUG_TASKS) Slog.i(TAG_TASKS, 274 "remove RecentTask " + tr + " when finishing user" + userId); 275 remove(i); 276 tr.removedFromRecents(); 277 } 278 } 279 } 280 281 void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) { 282 final Set<String> packageNames = Sets.newHashSet(packages); 283 for (int i = size() - 1; i >= 0; --i) { 284 final TaskRecord tr = get(i); 285 if (tr.realActivity != null 286 && packageNames.contains(tr.realActivity.getPackageName()) 287 && tr.userId == userId 288 && tr.realActivitySuspended != suspended) { 289 tr.realActivitySuspended = suspended; 290 notifyTaskPersisterLocked(tr, false); 291 } 292 } 293 294 } 295 296 /** 297 * Update the recent tasks lists: make sure tasks should still be here (their 298 * applications / activities still exist), update their availability, fix-up ordering 299 * of affiliations. 300 */ 301 void cleanupLocked(int userId) { 302 int recentsCount = size(); 303 if (recentsCount == 0) { 304 // Happens when called from the packagemanager broadcast before boot, 305 // or just any empty list. 306 return; 307 } 308 309 final IPackageManager pm = AppGlobals.getPackageManager(); 310 for (int i = recentsCount - 1; i >= 0; i--) { 311 final TaskRecord task = get(i); 312 if (userId != UserHandle.USER_ALL && task.userId != userId) { 313 // Only look at tasks for the user ID of interest. 314 continue; 315 } 316 if (task.autoRemoveRecents && task.getTopActivity() == null) { 317 // This situation is broken, and we should just get rid of it now. 318 remove(i); 319 task.removedFromRecents(); 320 Slog.w(TAG, "Removing auto-remove without activity: " + task); 321 continue; 322 } 323 // Check whether this activity is currently available. 324 if (task.realActivity != null) { 325 ActivityInfo ai = mTmpAvailActCache.get(task.realActivity); 326 if (ai == null) { 327 try { 328 // At this first cut, we're only interested in 329 // activities that are fully runnable based on 330 // current system state. 331 ai = pm.getActivityInfo(task.realActivity, 332 PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId); 333 } catch (RemoteException e) { 334 // Will never happen. 335 continue; 336 } 337 if (ai == null) { 338 ai = mTmpActivityInfo; 339 } 340 mTmpAvailActCache.put(task.realActivity, ai); 341 } 342 if (ai == mTmpActivityInfo) { 343 // This could be either because the activity no longer exists, or the 344 // app is temporarily gone. For the former we want to remove the recents 345 // entry; for the latter we want to mark it as unavailable. 346 ApplicationInfo app = mTmpAvailAppCache 347 .get(task.realActivity.getPackageName()); 348 if (app == null) { 349 try { 350 app = pm.getApplicationInfo(task.realActivity.getPackageName(), 351 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 352 } catch (RemoteException e) { 353 // Will never happen. 354 continue; 355 } 356 if (app == null) { 357 app = mTmpAppInfo; 358 } 359 mTmpAvailAppCache.put(task.realActivity.getPackageName(), app); 360 } 361 if (app == mTmpAppInfo 362 || (app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 363 // Doesn't exist any more! Good-bye. 364 remove(i); 365 task.removedFromRecents(); 366 Slog.w(TAG, "Removing no longer valid recent: " + task); 367 continue; 368 } else { 369 // Otherwise just not available for now. 370 if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS, 371 "Making recent unavailable: " + task); 372 task.isAvailable = false; 373 } 374 } else { 375 if (!ai.enabled || !ai.applicationInfo.enabled 376 || (ai.applicationInfo.flags 377 & ApplicationInfo.FLAG_INSTALLED) == 0) { 378 if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS, 379 "Making recent unavailable: " + task 380 + " (enabled=" + ai.enabled + "/" 381 + ai.applicationInfo.enabled 382 + " flags=" 383 + Integer.toHexString(ai.applicationInfo.flags) 384 + ")"); 385 task.isAvailable = false; 386 } else { 387 if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS, 388 "Making recent available: " + task); 389 task.isAvailable = true; 390 } 391 } 392 } 393 } 394 395 // Verify the affiliate chain for each task. 396 int i = 0; 397 recentsCount = size(); 398 while (i < recentsCount) { 399 i = processNextAffiliateChainLocked(i); 400 } 401 // recent tasks are now in sorted, affiliated order. 402 } 403 404 private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { 405 int recentsCount = size(); 406 TaskRecord top = task; 407 int topIndex = taskIndex; 408 while (top.mNextAffiliate != null && topIndex > 0) { 409 top = top.mNextAffiliate; 410 topIndex--; 411 } 412 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at " 413 + topIndex + " from intial " + taskIndex); 414 // Find the end of the chain, doing a sanity check along the way. 415 boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; 416 int endIndex = topIndex; 417 TaskRecord prev = top; 418 while (endIndex < recentsCount) { 419 TaskRecord cur = get(endIndex); 420 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @" 421 + endIndex + " " + cur); 422 if (cur == top) { 423 // Verify start of the chain. 424 if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { 425 Slog.wtf(TAG, "Bad chain @" + endIndex 426 + ": first task has next affiliate: " + prev); 427 sane = false; 428 break; 429 } 430 } else { 431 // Verify middle of the chain's next points back to the one before. 432 if (cur.mNextAffiliate != prev 433 || cur.mNextAffiliateTaskId != prev.taskId) { 434 Slog.wtf(TAG, "Bad chain @" + endIndex 435 + ": middle task " + cur + " @" + endIndex 436 + " has bad next affiliate " 437 + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId 438 + ", expected " + prev); 439 sane = false; 440 break; 441 } 442 } 443 if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { 444 // Chain ends here. 445 if (cur.mPrevAffiliate != null) { 446 Slog.wtf(TAG, "Bad chain @" + endIndex 447 + ": last task " + cur + " has previous affiliate " 448 + cur.mPrevAffiliate); 449 sane = false; 450 } 451 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex); 452 break; 453 } else { 454 // Verify middle of the chain's prev points to a valid item. 455 if (cur.mPrevAffiliate == null) { 456 Slog.wtf(TAG, "Bad chain @" + endIndex 457 + ": task " + cur + " has previous affiliate " 458 + cur.mPrevAffiliate + " but should be id " 459 + cur.mPrevAffiliate); 460 sane = false; 461 break; 462 } 463 } 464 if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { 465 Slog.wtf(TAG, "Bad chain @" + endIndex 466 + ": task " + cur + " has affiliated id " 467 + cur.mAffiliatedTaskId + " but should be " 468 + task.mAffiliatedTaskId); 469 sane = false; 470 break; 471 } 472 prev = cur; 473 endIndex++; 474 if (endIndex >= recentsCount) { 475 Slog.wtf(TAG, "Bad chain ran off index " + endIndex 476 + ": last task " + prev); 477 sane = false; 478 break; 479 } 480 } 481 if (sane) { 482 if (endIndex < taskIndex) { 483 Slog.wtf(TAG, "Bad chain @" + endIndex 484 + ": did not extend to task " + task + " @" + taskIndex); 485 sane = false; 486 } 487 } 488 if (sane) { 489 // All looks good, we can just move all of the affiliated tasks 490 // to the top. 491 for (int i=topIndex; i<=endIndex; i++) { 492 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task 493 + " from " + i + " to " + (i-topIndex)); 494 TaskRecord cur = remove(i); 495 add(i - topIndex, cur); 496 } 497 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex 498 + " to " + endIndex); 499 return true; 500 } 501 502 // Whoops, couldn't do it. 503 return false; 504 } 505 506 final void addLocked(TaskRecord task) { 507 final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId 508 || task.mNextAffiliateTaskId != INVALID_TASK_ID 509 || task.mPrevAffiliateTaskId != INVALID_TASK_ID; 510 511 int recentsCount = size(); 512 // Quick case: never add voice sessions. 513 // TODO: VI what about if it's just an activity? 514 // Probably nothing to do here 515 if (task.voiceSession != null) { 516 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 517 "addRecent: not adding voice interaction " + task); 518 return; 519 } 520 // Another quick case: check if the top-most recent task is the same. 521 if (!isAffiliated && recentsCount > 0 && get(0) == task) { 522 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task); 523 return; 524 } 525 // Another quick case: check if this is part of a set of affiliated 526 // tasks that are at the top. 527 if (isAffiliated && recentsCount > 0 && task.inRecents 528 && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) { 529 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0) 530 + " at top when adding " + task); 531 return; 532 } 533 534 boolean needAffiliationFix = false; 535 536 // Slightly less quick case: the task is already in recents, so all we need 537 // to do is move it. 538 if (task.inRecents) { 539 int taskIndex = indexOf(task); 540 if (taskIndex >= 0) { 541 if (!isAffiliated || MOVE_AFFILIATED_TASKS_TO_FRONT) { 542 // Simple case: this is not an affiliated task, so we just move it to the front. 543 remove(taskIndex); 544 add(0, task); 545 notifyTaskPersisterLocked(task, false); 546 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task 547 + " from " + taskIndex); 548 return; 549 } else { 550 // More complicated: need to keep all affiliated tasks together. 551 if (moveAffiliatedTasksToFront(task, taskIndex)) { 552 // All went well. 553 return; 554 } 555 556 // Uh oh... something bad in the affiliation chain, try to rebuild 557 // everything and then go through our general path of adding a new task. 558 needAffiliationFix = true; 559 } 560 } else { 561 Slog.wtf(TAG, "Task with inRecent not in recents: " + task); 562 needAffiliationFix = true; 563 } 564 } 565 566 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task); 567 trimForTaskLocked(task, true); 568 569 recentsCount = size(); 570 final int maxRecents = ActivityManager.getMaxRecentTasksStatic(); 571 while (recentsCount >= maxRecents) { 572 final TaskRecord tr = remove(recentsCount - 1); 573 tr.removedFromRecents(); 574 recentsCount--; 575 } 576 task.inRecents = true; 577 if (!isAffiliated || needAffiliationFix) { 578 // If this is a simple non-affiliated task, or we had some failure trying to 579 // handle it as part of an affilated task, then just place it at the top. 580 add(0, task); 581 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task); 582 } else if (isAffiliated) { 583 // If this is a new affiliated task, then move all of the affiliated tasks 584 // to the front and insert this new one. 585 TaskRecord other = task.mNextAffiliate; 586 if (other == null) { 587 other = task.mPrevAffiliate; 588 } 589 if (other != null) { 590 int otherIndex = indexOf(other); 591 if (otherIndex >= 0) { 592 // Insert new task at appropriate location. 593 int taskIndex; 594 if (other == task.mNextAffiliate) { 595 // We found the index of our next affiliation, which is who is 596 // before us in the list, so add after that point. 597 taskIndex = otherIndex+1; 598 } else { 599 // We found the index of our previous affiliation, which is who is 600 // after us in the list, so add at their position. 601 taskIndex = otherIndex; 602 } 603 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 604 "addRecent: new affiliated task added at " + taskIndex + ": " + task); 605 add(taskIndex, task); 606 607 // Now move everything to the front. 608 if (moveAffiliatedTasksToFront(task, taskIndex)) { 609 // All went well. 610 return; 611 } 612 613 // Uh oh... something bad in the affiliation chain, try to rebuild 614 // everything and then go through our general path of adding a new task. 615 needAffiliationFix = true; 616 } else { 617 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 618 "addRecent: couldn't find other affiliation " + other); 619 needAffiliationFix = true; 620 } 621 } else { 622 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 623 "addRecent: adding affiliated task without next/prev:" + task); 624 needAffiliationFix = true; 625 } 626 } 627 628 if (needAffiliationFix) { 629 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations"); 630 cleanupLocked(task.userId); 631 } 632 } 633 634 /** 635 * If needed, remove oldest existing entries in recents that are for the same kind 636 * of task as the given one. 637 */ 638 int trimForTaskLocked(TaskRecord task, boolean doTrim) { 639 int recentsCount = size(); 640 final Intent intent = task.intent; 641 final boolean document = intent != null && intent.isDocument(); 642 int maxRecents = task.maxRecents - 1; 643 final ActivityStack stack = task.getStack(); 644 for (int i = 0; i < recentsCount; i++) { 645 final TaskRecord tr = get(i); 646 final ActivityStack trStack = tr.getStack(); 647 if (task != tr) { 648 if (stack != null && trStack != null && stack != trStack) { 649 continue; 650 } 651 if (task.userId != tr.userId) { 652 continue; 653 } 654 if (i > MAX_RECENT_BITMAPS) { 655 tr.freeLastThumbnail(); 656 } 657 final Intent trIntent = tr.intent; 658 final boolean sameAffinity = 659 task.affinity != null && task.affinity.equals(tr.affinity); 660 final boolean sameIntentFilter = intent != null && intent.filterEquals(trIntent); 661 boolean multiTasksAllowed = false; 662 final int flags = intent.getFlags(); 663 if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0 664 && (flags & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { 665 multiTasksAllowed = true; 666 } 667 final boolean trIsDocument = trIntent != null && trIntent.isDocument(); 668 final boolean bothDocuments = document && trIsDocument; 669 if (!sameAffinity && !sameIntentFilter && !bothDocuments) { 670 continue; 671 } 672 673 if (bothDocuments) { 674 // Do these documents belong to the same activity? 675 final boolean sameActivity = task.realActivity != null 676 && tr.realActivity != null 677 && task.realActivity.equals(tr.realActivity); 678 // If the document is open in another app or is not the same 679 // document, we don't need to trim it. 680 if (!sameActivity) { 681 continue; 682 // Otherwise only trim if we are over our max recents for this task 683 } else if (maxRecents > 0) { 684 --maxRecents; 685 if (!doTrim || !sameIntentFilter || multiTasksAllowed) { 686 // We don't want to trim if we are not over the max allowed entries and 687 // the caller doesn't want us to trim, the tasks are not of the same 688 // intent filter, or multiple entries fot the task is allowed. 689 continue; 690 } 691 } 692 // Hit the maximum number of documents for this task. Fall through 693 // and remove this document from recents. 694 } else if (document || trIsDocument) { 695 // Only one of these is a document. Not the droid we're looking for. 696 continue; 697 } 698 } 699 700 if (!doTrim) { 701 // If the caller is not actually asking for a trim, just tell them we reached 702 // a point where the trim would happen. 703 return i; 704 } 705 706 // Either task and tr are the same or, their affinities match or their intents match 707 // and neither of them is a document, or they are documents using the same activity 708 // and their maxRecents has been reached. 709 tr.disposeThumbnail(); 710 remove(i); 711 if (task != tr) { 712 tr.removedFromRecents(); 713 } 714 i--; 715 recentsCount--; 716 if (task.intent == null) { 717 // If the new recent task we are adding is not fully 718 // specified, then replace it with the existing recent task. 719 task = tr; 720 } 721 notifyTaskPersisterLocked(tr, false); 722 } 723 724 return -1; 725 } 726 727 // Sort by taskId 728 private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() { 729 @Override 730 public int compare(TaskRecord lhs, TaskRecord rhs) { 731 return rhs.taskId - lhs.taskId; 732 } 733 }; 734 735 // Extract the affiliates of the chain containing recent at index start. 736 private int processNextAffiliateChainLocked(int start) { 737 final TaskRecord startTask = get(start); 738 final int affiliateId = startTask.mAffiliatedTaskId; 739 740 // Quick identification of isolated tasks. I.e. those not launched behind. 741 if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null && 742 startTask.mNextAffiliate == null) { 743 // There is still a slim chance that there are other tasks that point to this task 744 // and that the chain is so messed up that this task no longer points to them but 745 // the gain of this optimization outweighs the risk. 746 startTask.inRecents = true; 747 return start + 1; 748 } 749 750 // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents. 751 mTmpRecents.clear(); 752 for (int i = size() - 1; i >= start; --i) { 753 final TaskRecord task = get(i); 754 if (task.mAffiliatedTaskId == affiliateId) { 755 remove(i); 756 mTmpRecents.add(task); 757 } 758 } 759 760 // Sort them all by taskId. That is the order they were create in and that order will 761 // always be correct. 762 Collections.sort(mTmpRecents, sTaskRecordComparator); 763 764 // Go through and fix up the linked list. 765 // The first one is the end of the chain and has no next. 766 final TaskRecord first = mTmpRecents.get(0); 767 first.inRecents = true; 768 if (first.mNextAffiliate != null) { 769 Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate); 770 first.setNextAffiliate(null); 771 notifyTaskPersisterLocked(first, false); 772 } 773 // Everything in the middle is doubly linked from next to prev. 774 final int tmpSize = mTmpRecents.size(); 775 for (int i = 0; i < tmpSize - 1; ++i) { 776 final TaskRecord next = mTmpRecents.get(i); 777 final TaskRecord prev = mTmpRecents.get(i + 1); 778 if (next.mPrevAffiliate != prev) { 779 Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate + 780 " setting prev=" + prev); 781 next.setPrevAffiliate(prev); 782 notifyTaskPersisterLocked(next, false); 783 } 784 if (prev.mNextAffiliate != next) { 785 Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate + 786 " setting next=" + next); 787 prev.setNextAffiliate(next); 788 notifyTaskPersisterLocked(prev, false); 789 } 790 prev.inRecents = true; 791 } 792 // The last one is the beginning of the list and has no prev. 793 final TaskRecord last = mTmpRecents.get(tmpSize - 1); 794 if (last.mPrevAffiliate != null) { 795 Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate); 796 last.setPrevAffiliate(null); 797 notifyTaskPersisterLocked(last, false); 798 } 799 800 // Insert the group back into mRecentTasks at start. 801 addAll(start, mTmpRecents); 802 mTmpRecents.clear(); 803 804 // Let the caller know where we left off. 805 return start + tmpSize; 806 } 807 } 808