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
17package com.android.server.am;
18
19import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
20import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
21import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
22import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
23import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
24import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
25import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
26import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
27import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
28import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
29
30import com.google.android.collect.Sets;
31
32import android.app.ActivityManager;
33import android.app.AppGlobals;
34import android.content.ComponentName;
35import android.content.Intent;
36import android.content.pm.ActivityInfo;
37import android.content.pm.ApplicationInfo;
38import android.content.pm.IPackageManager;
39import android.content.pm.PackageManager;
40import android.graphics.Bitmap;
41import android.os.Environment;
42import android.os.RemoteException;
43import android.os.UserHandle;
44import android.util.ArraySet;
45import android.util.Slog;
46import android.util.SparseArray;
47import android.util.SparseBooleanArray;
48
49import java.io.File;
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.Collections;
53import java.util.Comparator;
54import java.util.HashMap;
55import java.util.Set;
56
57/**
58 * Class for managing the recent tasks list.
59 */
60class 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