TaskRecord.java revision 648f69b95ce7fc95f551f6e08a2408d6e57dbab9
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        if (intent != null &&
328                (intent.getFlags()&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 ((intent.getFlags()&(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 activityNdx;
785        final int topActivityNdx = mActivities.size() - 1;
786        for (activityNdx = 0; activityNdx < topActivityNdx; ++activityNdx) {
787            final ActivityRecord r = mActivities.get(activityNdx);
788            if (r.finishing) {
789                continue;
790            }
791            if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
792                break;
793            }
794        }
795        return activityNdx;
796    }
797
798    void updateEffectiveIntent() {
799        final int effectiveRootIndex = findEffectiveRootIndex();
800        final ActivityRecord r = mActivities.get(effectiveRootIndex);
801        setIntent(r);
802    }
803
804    void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
805        if (ActivityManagerService.DEBUG_RECENTS) Slog.i(TAG, "Saving task=" + this);
806
807        out.attribute(null, ATTR_TASKID, String.valueOf(taskId));
808        if (realActivity != null) {
809            out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString());
810        }
811        if (origActivity != null) {
812            out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString());
813        }
814        // Write affinity, and root affinity if it is different from affinity.
815        // We use the special string "@" for a null root affinity, so we can identify
816        // later whether we were given a root affinity or should just make it the
817        // same as the affinity.
818        if (affinity != null) {
819            out.attribute(null, ATTR_AFFINITY, affinity);
820            if (!affinity.equals(rootAffinity)) {
821                out.attribute(null, ATTR_ROOT_AFFINITY, rootAffinity != null ? rootAffinity : "@");
822            }
823        } else if (rootAffinity != null) {
824            out.attribute(null, ATTR_ROOT_AFFINITY, rootAffinity != null ? rootAffinity : "@");
825        }
826        out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset));
827        out.attribute(null, ATTR_AUTOREMOVERECENTS, String.valueOf(autoRemoveRecents));
828        out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
829        out.attribute(null, ATTR_USERID, String.valueOf(userId));
830        out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid));
831        out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
832        out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime));
833        out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
834        out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
835        out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
836        if (lastDescription != null) {
837            out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
838        }
839        if (lastTaskDescription != null) {
840            lastTaskDescription.saveToXml(out);
841        }
842        out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
843        out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
844        out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
845        out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
846        out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid));
847        out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage);
848
849        if (affinityIntent != null) {
850            out.startTag(null, TAG_AFFINITYINTENT);
851            affinityIntent.saveToXml(out);
852            out.endTag(null, TAG_AFFINITYINTENT);
853        }
854
855        out.startTag(null, TAG_INTENT);
856        intent.saveToXml(out);
857        out.endTag(null, TAG_INTENT);
858
859        final ArrayList<ActivityRecord> activities = mActivities;
860        final int numActivities = activities.size();
861        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
862            final ActivityRecord r = activities.get(activityNdx);
863            if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
864                    ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) &&
865                            activityNdx > 0) {
866                // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET).
867                break;
868            }
869            out.startTag(null, TAG_ACTIVITY);
870            r.saveToXml(out);
871            out.endTag(null, TAG_ACTIVITY);
872        }
873    }
874
875    static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
876            throws IOException, XmlPullParserException {
877        Intent intent = null;
878        Intent affinityIntent = null;
879        ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
880        ComponentName realActivity = null;
881        ComponentName origActivity = null;
882        String affinity = null;
883        String rootAffinity = null;
884        boolean hasRootAffinity = false;
885        boolean rootHasReset = false;
886        boolean autoRemoveRecents = false;
887        boolean askedCompatMode = false;
888        int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
889        int userId = 0;
890        int effectiveUid = -1;
891        String lastDescription = null;
892        long firstActiveTime = -1;
893        long lastActiveTime = -1;
894        long lastTimeOnTop = 0;
895        boolean neverRelinquishIdentity = true;
896        int taskId = -1;
897        final int outerDepth = in.getDepth();
898        TaskDescription taskDescription = new TaskDescription();
899        int taskAffiliation = -1;
900        int taskAffiliationColor = 0;
901        int prevTaskId = -1;
902        int nextTaskId = -1;
903        int callingUid = -1;
904        String callingPackage = "";
905
906        for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
907            final String attrName = in.getAttributeName(attrNdx);
908            final String attrValue = in.getAttributeValue(attrNdx);
909            if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" +
910                    attrName + " value=" + attrValue);
911            if (ATTR_TASKID.equals(attrName)) {
912                taskId = Integer.valueOf(attrValue);
913            } else if (ATTR_REALACTIVITY.equals(attrName)) {
914                realActivity = ComponentName.unflattenFromString(attrValue);
915            } else if (ATTR_ORIGACTIVITY.equals(attrName)) {
916                origActivity = ComponentName.unflattenFromString(attrValue);
917            } else if (ATTR_AFFINITY.equals(attrName)) {
918                affinity = attrValue;
919            } else if (ATTR_ROOT_AFFINITY.equals(attrName)) {
920                rootAffinity = attrValue;
921                hasRootAffinity = true;
922            } else if (ATTR_ROOTHASRESET.equals(attrName)) {
923                rootHasReset = Boolean.valueOf(attrValue);
924            } else if (ATTR_AUTOREMOVERECENTS.equals(attrName)) {
925                autoRemoveRecents = Boolean.valueOf(attrValue);
926            } else if (ATTR_ASKEDCOMPATMODE.equals(attrName)) {
927                askedCompatMode = Boolean.valueOf(attrValue);
928            } else if (ATTR_USERID.equals(attrName)) {
929                userId = Integer.valueOf(attrValue);
930            } else if (ATTR_EFFECTIVE_UID.equals(attrName)) {
931                effectiveUid = Integer.valueOf(attrValue);
932            } else if (ATTR_TASKTYPE.equals(attrName)) {
933                taskType = Integer.valueOf(attrValue);
934            } else if (ATTR_FIRSTACTIVETIME.equals(attrName)) {
935                firstActiveTime = Long.valueOf(attrValue);
936            } else if (ATTR_LASTACTIVETIME.equals(attrName)) {
937                lastActiveTime = Long.valueOf(attrValue);
938            } else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
939                lastDescription = attrValue;
940            } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
941                lastTimeOnTop = Long.valueOf(attrValue);
942            } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
943                neverRelinquishIdentity = Boolean.valueOf(attrValue);
944            } else if (attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
945                taskDescription.restoreFromXml(attrName, attrValue);
946            } else if (ATTR_TASK_AFFILIATION.equals(attrName)) {
947                taskAffiliation = Integer.valueOf(attrValue);
948            } else if (ATTR_PREV_AFFILIATION.equals(attrName)) {
949                prevTaskId = Integer.valueOf(attrValue);
950            } else if (ATTR_NEXT_AFFILIATION.equals(attrName)) {
951                nextTaskId = Integer.valueOf(attrValue);
952            } else if (ATTR_TASK_AFFILIATION_COLOR.equals(attrName)) {
953                taskAffiliationColor = Integer.valueOf(attrValue);
954            } else if (ATTR_CALLING_UID.equals(attrName)) {
955                callingUid = Integer.valueOf(attrValue);
956            } else if (ATTR_CALLING_PACKAGE.equals(attrName)) {
957                callingPackage = attrValue;
958            } else {
959                Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
960            }
961        }
962
963        int event;
964        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
965                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
966            if (event == XmlPullParser.START_TAG) {
967                final String name = in.getName();
968                if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" +
969                        name);
970                if (TAG_AFFINITYINTENT.equals(name)) {
971                    affinityIntent = Intent.restoreFromXml(in);
972                } else if (TAG_INTENT.equals(name)) {
973                    intent = Intent.restoreFromXml(in);
974                } else if (TAG_ACTIVITY.equals(name)) {
975                    ActivityRecord activity =
976                            ActivityRecord.restoreFromXml(in, taskId, stackSupervisor);
977                    if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" +
978                            activity);
979                    if (activity != null) {
980                        activities.add(activity);
981                    }
982                } else {
983                    Slog.e(TAG, "restoreTask: Unexpected name=" + name);
984                    XmlUtils.skipCurrentTag(in);
985                }
986            }
987        }
988
989        if (!hasRootAffinity) {
990            rootAffinity = affinity;
991        } else if ("@".equals(rootAffinity)) {
992            rootAffinity = null;
993        }
994
995        if (effectiveUid <= 0) {
996            Intent checkIntent = intent != null ? intent : affinityIntent;
997            effectiveUid = 0;
998            if (checkIntent != null) {
999                IPackageManager pm = AppGlobals.getPackageManager();
1000                try {
1001                    ApplicationInfo ai = pm.getApplicationInfo(
1002                            checkIntent.getComponent().getPackageName(),
1003                            PackageManager.GET_UNINSTALLED_PACKAGES
1004                                    | PackageManager.GET_DISABLED_COMPONENTS, userId);
1005                    if (ai != null) {
1006                        effectiveUid = ai.uid;
1007                    }
1008                } catch (RemoteException e) {
1009                }
1010            }
1011            Slog.w(TAG, "Updating task #" + taskId + " for " + checkIntent
1012                    + ": effectiveUid=" + effectiveUid);
1013        }
1014
1015        final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
1016                affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
1017                autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
1018                activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
1019                taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
1020                callingUid, callingPackage);
1021
1022        for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
1023            activities.get(activityNdx).task = task;
1024        }
1025
1026        if (ActivityManagerService.DEBUG_RECENTS) Slog.d(TAG, "Restored task=" + task);
1027        return task;
1028    }
1029
1030    void dump(PrintWriter pw, String prefix) {
1031        pw.print(prefix); pw.print("userId="); pw.print(userId);
1032                pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
1033                pw.print(" mCallingUid="); UserHandle.formatUid(pw, mCallingUid);
1034                pw.print(" mCallingPackage="); pw.println(mCallingPackage);
1035        if (affinity != null || rootAffinity != null) {
1036            pw.print(prefix); pw.print("affinity="); pw.print(affinity);
1037            if (affinity == null || !affinity.equals(rootAffinity)) {
1038                pw.print(" root="); pw.println(rootAffinity);
1039            } else {
1040                pw.println();
1041            }
1042        }
1043        if (voiceSession != null || voiceInteractor != null) {
1044            pw.print(prefix); pw.print("VOICE: session=0x");
1045            pw.print(Integer.toHexString(System.identityHashCode(voiceSession)));
1046            pw.print(" interactor=0x");
1047            pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor)));
1048        }
1049        if (intent != null) {
1050            StringBuilder sb = new StringBuilder(128);
1051            sb.append(prefix); sb.append("intent={");
1052            intent.toShortString(sb, false, true, false, true);
1053            sb.append('}');
1054            pw.println(sb.toString());
1055        }
1056        if (affinityIntent != null) {
1057            StringBuilder sb = new StringBuilder(128);
1058            sb.append(prefix); sb.append("affinityIntent={");
1059            affinityIntent.toShortString(sb, false, true, false, true);
1060            sb.append('}');
1061            pw.println(sb.toString());
1062        }
1063        if (origActivity != null) {
1064            pw.print(prefix); pw.print("origActivity=");
1065            pw.println(origActivity.flattenToShortString());
1066        }
1067        if (realActivity != null) {
1068            pw.print(prefix); pw.print("realActivity=");
1069            pw.println(realActivity.flattenToShortString());
1070        }
1071        if (autoRemoveRecents || isPersistable || taskType != 0 || mTaskToReturnTo != 0
1072                || numFullscreen != 0) {
1073            pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents);
1074                    pw.print(" isPersistable="); pw.print(isPersistable);
1075                    pw.print(" numFullscreen="); pw.print(numFullscreen);
1076                    pw.print(" taskType="); pw.print(taskType);
1077                    pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
1078        }
1079        if (rootWasReset || mNeverRelinquishIdentity || mReuseTask) {
1080            pw.print(prefix); pw.print("rootWasReset="); pw.print(rootWasReset);
1081                    pw.print(" mNeverRelinquishIdentity="); pw.print(mNeverRelinquishIdentity);
1082                    pw.print(" mReuseTask="); pw.println(mReuseTask);
1083        }
1084        if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != -1 || mPrevAffiliate != null
1085                || mNextAffiliateTaskId != -1 || mNextAffiliate != null) {
1086            pw.print(prefix); pw.print("affiliation="); pw.print(mAffiliatedTaskId);
1087                    pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
1088                    pw.print(" (");
1089                    if (mPrevAffiliate == null) {
1090                        pw.print("null");
1091                    } else {
1092                        pw.print(Integer.toHexString(System.identityHashCode(mPrevAffiliate)));
1093                    }
1094                    pw.print(") nextAffiliation="); pw.print(mNextAffiliateTaskId);
1095                    pw.print(" (");
1096                    if (mNextAffiliate == null) {
1097                        pw.print("null");
1098                    } else {
1099                        pw.print(Integer.toHexString(System.identityHashCode(mNextAffiliate)));
1100                    }
1101                    pw.println(")");
1102        }
1103        pw.print(prefix); pw.print("Activities="); pw.println(mActivities);
1104        if (!askedCompatMode || !inRecents || !isAvailable) {
1105            pw.print(prefix); pw.print("askedCompatMode="); pw.print(askedCompatMode);
1106                    pw.print(" inRecents="); pw.print(inRecents);
1107                    pw.print(" isAvailable="); pw.println(isAvailable);
1108        }
1109        pw.print(prefix); pw.print("lastThumbnail="); pw.print(mLastThumbnail);
1110                pw.print(" lastThumbnailFile="); pw.println(mLastThumbnailFile);
1111        if (lastDescription != null) {
1112            pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
1113        }
1114        pw.print(prefix); pw.print("hasBeenVisible="); pw.print(hasBeenVisible);
1115                pw.print(" firstActiveTime="); pw.print(lastActiveTime);
1116                pw.print(" lastActiveTime="); pw.print(lastActiveTime);
1117                pw.print(" (inactive for ");
1118                pw.print((getInactiveDuration()/1000)); pw.println("s)");
1119    }
1120
1121    @Override
1122    public String toString() {
1123        StringBuilder sb = new StringBuilder(128);
1124        if (stringName != null) {
1125            sb.append(stringName);
1126            sb.append(" U=");
1127            sb.append(userId);
1128            sb.append(" sz=");
1129            sb.append(mActivities.size());
1130            sb.append('}');
1131            return sb.toString();
1132        }
1133        sb.append("TaskRecord{");
1134        sb.append(Integer.toHexString(System.identityHashCode(this)));
1135        sb.append(" #");
1136        sb.append(taskId);
1137        if (affinity != null) {
1138            sb.append(" A=");
1139            sb.append(affinity);
1140        } else if (intent != null) {
1141            sb.append(" I=");
1142            sb.append(intent.getComponent().flattenToShortString());
1143        } else if (affinityIntent != null) {
1144            sb.append(" aI=");
1145            sb.append(affinityIntent.getComponent().flattenToShortString());
1146        } else {
1147            sb.append(" ??");
1148        }
1149        stringName = sb.toString();
1150        return toString();
1151    }
1152}
1153