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.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