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 com.android.server.am.ActivityManagerDebugConfig.*; 20 import static com.android.server.am.TaskRecord.INVALID_TASK_ID; 21 22 import android.app.ActivityManager; 23 import android.app.AppGlobals; 24 import android.content.ComponentName; 25 import android.content.Intent; 26 import android.content.pm.ActivityInfo; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.IPackageManager; 29 import android.content.pm.PackageManager; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.util.Slog; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.HashMap; 38 39 /** 40 * Class for managing the recent tasks list. 41 */ 42 class RecentTasks extends ArrayList<TaskRecord> { 43 private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM; 44 private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; 45 private static final String TAG_TASKS = TAG + POSTFIX_TASKS; 46 47 // Maximum number recent bitmaps to keep in memory. 48 private static final int MAX_RECENT_BITMAPS = 3; 49 50 // Activity manager service. 51 private final ActivityManagerService mService; 52 53 // Mainly to avoid object recreation on multiple calls. 54 private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>(); 55 private final HashMap<ComponentName, ActivityInfo> tmpAvailActCache = new HashMap<>(); 56 private final HashMap<String, ApplicationInfo> tmpAvailAppCache = new HashMap<>(); 57 private final ActivityInfo tmpActivityInfo = new ActivityInfo(); 58 private final ApplicationInfo tmpAppInfo = new ApplicationInfo(); 59 RecentTasks(ActivityManagerService service)60 RecentTasks(ActivityManagerService service) { 61 mService = service; 62 } 63 taskForIdLocked(int id)64 TaskRecord taskForIdLocked(int id) { 65 final int recentsCount = size(); 66 for (int i = 0; i < recentsCount; i++) { 67 TaskRecord tr = get(i); 68 if (tr.taskId == id) { 69 return tr; 70 } 71 } 72 return null; 73 } 74 75 /** Remove recent tasks for a user. */ removeTasksForUserLocked(int userId)76 void removeTasksForUserLocked(int userId) { 77 if(userId <= 0) { 78 Slog.i(TAG, "Can't remove recent task on user " + userId); 79 return; 80 } 81 82 for (int i = size() - 1; i >= 0; --i) { 83 TaskRecord tr = get(i); 84 if (tr.userId == userId) { 85 if(DEBUG_TASKS) Slog.i(TAG_TASKS, 86 "remove RecentTask " + tr + " when finishing user" + userId); 87 remove(i); 88 tr.removedFromRecents(); 89 } 90 } 91 92 // Remove tasks from persistent storage. 93 mService.notifyTaskPersisterLocked(null, true); 94 } 95 96 /** 97 * Update the recent tasks lists: make sure tasks should still be here (their 98 * applications / activities still exist), update their availability, fix-up ordering 99 * of affiliations. 100 */ cleanupLocked(int userId)101 void cleanupLocked(int userId) { 102 int recentsCount = size(); 103 if (recentsCount == 0) { 104 // Happens when called from the packagemanager broadcast before boot, 105 // or just any empty list. 106 return; 107 } 108 109 final IPackageManager pm = AppGlobals.getPackageManager(); 110 final int[] users = (userId == UserHandle.USER_ALL) 111 ? mService.getUsersLocked() : new int[] { userId }; 112 for (int userIdx = 0; userIdx < users.length; userIdx++) { 113 final int user = users[userIdx]; 114 recentsCount = size() - 1; 115 for (int i = recentsCount; i >= 0; i--) { 116 TaskRecord task = get(i); 117 if (task.userId != user) { 118 // Only look at tasks for the user ID of interest. 119 continue; 120 } 121 if (task.autoRemoveRecents && task.getTopActivity() == null) { 122 // This situation is broken, and we should just get rid of it now. 123 remove(i); 124 task.removedFromRecents(); 125 Slog.w(TAG, "Removing auto-remove without activity: " + task); 126 continue; 127 } 128 // Check whether this activity is currently available. 129 if (task.realActivity != null) { 130 ActivityInfo ai = tmpAvailActCache.get(task.realActivity); 131 if (ai == null) { 132 try { 133 ai = pm.getActivityInfo(task.realActivity, 134 PackageManager.GET_UNINSTALLED_PACKAGES 135 | PackageManager.GET_DISABLED_COMPONENTS, user); 136 } catch (RemoteException e) { 137 // Will never happen. 138 continue; 139 } 140 if (ai == null) { 141 ai = tmpActivityInfo; 142 } 143 tmpAvailActCache.put(task.realActivity, ai); 144 } 145 if (ai == tmpActivityInfo) { 146 // This could be either because the activity no longer exists, or the 147 // app is temporarily gone. For the former we want to remove the recents 148 // entry; for the latter we want to mark it as unavailable. 149 ApplicationInfo app = tmpAvailAppCache.get(task.realActivity.getPackageName()); 150 if (app == null) { 151 try { 152 app = pm.getApplicationInfo(task.realActivity.getPackageName(), 153 PackageManager.GET_UNINSTALLED_PACKAGES 154 | PackageManager.GET_DISABLED_COMPONENTS, user); 155 } catch (RemoteException e) { 156 // Will never happen. 157 continue; 158 } 159 if (app == null) { 160 app = tmpAppInfo; 161 } 162 tmpAvailAppCache.put(task.realActivity.getPackageName(), app); 163 } 164 if (app == tmpAppInfo || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 165 // Doesn't exist any more! Good-bye. 166 remove(i); 167 task.removedFromRecents(); 168 Slog.w(TAG, "Removing no longer valid recent: " + task); 169 continue; 170 } else { 171 // Otherwise just not available for now. 172 if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS, 173 "Making recent unavailable: " + task); 174 task.isAvailable = false; 175 } 176 } else { 177 if (!ai.enabled || !ai.applicationInfo.enabled 178 || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 179 if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS, 180 "Making recent unavailable: " + task 181 + " (enabled=" + ai.enabled + "/" + ai.applicationInfo.enabled 182 + " flags=" + Integer.toHexString(ai.applicationInfo.flags) 183 + ")"); 184 task.isAvailable = false; 185 } else { 186 if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS, 187 "Making recent available: " + task); 188 task.isAvailable = true; 189 } 190 } 191 } 192 } 193 } 194 195 // Verify the affiliate chain for each task. 196 int i = 0; 197 recentsCount = size(); 198 while (i < recentsCount) { 199 i = processNextAffiliateChainLocked(i); 200 } 201 // recent tasks are now in sorted, affiliated order. 202 } 203 moveAffiliatedTasksToFront(TaskRecord task, int taskIndex)204 private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { 205 int recentsCount = size(); 206 TaskRecord top = task; 207 int topIndex = taskIndex; 208 while (top.mNextAffiliate != null && topIndex > 0) { 209 top = top.mNextAffiliate; 210 topIndex--; 211 } 212 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at " 213 + topIndex + " from intial " + taskIndex); 214 // Find the end of the chain, doing a sanity check along the way. 215 boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; 216 int endIndex = topIndex; 217 TaskRecord prev = top; 218 while (endIndex < recentsCount) { 219 TaskRecord cur = get(endIndex); 220 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @" 221 + endIndex + " " + cur); 222 if (cur == top) { 223 // Verify start of the chain. 224 if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { 225 Slog.wtf(TAG, "Bad chain @" + endIndex 226 + ": first task has next affiliate: " + prev); 227 sane = false; 228 break; 229 } 230 } else { 231 // Verify middle of the chain's next points back to the one before. 232 if (cur.mNextAffiliate != prev 233 || cur.mNextAffiliateTaskId != prev.taskId) { 234 Slog.wtf(TAG, "Bad chain @" + endIndex 235 + ": middle task " + cur + " @" + endIndex 236 + " has bad next affiliate " 237 + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId 238 + ", expected " + prev); 239 sane = false; 240 break; 241 } 242 } 243 if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { 244 // Chain ends here. 245 if (cur.mPrevAffiliate != null) { 246 Slog.wtf(TAG, "Bad chain @" + endIndex 247 + ": last task " + cur + " has previous affiliate " 248 + cur.mPrevAffiliate); 249 sane = false; 250 } 251 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex); 252 break; 253 } else { 254 // Verify middle of the chain's prev points to a valid item. 255 if (cur.mPrevAffiliate == null) { 256 Slog.wtf(TAG, "Bad chain @" + endIndex 257 + ": task " + cur + " has previous affiliate " 258 + cur.mPrevAffiliate + " but should be id " 259 + cur.mPrevAffiliate); 260 sane = false; 261 break; 262 } 263 } 264 if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { 265 Slog.wtf(TAG, "Bad chain @" + endIndex 266 + ": task " + cur + " has affiliated id " 267 + cur.mAffiliatedTaskId + " but should be " 268 + task.mAffiliatedTaskId); 269 sane = false; 270 break; 271 } 272 prev = cur; 273 endIndex++; 274 if (endIndex >= recentsCount) { 275 Slog.wtf(TAG, "Bad chain ran off index " + endIndex 276 + ": last task " + prev); 277 sane = false; 278 break; 279 } 280 } 281 if (sane) { 282 if (endIndex < taskIndex) { 283 Slog.wtf(TAG, "Bad chain @" + endIndex 284 + ": did not extend to task " + task + " @" + taskIndex); 285 sane = false; 286 } 287 } 288 if (sane) { 289 // All looks good, we can just move all of the affiliated tasks 290 // to the top. 291 for (int i=topIndex; i<=endIndex; i++) { 292 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task 293 + " from " + i + " to " + (i-topIndex)); 294 TaskRecord cur = remove(i); 295 add(i - topIndex, cur); 296 } 297 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex 298 + " to " + endIndex); 299 return true; 300 } 301 302 // Whoops, couldn't do it. 303 return false; 304 } 305 addLocked(TaskRecord task)306 final void addLocked(TaskRecord task) { 307 final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId 308 || task.mNextAffiliateTaskId != INVALID_TASK_ID 309 || task.mPrevAffiliateTaskId != INVALID_TASK_ID; 310 311 int recentsCount = size(); 312 // Quick case: never add voice sessions. 313 if (task.voiceSession != null) { 314 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 315 "addRecent: not adding voice interaction " + task); 316 return; 317 } 318 // Another quick case: check if the top-most recent task is the same. 319 if (!isAffiliated && recentsCount > 0 && get(0) == task) { 320 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task); 321 return; 322 } 323 // Another quick case: check if this is part of a set of affiliated 324 // tasks that are at the top. 325 if (isAffiliated && recentsCount > 0 && task.inRecents 326 && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) { 327 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0) 328 + " at top when adding " + task); 329 return; 330 } 331 332 boolean needAffiliationFix = false; 333 334 // Slightly less quick case: the task is already in recents, so all we need 335 // to do is move it. 336 if (task.inRecents) { 337 int taskIndex = indexOf(task); 338 if (taskIndex >= 0) { 339 if (!isAffiliated) { 340 // Simple case: this is not an affiliated task, so we just move it to the front. 341 remove(taskIndex); 342 add(0, task); 343 mService.notifyTaskPersisterLocked(task, false); 344 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task 345 + " from " + taskIndex); 346 return; 347 } else { 348 // More complicated: need to keep all affiliated tasks together. 349 if (moveAffiliatedTasksToFront(task, taskIndex)) { 350 // All went well. 351 return; 352 } 353 354 // Uh oh... something bad in the affiliation chain, try to rebuild 355 // everything and then go through our general path of adding a new task. 356 needAffiliationFix = true; 357 } 358 } else { 359 Slog.wtf(TAG, "Task with inRecent not in recents: " + task); 360 needAffiliationFix = true; 361 } 362 } 363 364 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task); 365 trimForTaskLocked(task, true); 366 367 recentsCount = size(); 368 final int maxRecents = ActivityManager.getMaxRecentTasksStatic(); 369 while (recentsCount >= maxRecents) { 370 final TaskRecord tr = remove(recentsCount - 1); 371 tr.removedFromRecents(); 372 recentsCount--; 373 } 374 task.inRecents = true; 375 if (!isAffiliated || needAffiliationFix) { 376 // If this is a simple non-affiliated task, or we had some failure trying to 377 // handle it as part of an affilated task, then just place it at the top. 378 add(0, task); 379 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task); 380 } else if (isAffiliated) { 381 // If this is a new affiliated task, then move all of the affiliated tasks 382 // to the front and insert this new one. 383 TaskRecord other = task.mNextAffiliate; 384 if (other == null) { 385 other = task.mPrevAffiliate; 386 } 387 if (other != null) { 388 int otherIndex = indexOf(other); 389 if (otherIndex >= 0) { 390 // Insert new task at appropriate location. 391 int taskIndex; 392 if (other == task.mNextAffiliate) { 393 // We found the index of our next affiliation, which is who is 394 // before us in the list, so add after that point. 395 taskIndex = otherIndex+1; 396 } else { 397 // We found the index of our previous affiliation, which is who is 398 // after us in the list, so add at their position. 399 taskIndex = otherIndex; 400 } 401 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 402 "addRecent: new affiliated task added at " + taskIndex + ": " + task); 403 add(taskIndex, task); 404 405 // Now move everything to the front. 406 if (moveAffiliatedTasksToFront(task, taskIndex)) { 407 // All went well. 408 return; 409 } 410 411 // Uh oh... something bad in the affiliation chain, try to rebuild 412 // everything and then go through our general path of adding a new task. 413 needAffiliationFix = true; 414 } else { 415 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 416 "addRecent: couldn't find other affiliation " + other); 417 needAffiliationFix = true; 418 } 419 } else { 420 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 421 "addRecent: adding affiliated task without next/prev:" + task); 422 needAffiliationFix = true; 423 } 424 } 425 426 if (needAffiliationFix) { 427 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations"); 428 cleanupLocked(task.userId); 429 } 430 } 431 432 /** 433 * If needed, remove oldest existing entries in recents that are for the same kind 434 * of task as the given one. 435 */ trimForTaskLocked(TaskRecord task, boolean doTrim)436 int trimForTaskLocked(TaskRecord task, boolean doTrim) { 437 int recentsCount = size(); 438 final Intent intent = task.intent; 439 final boolean document = intent != null && intent.isDocument(); 440 int maxRecents = task.maxRecents - 1; 441 for (int i = 0; i < recentsCount; i++) { 442 final TaskRecord tr = get(i); 443 if (task != tr) { 444 if (task.userId != tr.userId) { 445 continue; 446 } 447 if (i > MAX_RECENT_BITMAPS) { 448 tr.freeLastThumbnail(); 449 } 450 final Intent trIntent = tr.intent; 451 final boolean sameAffinity = 452 task.affinity != null && task.affinity.equals(tr.affinity); 453 final boolean sameIntent = (intent != null && intent.filterEquals(trIntent)); 454 final boolean trIsDocument = trIntent != null && trIntent.isDocument(); 455 final boolean bothDocuments = document && trIsDocument; 456 if (!sameAffinity && !sameIntent && !bothDocuments) { 457 continue; 458 } 459 460 if (bothDocuments) { 461 // Do these documents belong to the same activity? 462 final boolean sameActivity = task.realActivity != null 463 && tr.realActivity != null 464 && task.realActivity.equals(tr.realActivity); 465 if (!sameActivity) { 466 continue; 467 } 468 if (maxRecents > 0) { 469 --maxRecents; 470 continue; 471 } 472 // Hit the maximum number of documents for this task. Fall through 473 // and remove this document from recents. 474 } else if (document || trIsDocument) { 475 // Only one of these is a document. Not the droid we're looking for. 476 continue; 477 } 478 } 479 480 if (!doTrim) { 481 // If the caller is not actually asking for a trim, just tell them we reached 482 // a point where the trim would happen. 483 return i; 484 } 485 486 // Either task and tr are the same or, their affinities match or their intents match 487 // and neither of them is a document, or they are documents using the same activity 488 // and their maxRecents has been reached. 489 tr.disposeThumbnail(); 490 remove(i); 491 if (task != tr) { 492 tr.removedFromRecents(); 493 } 494 i--; 495 recentsCount--; 496 if (task.intent == null) { 497 // If the new recent task we are adding is not fully 498 // specified, then replace it with the existing recent task. 499 task = tr; 500 } 501 mService.notifyTaskPersisterLocked(tr, false); 502 } 503 504 return -1; 505 } 506 507 // Sort by taskId 508 private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() { 509 @Override 510 public int compare(TaskRecord lhs, TaskRecord rhs) { 511 return rhs.taskId - lhs.taskId; 512 } 513 }; 514 515 // Extract the affiliates of the chain containing recent at index start. processNextAffiliateChainLocked(int start)516 private int processNextAffiliateChainLocked(int start) { 517 final TaskRecord startTask = get(start); 518 final int affiliateId = startTask.mAffiliatedTaskId; 519 520 // Quick identification of isolated tasks. I.e. those not launched behind. 521 if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null && 522 startTask.mNextAffiliate == null) { 523 // There is still a slim chance that there are other tasks that point to this task 524 // and that the chain is so messed up that this task no longer points to them but 525 // the gain of this optimization outweighs the risk. 526 startTask.inRecents = true; 527 return start + 1; 528 } 529 530 // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents. 531 mTmpRecents.clear(); 532 for (int i = size() - 1; i >= start; --i) { 533 final TaskRecord task = get(i); 534 if (task.mAffiliatedTaskId == affiliateId) { 535 remove(i); 536 mTmpRecents.add(task); 537 } 538 } 539 540 // Sort them all by taskId. That is the order they were create in and that order will 541 // always be correct. 542 Collections.sort(mTmpRecents, sTaskRecordComparator); 543 544 // Go through and fix up the linked list. 545 // The first one is the end of the chain and has no next. 546 final TaskRecord first = mTmpRecents.get(0); 547 first.inRecents = true; 548 if (first.mNextAffiliate != null) { 549 Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate); 550 first.setNextAffiliate(null); 551 mService.notifyTaskPersisterLocked(first, false); 552 } 553 // Everything in the middle is doubly linked from next to prev. 554 final int tmpSize = mTmpRecents.size(); 555 for (int i = 0; i < tmpSize - 1; ++i) { 556 final TaskRecord next = mTmpRecents.get(i); 557 final TaskRecord prev = mTmpRecents.get(i + 1); 558 if (next.mPrevAffiliate != prev) { 559 Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate + 560 " setting prev=" + prev); 561 next.setPrevAffiliate(prev); 562 mService.notifyTaskPersisterLocked(next, false); 563 } 564 if (prev.mNextAffiliate != next) { 565 Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate + 566 " setting next=" + next); 567 prev.setNextAffiliate(next); 568 mService.notifyTaskPersisterLocked(prev, false); 569 } 570 prev.inRecents = true; 571 } 572 // The last one is the beginning of the list and has no prev. 573 final TaskRecord last = mTmpRecents.get(tmpSize - 1); 574 if (last.mPrevAffiliate != null) { 575 Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate); 576 last.setPrevAffiliate(null); 577 mService.notifyTaskPersisterLocked(last, false); 578 } 579 580 // Insert the group back into mRecentTasks at start. 581 addAll(start, mTmpRecents); 582 mTmpRecents.clear(); 583 584 // Let the caller know where we left off. 585 return start + tmpSize; 586 } 587 588 } 589