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