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