TaskRecord.java revision df6523f2703920a1d2a84c45b862b325b8cebf40
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.ActivityOptions;
28import android.app.IThumbnailRetriever;
29import android.content.ComponentName;
30import android.content.Intent;
31import android.content.pm.ActivityInfo;
32import android.graphics.Bitmap;
33import android.os.SystemClock;
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.IOException;
44import java.io.PrintWriter;
45import java.util.ArrayList;
46
47final class TaskRecord extends ThumbnailHolder {
48    private static final String TAG_TASK = "task";
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_LASTDESCRIPTION = "last_description";
61    private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
62
63    private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
64
65    final int taskId;       // Unique identifier for this task.
66    final String affinity;  // The affinity name for this task, or null.
67    final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
68    final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
69    Intent intent;          // The original intent that started the task.
70    Intent affinityIntent;  // Intent of affinity-moved activity that started this task.
71    ComponentName origActivity; // The non-alias activity component of the intent.
72    ComponentName realActivity; // The actual activity component that started the task.
73    int numActivities;      // Current number of activities in this task.
74    long lastActiveTime;    // Last time this task was active, including sleep.
75    boolean rootWasReset;   // True if the intent at the root of the task had
76                            // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
77    boolean askedCompatMode;// Have asked the user about compat mode for this task.
78
79    String stringName;      // caching of toString() result.
80    int userId;             // user for which this task was created
81    int creatorUid;         // The app uid that originally created the task
82
83    int numFullscreen;      // Number of fullscreen activities.
84
85    // This represents the last resolved activity values for this task
86    // NOTE: This value needs to be persisted with each task
87    ActivityManager.TaskDescription lastTaskDescription =
88            new ActivityManager.TaskDescription();
89
90    /** List of all activities in the task arranged in history order */
91    final ArrayList<ActivityRecord> mActivities;
92
93    /** Current stack */
94    ActivityStack stack;
95
96    /** Takes on same set of values as ActivityRecord.mActivityType */
97    int taskType;
98
99    /** Takes on same value as first root activity */
100    boolean isPersistable = false;
101
102    /** Only used for persistable tasks, otherwise 0. The last time this task was moved. Used for
103     * determining the order when restoring. Sign indicates whether last task movement was to front
104     * (positive) or back (negative). Absolute value indicates time. */
105    long mLastTimeMoved = System.currentTimeMillis();
106
107    /** True if persistable, has changed, and has not yet been persisted */
108    boolean needsPersisting = false;
109
110    /** Indication of what to run next when task exits. Use ActivityRecord types.
111     * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the
112     * task stack. */
113    private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE;
114
115    final ActivityManagerService mService;
116
117    TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
118            IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
119        mService = service;
120        taskId = _taskId;
121        affinity = info.taskAffinity;
122        voiceSession = _voiceSession;
123        voiceInteractor = _voiceInteractor;
124        setIntent(_intent, info);
125        mActivities = new ArrayList<ActivityRecord>();
126    }
127
128    TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
129            String _affinity, ComponentName _realActivity, ComponentName _origActivity,
130            boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
131            String _lastDescription, ArrayList<ActivityRecord> activities, long lastTimeMoved) {
132        mService = service;
133        taskId = _taskId;
134        intent = _intent;
135        affinityIntent = _affinityIntent;
136        affinity = _affinity;
137        voiceSession = null;
138        voiceInteractor = null;
139        realActivity = _realActivity;
140        origActivity = _origActivity;
141        rootWasReset = _rootWasReset;
142        askedCompatMode = _askedCompatMode;
143        taskType = _taskType;
144        mTaskToReturnTo = HOME_ACTIVITY_TYPE;
145        userId = _userId;
146        lastDescription = _lastDescription;
147        mActivities = activities;
148        mLastTimeMoved = lastTimeMoved;
149    }
150
151    void touchActiveTime() {
152        lastActiveTime = android.os.SystemClock.elapsedRealtime();
153    }
154
155    long getInactiveDuration() {
156        return android.os.SystemClock.elapsedRealtime() - lastActiveTime;
157    }
158
159    void setIntent(Intent _intent, ActivityInfo info) {
160        stringName = null;
161
162        if (info.targetActivity == null) {
163            if (_intent != null) {
164                // If this Intent has a selector, we want to clear it for the
165                // recent task since it is not relevant if the user later wants
166                // to re-launch the app.
167                if (_intent.getSelector() != null || _intent.getSourceBounds() != null) {
168                    _intent = new Intent(_intent);
169                    _intent.setSelector(null);
170                    _intent.setSourceBounds(null);
171                }
172            }
173            if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
174                    "Setting Intent of " + this + " to " + _intent);
175            intent = _intent;
176            realActivity = _intent != null ? _intent.getComponent() : null;
177            origActivity = null;
178        } else {
179            ComponentName targetComponent = new ComponentName(
180                    info.packageName, info.targetActivity);
181            if (_intent != null) {
182                Intent targetIntent = new Intent(_intent);
183                targetIntent.setComponent(targetComponent);
184                targetIntent.setSelector(null);
185                targetIntent.setSourceBounds(null);
186                if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
187                        "Setting Intent of " + this + " to target " + targetIntent);
188                intent = targetIntent;
189                realActivity = targetComponent;
190                origActivity = _intent.getComponent();
191            } else {
192                intent = null;
193                realActivity = targetComponent;
194                origActivity = new ComponentName(info.packageName, info.name);
195            }
196        }
197
198        if (intent != null &&
199                (intent.getFlags()&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
200            // Once we are set to an Intent with this flag, we count this
201            // task as having a true root activity.
202            rootWasReset = true;
203        }
204
205        userId = UserHandle.getUserId(info.applicationInfo.uid);
206        creatorUid = info.applicationInfo.uid;
207        if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
208            intent.addFlags(Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS);
209        }
210    }
211
212    void setTaskToReturnTo(int taskToReturnTo) {
213        mTaskToReturnTo = taskToReturnTo;
214    }
215
216    int getTaskToReturnTo() {
217        return mTaskToReturnTo;
218    }
219
220    void disposeThumbnail() {
221        super.disposeThumbnail();
222        for (int i=mActivities.size()-1; i>=0; i--) {
223            ThumbnailHolder thumb = mActivities.get(i).thumbHolder;
224            if (thumb != this) {
225                thumb.disposeThumbnail();
226            }
227        }
228    }
229
230    /** Returns the intent for the root activity for this task */
231    Intent getBaseIntent() {
232        return intent != null ? intent : affinityIntent;
233    }
234
235    /** Returns the first non-finishing activity from the root. */
236    ActivityRecord getRootActivity() {
237        for (int i = 0; i < mActivities.size(); i++) {
238            final ActivityRecord r = mActivities.get(i);
239            if (r.finishing) {
240                continue;
241            }
242            return r;
243        }
244        return null;
245    }
246
247    ActivityRecord getTopActivity() {
248        for (int i = mActivities.size() - 1; i >= 0; --i) {
249            final ActivityRecord r = mActivities.get(i);
250            if (r.finishing) {
251                continue;
252            }
253            return r;
254        }
255        return null;
256    }
257
258    ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
259        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
260            ActivityRecord r = mActivities.get(activityNdx);
261            if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
262                return r;
263            }
264        }
265        return null;
266    }
267
268    /** Call after activity movement or finish to make sure that frontOfTask is set correctly */
269    final void setFrontOfTask() {
270        boolean foundFront = false;
271        final int numActivities = mActivities.size();
272        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
273            final ActivityRecord r = mActivities.get(activityNdx);
274            if (foundFront || r.finishing) {
275                r.frontOfTask = false;
276            } else {
277                r.frontOfTask = true;
278                // Set frontOfTask false for every following activity.
279                foundFront = true;
280            }
281        }
282    }
283
284    /**
285     * Reorder the history stack so that the passed activity is brought to the front.
286     */
287    final void moveActivityToFrontLocked(ActivityRecord newTop) {
288        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + newTop
289            + " to stack at top", new RuntimeException("here").fillInStackTrace());
290
291        mActivities.remove(newTop);
292        mActivities.add(newTop);
293
294        setFrontOfTask();
295    }
296
297    void addActivityAtBottom(ActivityRecord r) {
298        addActivityAtIndex(0, r);
299    }
300
301    void addActivityToTop(ActivityRecord r) {
302        addActivityAtIndex(mActivities.size(), r);
303    }
304
305    void addActivityAtIndex(int index, ActivityRecord r) {
306        // Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
307        if (!mActivities.remove(r) && r.fullscreen) {
308            // Was not previously in list.
309            numFullscreen++;
310        }
311        // Only set this based on the first activity
312        if (mActivities.isEmpty()) {
313            taskType = r.mActivityType;
314            isPersistable = r.isPersistable();
315        } else {
316            // Otherwise make all added activities match this one.
317            r.mActivityType = taskType;
318        }
319        mActivities.add(index, r);
320        if (r.isPersistable()) {
321            mService.notifyTaskPersisterLocked(this, false);
322        }
323    }
324
325    /** @return true if this was the last activity in the task */
326    boolean removeActivity(ActivityRecord r) {
327        if (mActivities.remove(r) && r.fullscreen) {
328            // Was previously in list.
329            numFullscreen--;
330        }
331        if (r.isPersistable()) {
332            mService.notifyTaskPersisterLocked(this, false);
333        }
334        return mActivities.size() == 0;
335    }
336
337    boolean autoRemoveFromRecents() {
338        return intent != null &&
339                (intent.getFlags() & Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS) != 0;
340    }
341
342    /**
343     * Completely remove all activities associated with an existing
344     * task starting at a specified index.
345     */
346    final void performClearTaskAtIndexLocked(int activityNdx) {
347        int numActivities = mActivities.size();
348        for ( ; activityNdx < numActivities; ++activityNdx) {
349            final ActivityRecord r = mActivities.get(activityNdx);
350            if (r.finishing) {
351                continue;
352            }
353            if (stack == null) {
354                // Task was restored from persistent storage.
355                r.takeFromHistory();
356                mActivities.remove(activityNdx);
357                --activityNdx;
358                --numActivities;
359            } else if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
360                    false)) {
361                --activityNdx;
362                --numActivities;
363            }
364        }
365    }
366
367    /**
368     * Completely remove all activities associated with an existing task.
369     */
370    final void performClearTaskLocked() {
371        performClearTaskAtIndexLocked(0);
372    }
373
374    /**
375     * Perform clear operation as requested by
376     * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the
377     * stack to the given task, then look for
378     * an instance of that activity in the stack and, if found, finish all
379     * activities on top of it and return the instance.
380     *
381     * @param newR Description of the new activity being started.
382     * @return Returns the old activity that should be continued to be used,
383     * or null if none was found.
384     */
385    final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) {
386        int numActivities = mActivities.size();
387        for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) {
388            ActivityRecord r = mActivities.get(activityNdx);
389            if (r.finishing) {
390                continue;
391            }
392            if (r.realActivity.equals(newR.realActivity)) {
393                // Here it is!  Now finish everything in front...
394                final ActivityRecord ret = r;
395
396                for (++activityNdx; activityNdx < numActivities; ++activityNdx) {
397                    r = mActivities.get(activityNdx);
398                    if (r.finishing) {
399                        continue;
400                    }
401                    ActivityOptions opts = r.takeOptionsLocked();
402                    if (opts != null) {
403                        ret.updateOptionsLocked(opts);
404                    }
405                    if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
406                            false)) {
407                        --activityNdx;
408                        --numActivities;
409                    }
410                }
411
412                // Finally, if this is a normal launch mode (that is, not
413                // expecting onNewIntent()), then we will finish the current
414                // instance of the activity so a new fresh one can be started.
415                if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE
416                        && (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) {
417                    if (!ret.finishing) {
418                        stack.finishActivityLocked(ret, Activity.RESULT_CANCELED, null,
419                                "clear", false);
420                        return null;
421                    }
422                }
423
424                return ret;
425            }
426        }
427
428        return null;
429    }
430
431    public ActivityManager.TaskThumbnails getTaskThumbnailsLocked() {
432        TaskAccessInfo info = getTaskAccessInfoLocked();
433        final ActivityRecord resumedActivity = stack.mResumedActivity;
434        if (resumedActivity != null && resumedActivity.thumbHolder == this) {
435            info.mainThumbnail = stack.screenshotActivities(resumedActivity);
436        }
437        if (info.mainThumbnail == null) {
438            info.mainThumbnail = lastThumbnail;
439        }
440        return info;
441    }
442
443    public Bitmap getTaskTopThumbnailLocked() {
444        if (stack != null) {
445            final ActivityRecord resumedActivity = stack.mResumedActivity;
446            if (resumedActivity != null && resumedActivity.task == this) {
447                // This task is the current resumed task, we just need to take
448                // a screenshot of it and return that.
449                return stack.screenshotActivities(resumedActivity);
450            }
451        }
452        // Return the information about the task, to figure out the top
453        // thumbnail to return.
454        TaskAccessInfo info = getTaskAccessInfoLocked();
455        if (info.numSubThumbbails <= 0) {
456            return info.mainThumbnail != null ? info.mainThumbnail : lastThumbnail;
457        }
458        return info.subtasks.get(info.numSubThumbbails-1).holder.lastThumbnail;
459    }
460
461    public ActivityRecord removeTaskActivitiesLocked(int subTaskIndex,
462            boolean taskRequired) {
463        TaskAccessInfo info = getTaskAccessInfoLocked();
464        if (info.root == null) {
465            if (taskRequired) {
466                Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId);
467            }
468            return null;
469        }
470
471        if (subTaskIndex < 0) {
472            // Just remove the entire task.
473            performClearTaskAtIndexLocked(info.rootIndex);
474            return info.root;
475        }
476
477        if (subTaskIndex >= info.subtasks.size()) {
478            if (taskRequired) {
479                Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex);
480            }
481            return null;
482        }
483
484        // Remove all of this task's activities starting at the sub task.
485        TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex);
486        performClearTaskAtIndexLocked(subtask.index);
487        return subtask.activity;
488    }
489
490    boolean isHomeTask() {
491        return taskType == HOME_ACTIVITY_TYPE;
492    }
493
494    boolean isApplicationTask() {
495        return taskType == APPLICATION_ACTIVITY_TYPE;
496    }
497
498    boolean isOverHomeStack() {
499        return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE;
500    }
501
502    public TaskAccessInfo getTaskAccessInfoLocked() {
503        final TaskAccessInfo thumbs = new TaskAccessInfo();
504        // How many different sub-thumbnails?
505        final int NA = mActivities.size();
506        int j = 0;
507        ThumbnailHolder holder = null;
508        while (j < NA) {
509            ActivityRecord ar = mActivities.get(j);
510            if (!ar.finishing) {
511                thumbs.root = ar;
512                thumbs.rootIndex = j;
513                holder = ar.thumbHolder;
514                if (holder != null) {
515                    thumbs.mainThumbnail = holder.lastThumbnail;
516                }
517                j++;
518                break;
519            }
520            j++;
521        }
522
523        if (j >= NA) {
524            return thumbs;
525        }
526
527        ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>();
528        thumbs.subtasks = subtasks;
529        while (j < NA) {
530            ActivityRecord ar = mActivities.get(j);
531            j++;
532            if (ar.finishing) {
533                continue;
534            }
535            if (ar.thumbHolder != holder && holder != null) {
536                thumbs.numSubThumbbails++;
537                holder = ar.thumbHolder;
538                TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask();
539                sub.holder = holder;
540                sub.activity = ar;
541                sub.index = j-1;
542                subtasks.add(sub);
543            }
544        }
545        if (thumbs.numSubThumbbails > 0) {
546            thumbs.retriever = new IThumbnailRetriever.Stub() {
547                @Override
548                public Bitmap getThumbnail(int index) {
549                    if (index < 0 || index >= thumbs.subtasks.size()) {
550                        return null;
551                    }
552                    TaskAccessInfo.SubTask sub = thumbs.subtasks.get(index);
553                    ActivityRecord resumedActivity = stack.mResumedActivity;
554                    if (resumedActivity != null && resumedActivity.thumbHolder == sub.holder) {
555                        return stack.screenshotActivities(resumedActivity);
556                    }
557                    return sub.holder.lastThumbnail;
558                }
559            };
560        }
561        return thumbs;
562    }
563
564    /**
565     * Find the activity in the history stack within the given task.  Returns
566     * the index within the history at which it's found, or < 0 if not found.
567     */
568    final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) {
569        final ComponentName realActivity = r.realActivity;
570        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
571            ActivityRecord candidate = mActivities.get(activityNdx);
572            if (candidate.finishing) {
573                continue;
574            }
575            if (candidate.realActivity.equals(realActivity)) {
576                return candidate;
577            }
578        }
579        return null;
580    }
581
582    /** Updates the last task description values. */
583    void updateTaskDescription() {
584        // Traverse upwards looking for any break between main task activities and
585        // utility activities.
586        int activityNdx;
587        final int numActivities = mActivities.size();
588        for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
589                ++activityNdx) {
590            final ActivityRecord r = mActivities.get(activityNdx);
591            if (r.intent != null &&
592                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
593                            != 0) {
594                break;
595            }
596        }
597        if (activityNdx > 0) {
598            // Traverse downwards starting below break looking for set label, icon.
599            // Note that if there are activities in the task but none of them set the
600            // recent activity values, then we do not fall back to the last set
601            // values in the TaskRecord.
602            String label = null;
603            Bitmap icon = null;
604            int colorPrimary = 0;
605            for (--activityNdx; activityNdx >= 0; --activityNdx) {
606                final ActivityRecord r = mActivities.get(activityNdx);
607                if (r.taskDescription != null) {
608                    if (label == null) {
609                        label = r.taskDescription.getLabel();
610                    }
611                    if (icon == null) {
612                        icon = r.taskDescription.getIcon();
613                    }
614                    if (colorPrimary == 0) {
615                        colorPrimary = r.taskDescription.getPrimaryColor();
616
617                    }
618                }
619            }
620            lastTaskDescription = new ActivityManager.TaskDescription(label, icon, colorPrimary);
621        }
622    }
623
624    void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
625        Slog.i(TAG, "Saving task=" + this);
626
627        out.attribute(null, ATTR_TASKID, String.valueOf(taskId));
628        if (realActivity != null) {
629            out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString());
630        }
631        if (origActivity != null) {
632            out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString());
633        }
634        if (affinity != null) {
635            out.attribute(null, ATTR_AFFINITY, affinity);
636        }
637        out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset));
638        out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
639        out.attribute(null, ATTR_USERID, String.valueOf(userId));
640        out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
641        out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
642        if (lastDescription != null) {
643            out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
644        }
645
646        if (affinityIntent != null) {
647            out.startTag(null, TAG_AFFINITYINTENT);
648            affinityIntent.saveToXml(out);
649            out.endTag(null, TAG_AFFINITYINTENT);
650        }
651
652        out.startTag(null, TAG_INTENT);
653        intent.saveToXml(out);
654        out.endTag(null, TAG_INTENT);
655
656        final ArrayList<ActivityRecord> activities = mActivities;
657        final int numActivities = activities.size();
658        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
659            final ActivityRecord r = activities.get(activityNdx);
660            if (!r.isPersistable() || (r.intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) ==
661                    Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) {
662                break;
663            }
664            out.startTag(null, TAG_ACTIVITY);
665            r.saveToXml(out);
666            out.endTag(null, TAG_ACTIVITY);
667        }
668
669        final Bitmap thumbnail = getTaskTopThumbnailLocked();
670        if (thumbnail != null) {
671            TaskPersister.saveImage(thumbnail, String.valueOf(taskId) + TASK_THUMBNAIL_SUFFIX);
672        }
673    }
674
675    static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
676            throws IOException, XmlPullParserException {
677        Intent intent = null;
678        Intent affinityIntent = null;
679        ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
680        ComponentName realActivity = null;
681        ComponentName origActivity = null;
682        String affinity = null;
683        boolean rootHasReset = false;
684        boolean askedCompatMode = false;
685        int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
686        int userId = 0;
687        String lastDescription = null;
688        long lastTimeOnTop = 0;
689        int taskId = -1;
690        final int outerDepth = in.getDepth();
691
692        for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
693            final String attrName = in.getAttributeName(attrNdx);
694            final String attrValue = in.getAttributeValue(attrNdx);
695            if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" +
696                    attrName + " value=" + attrValue);
697            if (ATTR_TASKID.equals(attrName)) {
698                taskId = Integer.valueOf(attrValue);
699            } else if (ATTR_REALACTIVITY.equals(attrName)) {
700                realActivity = ComponentName.unflattenFromString(attrValue);
701            } else if (ATTR_ORIGACTIVITY.equals(attrName)) {
702                origActivity = ComponentName.unflattenFromString(attrValue);
703            } else if (ATTR_AFFINITY.equals(attrName)) {
704                affinity = attrValue;
705            } else if (ATTR_ROOTHASRESET.equals(attrName)) {
706                rootHasReset = Boolean.valueOf(attrValue);
707            } else if (ATTR_ASKEDCOMPATMODE.equals(attrName)) {
708                askedCompatMode = Boolean.valueOf(attrValue);
709            } else if (ATTR_USERID.equals(attrName)) {
710                userId = Integer.valueOf(attrValue);
711            } else if (ATTR_TASKTYPE.equals(attrName)) {
712                taskType = Integer.valueOf(attrValue);
713            } else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
714                lastDescription = attrValue;
715            } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
716                lastTimeOnTop = Long.valueOf(attrValue);
717            } else {
718                Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
719            }
720        }
721
722        int event;
723        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
724                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
725            if (event == XmlPullParser.START_TAG) {
726                final String name = in.getName();
727                if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" +
728                        name);
729                if (TAG_AFFINITYINTENT.equals(name)) {
730                    affinityIntent = Intent.restoreFromXml(in);
731                } else if (TAG_INTENT.equals(name)) {
732                    intent = Intent.restoreFromXml(in);
733                } else if (TAG_ACTIVITY.equals(name)) {
734                    ActivityRecord activity =
735                            ActivityRecord.restoreFromXml(in, taskId, stackSupervisor);
736                    if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" +
737                            activity);
738                    if (activity != null) {
739                        activities.add(activity);
740                    }
741                } else {
742                    Slog.e(TAG, "restoreTask: Unexpected name=" + name);
743                    XmlUtils.skipCurrentTag(in);
744                }
745            }
746        }
747
748        final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
749                affinityIntent, affinity, realActivity, origActivity, rootHasReset,
750                askedCompatMode, taskType, userId, lastDescription, activities, lastTimeOnTop);
751
752        for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
753            final ActivityRecord r = activities.get(activityNdx);
754            r.thumbHolder = r.task = task;
755        }
756
757        task.lastThumbnail = TaskPersister.restoreImage(taskId + TASK_THUMBNAIL_SUFFIX);
758
759        Slog.i(TAG, "Restored task=" + task);
760        return task;
761    }
762
763    void dump(PrintWriter pw, String prefix) {
764        if (rootWasReset || userId != 0 || numFullscreen != 0) {
765            pw.print(prefix); pw.print(" rootWasReset="); pw.print(rootWasReset);
766                    pw.print(" userId="); pw.print(userId);
767                    pw.print(" taskType="); pw.print(taskType);
768                    pw.print(" numFullscreen="); pw.print(numFullscreen);
769                    pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
770        }
771        if (affinity != null) {
772            pw.print(prefix); pw.print("affinity="); pw.println(affinity);
773        }
774        if (voiceSession != null || voiceInteractor != null) {
775            pw.print(prefix); pw.print("VOICE: session=0x");
776            pw.print(Integer.toHexString(System.identityHashCode(voiceSession)));
777            pw.print(" interactor=0x");
778            pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor)));
779        }
780        if (intent != null) {
781            StringBuilder sb = new StringBuilder(128);
782            sb.append(prefix); sb.append("intent={");
783            intent.toShortString(sb, false, true, false, true);
784            sb.append('}');
785            pw.println(sb.toString());
786        }
787        if (affinityIntent != null) {
788            StringBuilder sb = new StringBuilder(128);
789            sb.append(prefix); sb.append("affinityIntent={");
790            affinityIntent.toShortString(sb, false, true, false, true);
791            sb.append('}');
792            pw.println(sb.toString());
793        }
794        if (origActivity != null) {
795            pw.print(prefix); pw.print("origActivity=");
796            pw.println(origActivity.flattenToShortString());
797        }
798        if (realActivity != null) {
799            pw.print(prefix); pw.print("realActivity=");
800            pw.println(realActivity.flattenToShortString());
801        }
802        pw.print(prefix); pw.print("Activities="); pw.println(mActivities);
803        if (!askedCompatMode) {
804            pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode);
805        }
806        pw.print(prefix); pw.print("lastThumbnail="); pw.print(lastThumbnail);
807                pw.print(" lastDescription="); pw.println(lastDescription);
808        pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
809                pw.print(" (inactive for ");
810                pw.print((getInactiveDuration()/1000)); pw.println("s)");
811    }
812
813    @Override
814    public String toString() {
815        StringBuilder sb = new StringBuilder(128);
816        if (stringName != null) {
817            sb.append(stringName);
818            sb.append(" U=");
819            sb.append(userId);
820            sb.append(" sz=");
821            sb.append(mActivities.size());
822            sb.append('}');
823            return sb.toString();
824        }
825        sb.append("TaskRecord{");
826        sb.append(Integer.toHexString(System.identityHashCode(this)));
827        sb.append(" #");
828        sb.append(taskId);
829        if (affinity != null) {
830            sb.append(" A=");
831            sb.append(affinity);
832        } else if (intent != null) {
833            sb.append(" I=");
834            sb.append(intent.getComponent().flattenToShortString());
835        } else if (affinityIntent != null) {
836            sb.append(" aI=");
837            sb.append(affinityIntent.getComponent().flattenToShortString());
838        } else {
839            sb.append(" ??");
840        }
841        stringName = sb.toString();
842        return toString();
843    }
844}
845