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