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