TaskRecord.java revision a228ae95ea2f842c0e84f237c64bf032689410dd
1/*
2 * Copyright (C) 2006 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 com.android.server.am.ActivityManagerService.TAG;
20import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
21import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
22import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
23import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
24
25import android.app.Activity;
26import android.app.ActivityManager;
27import android.app.ActivityManager.TaskThumbnail;
28import android.app.ActivityOptions;
29import android.content.ComponentName;
30import android.content.Intent;
31import android.content.pm.ActivityInfo;
32import android.graphics.Bitmap;
33import android.os.ParcelFileDescriptor;
34import android.os.UserHandle;
35import android.service.voice.IVoiceInteractionSession;
36import android.util.Slog;
37import com.android.internal.app.IVoiceInteractor;
38import com.android.internal.util.XmlUtils;
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41import org.xmlpull.v1.XmlSerializer;
42
43import java.io.File;
44import java.io.IOException;
45import java.io.PrintWriter;
46import java.util.ArrayList;
47
48final class TaskRecord {
49    private static final String ATTR_TASKID = "task_id";
50    private static final String TAG_INTENT = "intent";
51    private static final String TAG_AFFINITYINTENT = "affinity_intent";
52    private static final String ATTR_REALACTIVITY = "real_activity";
53    private static final String ATTR_ORIGACTIVITY = "orig_activity";
54    private static final String TAG_ACTIVITY = "activity";
55    private static final String ATTR_AFFINITY = "affinity";
56    private static final String ATTR_ROOTHASRESET = "root_has_reset";
57    private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
58    private static final String ATTR_USERID = "user_id";
59    private static final String ATTR_TASKTYPE = "task_type";
60    private static final String ATTR_FIRSTACTIVETIME = "first_active_time";
61    private static final String ATTR_LASTACTIVETIME = "last_active_time";
62    private static final String ATTR_LASTDESCRIPTION = "last_description";
63    private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
64    private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
65    private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
66    private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
67    private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
68    private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
69    private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
70    private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
71
72    private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
73
74    final int taskId;       // Unique identifier for this task.
75    String affinity;        // The affinity name for this task, or null.
76    final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
77    final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
78    Intent intent;          // The original intent that started the task.
79    Intent affinityIntent;  // Intent of affinity-moved activity that started this task.
80    ComponentName origActivity; // The non-alias activity component of the intent.
81    ComponentName realActivity; // The actual activity component that started the task.
82    long firstActiveTime;   // First time this task was active.
83    long lastActiveTime;    // Last time this task was active, including sleep.
84    boolean rootWasReset;   // True if the intent at the root of the task had
85                            // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
86    boolean askedCompatMode;// Have asked the user about compat mode for this task.
87    boolean hasBeenVisible; // Set if any activities in the task have been visible to the user.
88
89    String stringName;      // caching of toString() result.
90    int userId;             // user for which this task was created
91    int creatorUid;         // The app uid that originally created the task
92
93    int numFullscreen;      // Number of fullscreen activities.
94
95    // This represents the last resolved activity values for this task
96    // NOTE: This value needs to be persisted with each task
97    ActivityManager.TaskDescription lastTaskDescription =
98            new ActivityManager.TaskDescription();
99
100    /** List of all activities in the task arranged in history order */
101    final ArrayList<ActivityRecord> mActivities;
102
103    /** Current stack */
104    ActivityStack stack;
105
106    /** Takes on same set of values as ActivityRecord.mActivityType */
107    int taskType;
108
109    /** Takes on same value as first root activity */
110    boolean isPersistable = false;
111    int maxRecents;
112
113    /** Only used for persistable tasks, otherwise 0. The last time this task was moved. Used for
114     * determining the order when restoring. Sign indicates whether last task movement was to front
115     * (positive) or back (negative). Absolute value indicates time. */
116    long mLastTimeMoved = System.currentTimeMillis();
117
118    /** True if persistable, has changed, and has not yet been persisted */
119    boolean needsPersisting = false;
120
121    /** Indication of what to run next when task exits. Use ActivityRecord types.
122     * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the
123     * task stack. */
124    private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE;
125
126    /** If original intent did not allow relinquishing task identity, save that information */
127    boolean mNeverRelinquishIdentity = true;
128
129    // Used in the unique case where we are clearing the task in order to reuse it. In that case we
130    // do not want to delete the stack when the task goes empty.
131    boolean mReuseTask = false;
132
133    private Bitmap mLastThumbnail; // Last thumbnail captured for this item.
134    private final File mLastThumbnailFile; // File containing last thubmnail.
135    private final String mFilename;
136    CharSequence lastDescription; // Last description captured for this item.
137
138    int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
139    TaskRecord mPrevAffiliate; // previous task in affiliated chain.
140    int mPrevAffiliateTaskId = -1; // previous id for persistence.
141    TaskRecord mNextAffiliate; // next task in affiliated chain.
142    int mNextAffiliateTaskId = -1; // next id for persistence.
143
144    final ActivityManagerService mService;
145
146    TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
147            IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
148        mService = service;
149        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
150                TaskPersister.IMAGE_EXTENSION;
151        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
152        taskId = _taskId;
153        mAffiliatedTaskId = _taskId;
154        voiceSession = _voiceSession;
155        voiceInteractor = _voiceInteractor;
156        mActivities = new ArrayList<ActivityRecord>();
157        setIntent(_intent, info);
158    }
159
160    TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
161            String _affinity, ComponentName _realActivity, ComponentName _origActivity,
162            boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
163            String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime,
164            long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity,
165            ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
166            int prevTaskId, int nextTaskId) {
167        mService = service;
168        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
169                TaskPersister.IMAGE_EXTENSION;
170        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
171        taskId = _taskId;
172        intent = _intent;
173        affinityIntent = _affinityIntent;
174        affinity = _affinity;
175        voiceSession = null;
176        voiceInteractor = null;
177        realActivity = _realActivity;
178        origActivity = _origActivity;
179        rootWasReset = _rootWasReset;
180        askedCompatMode = _askedCompatMode;
181        taskType = _taskType;
182        mTaskToReturnTo = HOME_ACTIVITY_TYPE;
183        userId = _userId;
184        firstActiveTime = _firstActiveTime;
185        lastActiveTime = _lastActiveTime;
186        lastDescription = _lastDescription;
187        mActivities = activities;
188        mLastTimeMoved = lastTimeMoved;
189        mNeverRelinquishIdentity = neverRelinquishIdentity;
190        lastTaskDescription = _lastTaskDescription;
191        mAffiliatedTaskId = taskAffiliation;
192        mPrevAffiliateTaskId = prevTaskId;
193        mNextAffiliateTaskId = nextTaskId;
194    }
195
196    void touchActiveTime() {
197        lastActiveTime = android.os.SystemClock.elapsedRealtime();
198        if (firstActiveTime == 0) {
199            firstActiveTime = lastActiveTime;
200        }
201    }
202
203    long getInactiveDuration() {
204        return android.os.SystemClock.elapsedRealtime() - lastActiveTime;
205    }
206
207    void setIntent(Intent _intent, ActivityInfo info) {
208        if (intent == null) {
209            mNeverRelinquishIdentity =
210                    (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
211        } else if (mNeverRelinquishIdentity) {
212            return;
213        }
214
215        affinity = info.taskAffinity;
216        stringName = null;
217
218        if (info.targetActivity == null) {
219            if (_intent != null) {
220                // If this Intent has a selector, we want to clear it for the
221                // recent task since it is not relevant if the user later wants
222                // to re-launch the app.
223                if (_intent.getSelector() != null || _intent.getSourceBounds() != null) {
224                    _intent = new Intent(_intent);
225                    _intent.setSelector(null);
226                    _intent.setSourceBounds(null);
227                }
228            }
229            if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
230                    "Setting Intent of " + this + " to " + _intent);
231            intent = _intent;
232            realActivity = _intent != null ? _intent.getComponent() : null;
233            origActivity = null;
234        } else {
235            ComponentName targetComponent = new ComponentName(
236                    info.packageName, info.targetActivity);
237            if (_intent != null) {
238                Intent targetIntent = new Intent(_intent);
239                targetIntent.setComponent(targetComponent);
240                targetIntent.setSelector(null);
241                targetIntent.setSourceBounds(null);
242                if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
243                        "Setting Intent of " + this + " to target " + targetIntent);
244                intent = targetIntent;
245                realActivity = targetComponent;
246                origActivity = _intent.getComponent();
247            } else {
248                intent = null;
249                realActivity = targetComponent;
250                origActivity = new ComponentName(info.packageName, info.name);
251            }
252        }
253
254        if (intent != null &&
255                (intent.getFlags()&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
256            // Once we are set to an Intent with this flag, we count this
257            // task as having a true root activity.
258            rootWasReset = true;
259        }
260
261        userId = UserHandle.getUserId(info.applicationInfo.uid);
262        creatorUid = info.applicationInfo.uid;
263        if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
264            intent.addFlags(Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS);
265        }
266    }
267
268    void setTaskToReturnTo(int taskToReturnTo) {
269        mTaskToReturnTo = taskToReturnTo;
270    }
271
272    int getTaskToReturnTo() {
273        return mTaskToReturnTo;
274    }
275
276    void setPrevAffiliate(TaskRecord prevAffiliate) {
277        mPrevAffiliate = prevAffiliate;
278        mPrevAffiliateTaskId = prevAffiliate == null ? -1 : prevAffiliate.taskId;
279    }
280
281    void setNextAffiliate(TaskRecord nextAffiliate) {
282        mNextAffiliate = nextAffiliate;
283        mNextAffiliateTaskId = nextAffiliate == null ? -1 : nextAffiliate.taskId;
284    }
285
286    // Close up recents linked list.
287    void closeRecentsChain() {
288        if (mPrevAffiliate != null) {
289            mPrevAffiliate.setNextAffiliate(mNextAffiliate);
290        }
291        if (mNextAffiliate != null) {
292            mNextAffiliate.setPrevAffiliate(mPrevAffiliate);
293        }
294        setPrevAffiliate(null);
295        setNextAffiliate(null);
296    }
297
298    void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
299        closeRecentsChain();
300        mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
301        // Find the end
302        while (taskToAffiliateWith.mNextAffiliate != null) {
303            final TaskRecord nextRecents = taskToAffiliateWith.mNextAffiliate;
304            if (nextRecents.mAffiliatedTaskId != mAffiliatedTaskId) {
305                Slog.e(TAG, "setTaskToAffiliateWith: nextRecents=" + nextRecents + " affilTaskId="
306                        + nextRecents.mAffiliatedTaskId + " should be " + mAffiliatedTaskId);
307                if (nextRecents.mPrevAffiliate == taskToAffiliateWith) {
308                    nextRecents.setPrevAffiliate(null);
309                }
310                taskToAffiliateWith.setNextAffiliate(null);
311                break;
312            }
313            taskToAffiliateWith = nextRecents;
314        }
315        taskToAffiliateWith.setNextAffiliate(this);
316        setPrevAffiliate(taskToAffiliateWith);
317        setNextAffiliate(null);
318    }
319
320    void setLastThumbnail(Bitmap thumbnail) {
321        mLastThumbnail = thumbnail;
322        if (thumbnail == null) {
323            if (mLastThumbnailFile != null) {
324                mLastThumbnailFile.delete();
325            }
326        } else {
327            mService.mTaskPersister.saveImage(thumbnail, mFilename);
328        }
329    }
330
331    void getLastThumbnail(TaskThumbnail thumbs) {
332        thumbs.mainThumbnail = mLastThumbnail;
333        thumbs.thumbnailFileDescriptor = null;
334        if (mLastThumbnailFile.exists()) {
335            try {
336                thumbs.thumbnailFileDescriptor = ParcelFileDescriptor.open(mLastThumbnailFile,
337                        ParcelFileDescriptor.MODE_READ_ONLY);
338            } catch (IOException e) {
339            }
340        }
341    }
342
343    void freeLastThumbnail() {
344        mLastThumbnail = null;
345    }
346
347    void disposeThumbnail() {
348        mLastThumbnail = null;
349        lastDescription = null;
350    }
351
352    /** Returns the intent for the root activity for this task */
353    Intent getBaseIntent() {
354        return intent != null ? intent : affinityIntent;
355    }
356
357    /** Returns the first non-finishing activity from the root. */
358    ActivityRecord getRootActivity() {
359        for (int i = 0; i < mActivities.size(); i++) {
360            final ActivityRecord r = mActivities.get(i);
361            if (r.finishing) {
362                continue;
363            }
364            return r;
365        }
366        return null;
367    }
368
369    ActivityRecord getTopActivity() {
370        for (int i = mActivities.size() - 1; i >= 0; --i) {
371            final ActivityRecord r = mActivities.get(i);
372            if (r.finishing) {
373                continue;
374            }
375            return r;
376        }
377        return null;
378    }
379
380    ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
381        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
382            ActivityRecord r = mActivities.get(activityNdx);
383            if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
384                return r;
385            }
386        }
387        return null;
388    }
389
390    /** Call after activity movement or finish to make sure that frontOfTask is set correctly */
391    final void setFrontOfTask() {
392        boolean foundFront = false;
393        final int numActivities = mActivities.size();
394        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
395            final ActivityRecord r = mActivities.get(activityNdx);
396            if (foundFront || r.finishing) {
397                r.frontOfTask = false;
398            } else {
399                r.frontOfTask = true;
400                // Set frontOfTask false for every following activity.
401                foundFront = true;
402            }
403        }
404        if (!foundFront && numActivities > 0) {
405            // All activities of this task are finishing. As we ought to have a frontOfTask
406            // activity, make the bottom activity front.
407            mActivities.get(0).frontOfTask = true;
408        }
409    }
410
411    /**
412     * Reorder the history stack so that the passed activity is brought to the front.
413     */
414    final void moveActivityToFrontLocked(ActivityRecord newTop) {
415        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + newTop
416            + " to stack at top", new RuntimeException("here").fillInStackTrace());
417
418        mActivities.remove(newTop);
419        mActivities.add(newTop);
420        updateEffectiveIntent();
421
422        setFrontOfTask();
423    }
424
425    void addActivityAtBottom(ActivityRecord r) {
426        addActivityAtIndex(0, r);
427    }
428
429    void addActivityToTop(ActivityRecord r) {
430        addActivityAtIndex(mActivities.size(), r);
431    }
432
433    void addActivityAtIndex(int index, ActivityRecord r) {
434        // Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
435        if (!mActivities.remove(r) && r.fullscreen) {
436            // Was not previously in list.
437            numFullscreen++;
438        }
439        // Only set this based on the first activity
440        if (mActivities.isEmpty()) {
441            taskType = r.mActivityType;
442            isPersistable = r.isPersistable();
443            // Clamp to [1, 100].
444            maxRecents = Math.min(Math.max(r.info.maxRecents, 1), 100);
445        } else {
446            // Otherwise make all added activities match this one.
447            r.mActivityType = taskType;
448        }
449        mActivities.add(index, r);
450        updateEffectiveIntent();
451        if (r.isPersistable()) {
452            mService.notifyTaskPersisterLocked(this, false);
453        }
454    }
455
456    /** @return true if this was the last activity in the task */
457    boolean removeActivity(ActivityRecord r) {
458        if (mActivities.remove(r) && r.fullscreen) {
459            // Was previously in list.
460            numFullscreen--;
461        }
462        if (r.isPersistable()) {
463            mService.notifyTaskPersisterLocked(this, false);
464        }
465        if (mActivities.isEmpty()) {
466            return !mReuseTask;
467        }
468        updateEffectiveIntent();
469        return false;
470    }
471
472    boolean autoRemoveFromRecents() {
473        // We will automatically remove the task either if it has explicitly asked for
474        // this, or it is empty and has never contained an activity that got shown to
475        // the user.
476        return (intent != null &&
477                (intent.getFlags() & Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS) != 0) ||
478                (mActivities.isEmpty() && !hasBeenVisible);
479    }
480
481    /**
482     * Completely remove all activities associated with an existing
483     * task starting at a specified index.
484     */
485    final void performClearTaskAtIndexLocked(int activityNdx) {
486        int numActivities = mActivities.size();
487        for ( ; activityNdx < numActivities; ++activityNdx) {
488            final ActivityRecord r = mActivities.get(activityNdx);
489            if (r.finishing) {
490                continue;
491            }
492            if (stack == null) {
493                // Task was restored from persistent storage.
494                r.takeFromHistory();
495                mActivities.remove(activityNdx);
496                --activityNdx;
497                --numActivities;
498            } else if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
499                    false)) {
500                --activityNdx;
501                --numActivities;
502            }
503        }
504    }
505
506    /**
507     * Completely remove all activities associated with an existing task.
508     */
509    final void performClearTaskLocked() {
510        mReuseTask = true;
511        performClearTaskAtIndexLocked(0);
512        mReuseTask = false;
513    }
514
515    /**
516     * Perform clear operation as requested by
517     * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the
518     * stack to the given task, then look for
519     * an instance of that activity in the stack and, if found, finish all
520     * activities on top of it and return the instance.
521     *
522     * @param newR Description of the new activity being started.
523     * @return Returns the old activity that should be continued to be used,
524     * or null if none was found.
525     */
526    final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) {
527        int numActivities = mActivities.size();
528        for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) {
529            ActivityRecord r = mActivities.get(activityNdx);
530            if (r.finishing) {
531                continue;
532            }
533            if (r.realActivity.equals(newR.realActivity)) {
534                // Here it is!  Now finish everything in front...
535                final ActivityRecord ret = r;
536
537                for (++activityNdx; activityNdx < numActivities; ++activityNdx) {
538                    r = mActivities.get(activityNdx);
539                    if (r.finishing) {
540                        continue;
541                    }
542                    ActivityOptions opts = r.takeOptionsLocked();
543                    if (opts != null) {
544                        ret.updateOptionsLocked(opts);
545                    }
546                    if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
547                            false)) {
548                        --activityNdx;
549                        --numActivities;
550                    }
551                }
552
553                // Finally, if this is a normal launch mode (that is, not
554                // expecting onNewIntent()), then we will finish the current
555                // instance of the activity so a new fresh one can be started.
556                if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE
557                        && (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) {
558                    if (!ret.finishing) {
559                        stack.finishActivityLocked(ret, Activity.RESULT_CANCELED, null,
560                                "clear", false);
561                        return null;
562                    }
563                }
564
565                return ret;
566            }
567        }
568
569        return null;
570    }
571
572    public TaskThumbnail getTaskThumbnailLocked() {
573        if (stack != null) {
574            final ActivityRecord resumedActivity = stack.mResumedActivity;
575            if (resumedActivity != null && resumedActivity.task == this) {
576                final Bitmap thumbnail = stack.screenshotActivities(resumedActivity);
577                setLastThumbnail(thumbnail);
578            }
579        }
580        final TaskThumbnail taskThumbnail = new TaskThumbnail();
581        getLastThumbnail(taskThumbnail);
582        return taskThumbnail;
583    }
584
585    public void removeTaskActivitiesLocked() {
586        // Just remove the entire task.
587        performClearTaskAtIndexLocked(0);
588    }
589
590    boolean isHomeTask() {
591        return taskType == HOME_ACTIVITY_TYPE;
592    }
593
594    boolean isApplicationTask() {
595        return taskType == APPLICATION_ACTIVITY_TYPE;
596    }
597
598    boolean isOverHomeStack() {
599        return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE;
600    }
601
602    /**
603     * Find the activity in the history stack within the given task.  Returns
604     * the index within the history at which it's found, or < 0 if not found.
605     */
606    final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) {
607        final ComponentName realActivity = r.realActivity;
608        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
609            ActivityRecord candidate = mActivities.get(activityNdx);
610            if (candidate.finishing) {
611                continue;
612            }
613            if (candidate.realActivity.equals(realActivity)) {
614                return candidate;
615            }
616        }
617        return null;
618    }
619
620    /** Updates the last task description values. */
621    void updateTaskDescription() {
622        // Traverse upwards looking for any break between main task activities and
623        // utility activities.
624        int activityNdx;
625        final int numActivities = mActivities.size();
626        final boolean relinquish = numActivities == 0 ? false :
627                (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0;
628        for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
629                ++activityNdx) {
630            final ActivityRecord r = mActivities.get(activityNdx);
631            if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
632                // This will be the top activity for determining taskDescription. Pre-inc to
633                // overcome initial decrement below.
634                ++activityNdx;
635                break;
636            }
637            if (r.intent != null &&
638                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
639                break;
640            }
641        }
642        if (activityNdx > 0) {
643            // Traverse downwards starting below break looking for set label, icon.
644            // Note that if there are activities in the task but none of them set the
645            // recent activity values, then we do not fall back to the last set
646            // values in the TaskRecord.
647            String label = null;
648            Bitmap icon = null;
649            int colorPrimary = 0;
650            for (--activityNdx; activityNdx >= 0; --activityNdx) {
651                final ActivityRecord r = mActivities.get(activityNdx);
652                if (r.taskDescription != null) {
653                    if (label == null) {
654                        label = r.taskDescription.getLabel();
655                    }
656                    if (icon == null) {
657                        icon = r.taskDescription.getIcon();
658                    }
659                    if (colorPrimary == 0) {
660                        colorPrimary = r.taskDescription.getPrimaryColor();
661
662                    }
663                }
664            }
665            lastTaskDescription = new ActivityManager.TaskDescription(label, icon, colorPrimary);
666        }
667    }
668
669    int findEffectiveRootIndex() {
670        int activityNdx;
671        final int topActivityNdx = mActivities.size() - 1;
672        for (activityNdx = 0; activityNdx < topActivityNdx; ++activityNdx) {
673            final ActivityRecord r = mActivities.get(activityNdx);
674            if (r.finishing) {
675                continue;
676            }
677            if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
678                break;
679            }
680        }
681        return activityNdx;
682    }
683
684    void updateEffectiveIntent() {
685        final int effectiveRootIndex = findEffectiveRootIndex();
686        final ActivityRecord r = mActivities.get(effectiveRootIndex);
687        setIntent(r.intent, r.info);
688    }
689
690    void saveTaskDescription(ActivityManager.TaskDescription taskDescription,
691            String iconFilename, XmlSerializer out) throws IOException {
692        if (taskDescription != null) {
693            final String label = taskDescription.getLabel();
694            if (label != null) {
695                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
696            }
697            final int colorPrimary = taskDescription.getPrimaryColor();
698            if (colorPrimary != 0) {
699                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
700            }
701            final Bitmap icon = taskDescription.getIcon();
702            if (icon != null) {
703                mService.mTaskPersister.saveImage(icon, iconFilename);
704            }
705        }
706    }
707
708    static boolean readTaskDescriptionAttribute(ActivityManager.TaskDescription taskDescription,
709            String attrName, String attrValue) {
710        if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
711            taskDescription.setLabel(attrValue);
712        } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
713            taskDescription.setPrimaryColor((int) Long.parseLong(attrValue, 16));
714        } else {
715            return false;
716        }
717        return true;
718    }
719
720    void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
721        Slog.i(TAG, "Saving task=" + this);
722
723        out.attribute(null, ATTR_TASKID, String.valueOf(taskId));
724        if (realActivity != null) {
725            out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString());
726        }
727        if (origActivity != null) {
728            out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString());
729        }
730        if (affinity != null) {
731            out.attribute(null, ATTR_AFFINITY, affinity);
732        }
733        out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset));
734        out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
735        out.attribute(null, ATTR_USERID, String.valueOf(userId));
736        out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
737        out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime));
738        out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
739        out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
740        out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
741        if (lastDescription != null) {
742            out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
743        }
744        if (lastTaskDescription != null) {
745            saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
746                    LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
747        }
748        out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
749        out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
750        out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
751
752        if (affinityIntent != null) {
753            out.startTag(null, TAG_AFFINITYINTENT);
754            affinityIntent.saveToXml(out);
755            out.endTag(null, TAG_AFFINITYINTENT);
756        }
757
758        out.startTag(null, TAG_INTENT);
759        intent.saveToXml(out);
760        out.endTag(null, TAG_INTENT);
761
762        final ArrayList<ActivityRecord> activities = mActivities;
763        final int numActivities = activities.size();
764        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
765            final ActivityRecord r = activities.get(activityNdx);
766            if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
767                    ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) &&
768                            activityNdx > 0) {
769                // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET).
770                break;
771            }
772            out.startTag(null, TAG_ACTIVITY);
773            r.saveToXml(out);
774            out.endTag(null, TAG_ACTIVITY);
775        }
776    }
777
778    static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
779            throws IOException, XmlPullParserException {
780        Intent intent = null;
781        Intent affinityIntent = null;
782        ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
783        ComponentName realActivity = null;
784        ComponentName origActivity = null;
785        String affinity = null;
786        boolean rootHasReset = false;
787        boolean askedCompatMode = false;
788        int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
789        int userId = 0;
790        String lastDescription = null;
791        long firstActiveTime = -1;
792        long lastActiveTime = -1;
793        long lastTimeOnTop = 0;
794        boolean neverRelinquishIdentity = true;
795        int taskId = -1;
796        final int outerDepth = in.getDepth();
797        ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
798        int taskAffiliation = -1;
799        int prevTaskId = -1;
800        int nextTaskId = -1;
801
802        for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
803            final String attrName = in.getAttributeName(attrNdx);
804            final String attrValue = in.getAttributeValue(attrNdx);
805            if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" +
806                    attrName + " value=" + attrValue);
807            if (ATTR_TASKID.equals(attrName)) {
808                taskId = Integer.valueOf(attrValue);
809            } else if (ATTR_REALACTIVITY.equals(attrName)) {
810                realActivity = ComponentName.unflattenFromString(attrValue);
811            } else if (ATTR_ORIGACTIVITY.equals(attrName)) {
812                origActivity = ComponentName.unflattenFromString(attrValue);
813            } else if (ATTR_AFFINITY.equals(attrName)) {
814                affinity = attrValue;
815            } else if (ATTR_ROOTHASRESET.equals(attrName)) {
816                rootHasReset = Boolean.valueOf(attrValue);
817            } else if (ATTR_ASKEDCOMPATMODE.equals(attrName)) {
818                askedCompatMode = Boolean.valueOf(attrValue);
819            } else if (ATTR_USERID.equals(attrName)) {
820                userId = Integer.valueOf(attrValue);
821            } else if (ATTR_TASKTYPE.equals(attrName)) {
822                taskType = Integer.valueOf(attrValue);
823            } else if (ATTR_FIRSTACTIVETIME.equals(attrName)) {
824                firstActiveTime = Long.valueOf(attrValue);
825            } else if (ATTR_LASTACTIVETIME.equals(attrName)) {
826                lastActiveTime = Long.valueOf(attrValue);
827            } else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
828                lastDescription = attrValue;
829            } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
830                lastTimeOnTop = Long.valueOf(attrValue);
831            } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
832                neverRelinquishIdentity = Boolean.valueOf(attrValue);
833            } else if (readTaskDescriptionAttribute(taskDescription, attrName, attrValue)) {
834                // Completed in TaskPersister.readTaskDescriptionAttribute()
835            } else if (ATTR_TASK_AFFILIATION.equals(attrName)) {
836                taskAffiliation = Integer.valueOf(attrValue);
837            } else if (ATTR_PREV_AFFILIATION.equals(attrName)) {
838                prevTaskId = Integer.valueOf(attrValue);
839            } else if (ATTR_NEXT_AFFILIATION.equals(attrName)) {
840                nextTaskId = Integer.valueOf(attrValue);
841            } else {
842                Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
843            }
844        }
845
846        int event;
847        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
848                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
849            if (event == XmlPullParser.START_TAG) {
850                final String name = in.getName();
851                if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" +
852                        name);
853                if (TAG_AFFINITYINTENT.equals(name)) {
854                    affinityIntent = Intent.restoreFromXml(in);
855                } else if (TAG_INTENT.equals(name)) {
856                    intent = Intent.restoreFromXml(in);
857                } else if (TAG_ACTIVITY.equals(name)) {
858                    ActivityRecord activity =
859                            ActivityRecord.restoreFromXml(in, taskId, stackSupervisor);
860                    if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" +
861                            activity);
862                    if (activity != null) {
863                        activities.add(activity);
864                    }
865                } else {
866                    Slog.e(TAG, "restoreTask: Unexpected name=" + name);
867                    XmlUtils.skipCurrentTag(in);
868                }
869            }
870        }
871
872        if (lastActiveTime >= 0) {
873            taskDescription.setIcon(TaskPersister.restoreImage(String.valueOf(taskId) +
874                    LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime + TaskPersister.IMAGE_EXTENSION));
875        }
876
877        final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
878                affinityIntent, affinity, realActivity, origActivity, rootHasReset,
879                askedCompatMode, taskType, userId, lastDescription, activities, firstActiveTime,
880                lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription,
881                taskAffiliation, prevTaskId, nextTaskId);
882
883        for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
884            activities.get(activityNdx).task = task;
885        }
886
887        Slog.i(TAG, "Restored task=" + task);
888        return task;
889    }
890
891    void dump(PrintWriter pw, String prefix) {
892        if (rootWasReset || userId != 0 || numFullscreen != 0) {
893            pw.print(prefix); pw.print(" rootWasReset="); pw.print(rootWasReset);
894                    pw.print(" userId="); pw.print(userId);
895                    pw.print(" taskType="); pw.print(taskType);
896                    pw.print(" numFullscreen="); pw.print(numFullscreen);
897                    pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
898        }
899        if (affinity != null) {
900            pw.print(prefix); pw.print("affinity="); pw.println(affinity);
901        }
902        if (voiceSession != null || voiceInteractor != null) {
903            pw.print(prefix); pw.print("VOICE: session=0x");
904            pw.print(Integer.toHexString(System.identityHashCode(voiceSession)));
905            pw.print(" interactor=0x");
906            pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor)));
907        }
908        if (intent != null) {
909            StringBuilder sb = new StringBuilder(128);
910            sb.append(prefix); sb.append("intent={");
911            intent.toShortString(sb, false, true, false, true);
912            sb.append('}');
913            pw.println(sb.toString());
914        }
915        if (affinityIntent != null) {
916            StringBuilder sb = new StringBuilder(128);
917            sb.append(prefix); sb.append("affinityIntent={");
918            affinityIntent.toShortString(sb, false, true, false, true);
919            sb.append('}');
920            pw.println(sb.toString());
921        }
922        if (origActivity != null) {
923            pw.print(prefix); pw.print("origActivity=");
924            pw.println(origActivity.flattenToShortString());
925        }
926        if (realActivity != null) {
927            pw.print(prefix); pw.print("realActivity=");
928            pw.println(realActivity.flattenToShortString());
929        }
930        pw.print(prefix); pw.print("Activities="); pw.println(mActivities);
931        if (!askedCompatMode) {
932            pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode);
933        }
934        pw.print(prefix); pw.print("lastThumbnail="); pw.print(mLastThumbnail);
935                pw.print(" lastThumbnailFile="); pw.print(mLastThumbnailFile);
936                pw.print(" lastDescription="); pw.println(lastDescription);
937        pw.print(prefix); pw.print("hasBeenVisible="); pw.print(hasBeenVisible);
938                pw.print(" firstActiveTime="); pw.print(lastActiveTime);
939                pw.print(" lastActiveTime="); pw.print(lastActiveTime);
940                pw.print(" (inactive for ");
941                pw.print((getInactiveDuration()/1000)); pw.println("s)");
942        pw.print(prefix); pw.print("isPersistable="); pw.print(isPersistable);
943                pw.print(" affiliation="); pw.print(mAffiliatedTaskId);
944                pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
945                pw.print(" nextAffiliation="); pw.println(mNextAffiliateTaskId);
946    }
947
948    @Override
949    public String toString() {
950        StringBuilder sb = new StringBuilder(128);
951        if (stringName != null) {
952            sb.append(stringName);
953            sb.append(" U=");
954            sb.append(userId);
955            sb.append(" sz=");
956            sb.append(mActivities.size());
957            sb.append('}');
958            return sb.toString();
959        }
960        sb.append("TaskRecord{");
961        sb.append(Integer.toHexString(System.identityHashCode(this)));
962        sb.append(" #");
963        sb.append(taskId);
964        if (affinity != null) {
965            sb.append(" A=");
966            sb.append(affinity);
967        } else if (intent != null) {
968            sb.append(" I=");
969            sb.append(intent.getComponent().flattenToShortString());
970        } else if (affinityIntent != null) {
971            sb.append(" aI=");
972            sb.append(affinityIntent.getComponent().flattenToShortString());
973        } else {
974            sb.append(" ??");
975        }
976        stringName = sb.toString();
977        return toString();
978    }
979}
980