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