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