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