/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.TaskThumbnail; import android.app.ActivityManager.TaskDescription; import android.app.ActivityOptions; import android.app.AppGlobals; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.os.Debug; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.util.Slog; import com.android.internal.app.IVoiceInteractor; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; final class TaskRecord { private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM; private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; private static final String TAG_TASKS = TAG + POSTFIX_TASKS; static final String ATTR_TASKID = "task_id"; private static final String TAG_INTENT = "intent"; private static final String TAG_AFFINITYINTENT = "affinity_intent"; static final String ATTR_REALACTIVITY = "real_activity"; private static final String ATTR_ORIGACTIVITY = "orig_activity"; private static final String TAG_ACTIVITY = "activity"; private static final String ATTR_AFFINITY = "affinity"; private static final String ATTR_ROOT_AFFINITY = "root_affinity"; private static final String ATTR_ROOTHASRESET = "root_has_reset"; private static final String ATTR_AUTOREMOVERECENTS = "auto_remove_recents"; private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode"; private static final String ATTR_USERID = "user_id"; private static final String ATTR_EFFECTIVE_UID = "effective_uid"; private static final String ATTR_TASKTYPE = "task_type"; private static final String ATTR_FIRSTACTIVETIME = "first_active_time"; private static final String ATTR_LASTACTIVETIME = "last_active_time"; private static final String ATTR_LASTDESCRIPTION = "last_description"; private static final String ATTR_LASTTIMEMOVED = "last_time_moved"; private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity"; static final String ATTR_TASK_AFFILIATION = "task_affiliation"; private static final String ATTR_PREV_AFFILIATION = "prev_affiliation"; private static final String ATTR_NEXT_AFFILIATION = "next_affiliation"; private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color"; private static final String ATTR_CALLING_UID = "calling_uid"; private static final String ATTR_CALLING_PACKAGE = "calling_package"; private static final String ATTR_RESIZEABLE = "resizeable"; private static final String ATTR_PRIVILEGED = "privileged"; private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail"; static final boolean IGNORE_RETURN_TO_RECENTS = true; static final int INVALID_TASK_ID = -1; final int taskId; // Unique identifier for this task. String affinity; // The affinity name for this task, or null; may change identity. String rootAffinity; // Initial base affinity, or null; does not change from initial root. final IVoiceInteractionSession voiceSession; // Voice interaction session driving task final IVoiceInteractor voiceInteractor; // Associated interactor to provide to app Intent intent; // The original intent that started the task. Intent affinityIntent; // Intent of affinity-moved activity that started this task. int effectiveUid; // The current effective uid of the identity of this task. ComponentName origActivity; // The non-alias activity component of the intent. ComponentName realActivity; // The actual activity component that started the task. long firstActiveTime; // First time this task was active. long lastActiveTime; // Last time this task was active, including sleep. boolean inRecents; // Actually in the recents list? boolean isAvailable; // Is the activity available to be launched? boolean rootWasReset; // True if the intent at the root of the task had // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag. boolean autoRemoveRecents; // If true, we should automatically remove the task from // recents when activity finishes boolean askedCompatMode;// Have asked the user about compat mode for this task. boolean hasBeenVisible; // Set if any activities in the task have been visible to the user. String stringName; // caching of toString() result. int userId; // user for which this task was created int numFullscreen; // Number of fullscreen activities. boolean mResizeable; // Activities in the task resizeable. Based on the resizable setting of // the root activity. int mLockTaskMode; // Which tasklock mode to launch this task in. One of // ActivityManager.LOCK_TASK_LAUNCH_MODE_* private boolean mPrivileged; // The root activity application of this task holds // privileged permissions. /** Can't be put in lockTask mode. */ final static int LOCK_TASK_AUTH_DONT_LOCK = 0; /** Can enter app pinning with user approval. Can never start over existing lockTask task. */ final static int LOCK_TASK_AUTH_PINNABLE = 1; /** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */ final static int LOCK_TASK_AUTH_LAUNCHABLE = 2; /** Can enter lockTask without user approval. Can start over existing lockTask task. */ final static int LOCK_TASK_AUTH_WHITELISTED = 3; /** Priv-app that starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing * lockTask task. */ final static int LOCK_TASK_AUTH_LAUNCHABLE_PRIV = 4; int mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE; int mLockTaskUid = -1; // The uid of the application that called startLockTask(). // This represents the last resolved activity values for this task // NOTE: This value needs to be persisted with each task TaskDescription lastTaskDescription = new TaskDescription(); /** List of all activities in the task arranged in history order */ final ArrayList<ActivityRecord> mActivities; /** Current stack */ ActivityStack stack; /** Takes on same set of values as ActivityRecord.mActivityType */ int taskType; /** Takes on same value as first root activity */ boolean isPersistable = false; int maxRecents; /** Only used for persistable tasks, otherwise 0. The last time this task was moved. Used for * determining the order when restoring. Sign indicates whether last task movement was to front * (positive) or back (negative). Absolute value indicates time. */ long mLastTimeMoved = System.currentTimeMillis(); /** Indication of what to run next when task exits. Use ActivityRecord types. * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the * task stack. */ private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE; /** If original intent did not allow relinquishing task identity, save that information */ boolean mNeverRelinquishIdentity = true; // Used in the unique case where we are clearing the task in order to reuse it. In that case we // do not want to delete the stack when the task goes empty. boolean mReuseTask = false; private Bitmap mLastThumbnail; // Last thumbnail captured for this item. private final File mLastThumbnailFile; // File containing last thumbnail. private final String mFilename; CharSequence lastDescription; // Last description captured for this item. int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent. int mAffiliatedTaskColor; // color of the parent task affiliation. TaskRecord mPrevAffiliate; // previous task in affiliated chain. int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence. TaskRecord mNextAffiliate; // next task in affiliated chain. int mNextAffiliateTaskId = INVALID_TASK_ID; // next id for persistence. // For relaunching the task from recents as though it was launched by the original launcher. int mCallingUid; String mCallingPackage; final ActivityManagerService mService; TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename); taskId = _taskId; mAffiliatedTaskId = _taskId; voiceSession = _voiceSession; voiceInteractor = _voiceInteractor; isAvailable = true; mActivities = new ArrayList<>(); mCallingUid = info.applicationInfo.uid; mCallingPackage = info.packageName; setIntent(_intent, info); } TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, TaskDescription _taskDescription) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename); taskId = _taskId; mAffiliatedTaskId = _taskId; voiceSession = null; voiceInteractor = null; isAvailable = true; mActivities = new ArrayList<>(); mCallingUid = info.applicationInfo.uid; mCallingPackage = info.packageName; setIntent(_intent, info); taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; isPersistable = true; // Clamp to [1, max]. maxRecents = Math.min(Math.max(info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); taskType = APPLICATION_ACTIVITY_TYPE; mTaskToReturnTo = HOME_ACTIVITY_TYPE; userId = UserHandle.getUserId(info.applicationInfo.uid); lastTaskDescription = _taskDescription; } private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, boolean _autoRemoveRecents, boolean _askedCompatMode, int _taskType, int _userId, int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime, long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage, boolean resizeable, boolean privileged) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename); taskId = _taskId; intent = _intent; affinityIntent = _affinityIntent; affinity = _affinity; rootAffinity = _rootAffinity; voiceSession = null; voiceInteractor = null; realActivity = _realActivity; origActivity = _origActivity; rootWasReset = _rootWasReset; isAvailable = true; autoRemoveRecents = _autoRemoveRecents; askedCompatMode = _askedCompatMode; taskType = _taskType; mTaskToReturnTo = HOME_ACTIVITY_TYPE; userId = _userId; effectiveUid = _effectiveUid; firstActiveTime = _firstActiveTime; lastActiveTime = _lastActiveTime; lastDescription = _lastDescription; mActivities = activities; mLastTimeMoved = lastTimeMoved; mNeverRelinquishIdentity = neverRelinquishIdentity; lastTaskDescription = _lastTaskDescription; mAffiliatedTaskId = taskAffiliation; mAffiliatedTaskColor = taskAffiliationColor; mPrevAffiliateTaskId = prevTaskId; mNextAffiliateTaskId = nextTaskId; mCallingUid = callingUid; mCallingPackage = callingPackage; mResizeable = resizeable; mPrivileged = privileged; } void touchActiveTime() { lastActiveTime = System.currentTimeMillis(); if (firstActiveTime == 0) { firstActiveTime = lastActiveTime; } } long getInactiveDuration() { return System.currentTimeMillis() - lastActiveTime; } /** Sets the original intent, and the calling uid and package. */ void setIntent(ActivityRecord r) { mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; setIntent(r.intent, r.info); } /** Sets the original intent, _without_ updating the calling uid or package. */ private void setIntent(Intent _intent, ActivityInfo info) { if (intent == null) { mNeverRelinquishIdentity = (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0; } else if (mNeverRelinquishIdentity) { return; } affinity = info.taskAffinity; if (intent == null) { // If this task already has an intent associated with it, don't set the root // affinity -- we don't want it changing after initially set, but the initially // set value may be null. rootAffinity = affinity; } effectiveUid = info.applicationInfo.uid; stringName = null; if (info.targetActivity == null) { if (_intent != null) { // If this Intent has a selector, we want to clear it for the // recent task since it is not relevant if the user later wants // to re-launch the app. if (_intent.getSelector() != null || _intent.getSourceBounds() != null) { _intent = new Intent(_intent); _intent.setSelector(null); _intent.setSourceBounds(null); } } if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Setting Intent of " + this + " to " + _intent); intent = _intent; realActivity = _intent != null ? _intent.getComponent() : null; origActivity = null; } else { ComponentName targetComponent = new ComponentName( info.packageName, info.targetActivity); if (_intent != null) { Intent targetIntent = new Intent(_intent); targetIntent.setComponent(targetComponent); targetIntent.setSelector(null); targetIntent.setSourceBounds(null); if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Setting Intent of " + this + " to target " + targetIntent); intent = targetIntent; realActivity = targetComponent; origActivity = _intent.getComponent(); } else { intent = null; realActivity = targetComponent; origActivity = new ComponentName(info.packageName, info.name); } } final int intentFlags = intent == null ? 0 : intent.getFlags(); if ((intentFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { // Once we are set to an Intent with this flag, we count this // task as having a true root activity. rootWasReset = true; } userId = UserHandle.getUserId(info.applicationInfo.uid); if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) { // If the activity itself has requested auto-remove, then just always do it. autoRemoveRecents = true; } else if ((intentFlags & (FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == FLAG_ACTIVITY_NEW_DOCUMENT) { // If the caller has not asked for the document to be retained, then we may // want to turn on auto-remove, depending on whether the target has set its // own document launch mode. if (info.documentLaunchMode != ActivityInfo.DOCUMENT_LAUNCH_NONE) { autoRemoveRecents = false; } else { autoRemoveRecents = true; } } else { autoRemoveRecents = false; } mResizeable = info.resizeable; mLockTaskMode = info.lockTaskLaunchMode; mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0; setLockTaskAuth(); } void setTaskToReturnTo(int taskToReturnTo) { mTaskToReturnTo = (IGNORE_RETURN_TO_RECENTS && taskToReturnTo == RECENTS_ACTIVITY_TYPE) ? HOME_ACTIVITY_TYPE : taskToReturnTo; } int getTaskToReturnTo() { return mTaskToReturnTo; } void setPrevAffiliate(TaskRecord prevAffiliate) { mPrevAffiliate = prevAffiliate; mPrevAffiliateTaskId = prevAffiliate == null ? INVALID_TASK_ID : prevAffiliate.taskId; } void setNextAffiliate(TaskRecord nextAffiliate) { mNextAffiliate = nextAffiliate; mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId; } // Close up recents linked list. void closeRecentsChain() { if (mPrevAffiliate != null) { mPrevAffiliate.setNextAffiliate(mNextAffiliate); } if (mNextAffiliate != null) { mNextAffiliate.setPrevAffiliate(mPrevAffiliate); } setPrevAffiliate(null); setNextAffiliate(null); } void removedFromRecents() { disposeThumbnail(); closeRecentsChain(); if (inRecents) { inRecents = false; mService.notifyTaskPersisterLocked(this, false); } } void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) { closeRecentsChain(); mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId; mAffiliatedTaskColor = taskToAffiliateWith.mAffiliatedTaskColor; // Find the end while (taskToAffiliateWith.mNextAffiliate != null) { final TaskRecord nextRecents = taskToAffiliateWith.mNextAffiliate; if (nextRecents.mAffiliatedTaskId != mAffiliatedTaskId) { Slog.e(TAG, "setTaskToAffiliateWith: nextRecents=" + nextRecents + " affilTaskId=" + nextRecents.mAffiliatedTaskId + " should be " + mAffiliatedTaskId); if (nextRecents.mPrevAffiliate == taskToAffiliateWith) { nextRecents.setPrevAffiliate(null); } taskToAffiliateWith.setNextAffiliate(null); break; } taskToAffiliateWith = nextRecents; } taskToAffiliateWith.setNextAffiliate(this); setPrevAffiliate(taskToAffiliateWith); setNextAffiliate(null); } /** * Sets the last thumbnail. * @return whether the thumbnail was set */ boolean setLastThumbnail(Bitmap thumbnail) { if (mLastThumbnail != thumbnail) { mLastThumbnail = thumbnail; if (thumbnail == null) { if (mLastThumbnailFile != null) { mLastThumbnailFile.delete(); } } else { mService.mTaskPersister.saveImage(thumbnail, mFilename); } return true; } return false; } void getLastThumbnail(TaskThumbnail thumbs) { thumbs.mainThumbnail = mLastThumbnail; thumbs.thumbnailFileDescriptor = null; if (mLastThumbnail == null) { thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(mFilename); } // Only load the thumbnail file if we don't have a thumbnail if (thumbs.mainThumbnail == null && mLastThumbnailFile.exists()) { try { thumbs.thumbnailFileDescriptor = ParcelFileDescriptor.open(mLastThumbnailFile, ParcelFileDescriptor.MODE_READ_ONLY); } catch (IOException e) { } } } void freeLastThumbnail() { mLastThumbnail = null; } void disposeThumbnail() { mLastThumbnail = null; lastDescription = null; } /** Returns the intent for the root activity for this task */ Intent getBaseIntent() { return intent != null ? intent : affinityIntent; } /** Returns the first non-finishing activity from the root. */ ActivityRecord getRootActivity() { for (int i = 0; i < mActivities.size(); i++) { final ActivityRecord r = mActivities.get(i); if (r.finishing) { continue; } return r; } return null; } ActivityRecord getTopActivity() { for (int i = mActivities.size() - 1; i >= 0; --i) { final ActivityRecord r = mActivities.get(i); if (r.finishing) { continue; } return r; } return null; } ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { if (stack != null) { for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) { ActivityRecord r = mActivities.get(activityNdx); if (!r.finishing && r != notTop && stack.okToShowLocked(r)) { return r; } } } return null; } /** Call after activity movement or finish to make sure that frontOfTask is set correctly */ final void setFrontOfTask() { boolean foundFront = false; final int numActivities = mActivities.size(); for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (foundFront || r.finishing) { r.frontOfTask = false; } else { r.frontOfTask = true; // Set frontOfTask false for every following activity. foundFront = true; } } if (!foundFront && numActivities > 0) { // All activities of this task are finishing. As we ought to have a frontOfTask // activity, make the bottom activity front. mActivities.get(0).frontOfTask = true; } } /** * Reorder the history stack so that the passed activity is brought to the front. */ final void moveActivityToFrontLocked(ActivityRecord newTop) { if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing and adding activity " + newTop + " to stack at top callers=" + Debug.getCallers(4)); mActivities.remove(newTop); mActivities.add(newTop); updateEffectiveIntent(); setFrontOfTask(); } void addActivityAtBottom(ActivityRecord r) { addActivityAtIndex(0, r); } void addActivityToTop(ActivityRecord r) { addActivityAtIndex(mActivities.size(), r); } void addActivityAtIndex(int index, ActivityRecord r) { // Remove r first, and if it wasn't already in the list and it's fullscreen, count it. if (!mActivities.remove(r) && r.fullscreen) { // Was not previously in list. numFullscreen++; } // Only set this based on the first activity if (mActivities.isEmpty()) { taskType = r.mActivityType; isPersistable = r.isPersistable(); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; // Clamp to [1, max]. maxRecents = Math.min(Math.max(r.info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); } else { // Otherwise make all added activities match this one. r.mActivityType = taskType; } mActivities.add(index, r); updateEffectiveIntent(); if (r.isPersistable()) { mService.notifyTaskPersisterLocked(this, false); } } /** @return true if this was the last activity in the task */ boolean removeActivity(ActivityRecord r) { if (mActivities.remove(r) && r.fullscreen) { // Was previously in list. numFullscreen--; } if (r.isPersistable()) { mService.notifyTaskPersisterLocked(this, false); } if (mActivities.isEmpty()) { return !mReuseTask; } updateEffectiveIntent(); return false; } boolean autoRemoveFromRecents() { // We will automatically remove the task either if it has explicitly asked for // this, or it is empty and has never contained an activity that got shown to // the user. return autoRemoveRecents || (mActivities.isEmpty() && !hasBeenVisible); } /** * Completely remove all activities associated with an existing * task starting at a specified index. */ final void performClearTaskAtIndexLocked(int activityNdx) { int numActivities = mActivities.size(); for ( ; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (r.finishing) { continue; } if (stack == null) { // Task was restored from persistent storage. r.takeFromHistory(); mActivities.remove(activityNdx); --activityNdx; --numActivities; } else if (stack.finishActivityLocked( r, Activity.RESULT_CANCELED, null, "clear-task-index", false)) { --activityNdx; --numActivities; } } } /** * Completely remove all activities associated with an existing task. */ final void performClearTaskLocked() { mReuseTask = true; performClearTaskAtIndexLocked(0); mReuseTask = false; } /** * Perform clear operation as requested by * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the * stack to the given task, then look for * an instance of that activity in the stack and, if found, finish all * activities on top of it and return the instance. * * @param newR Description of the new activity being started. * @return Returns the old activity that should be continued to be used, * or null if none was found. */ final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) { int numActivities = mActivities.size(); for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) { ActivityRecord r = mActivities.get(activityNdx); if (r.finishing) { continue; } if (r.realActivity.equals(newR.realActivity)) { // Here it is! Now finish everything in front... final ActivityRecord ret = r; for (++activityNdx; activityNdx < numActivities; ++activityNdx) { r = mActivities.get(activityNdx); if (r.finishing) { continue; } ActivityOptions opts = r.takeOptionsLocked(); if (opts != null) { ret.updateOptionsLocked(opts); } if (stack != null && stack.finishActivityLocked( r, Activity.RESULT_CANCELED, null, "clear-task-stack", false)) { --activityNdx; --numActivities; } } // Finally, if this is a normal launch mode (that is, not // expecting onNewIntent()), then we will finish the current // instance of the activity so a new fresh one can be started. if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE && (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { if (!ret.finishing) { if (stack != null) { stack.finishActivityLocked( ret, Activity.RESULT_CANCELED, null, "clear-task-top", false); } return null; } } return ret; } } return null; } public TaskThumbnail getTaskThumbnailLocked() { if (stack != null) { final ActivityRecord resumedActivity = stack.mResumedActivity; if (resumedActivity != null && resumedActivity.task == this) { final Bitmap thumbnail = stack.screenshotActivities(resumedActivity); setLastThumbnail(thumbnail); } } final TaskThumbnail taskThumbnail = new TaskThumbnail(); getLastThumbnail(taskThumbnail); return taskThumbnail; } public void removeTaskActivitiesLocked() { // Just remove the entire task. performClearTaskAtIndexLocked(0); } String lockTaskAuthToString() { switch (mLockTaskAuth) { case LOCK_TASK_AUTH_DONT_LOCK: return "LOCK_TASK_AUTH_DONT_LOCK"; case LOCK_TASK_AUTH_PINNABLE: return "LOCK_TASK_AUTH_PINNABLE"; case LOCK_TASK_AUTH_LAUNCHABLE: return "LOCK_TASK_AUTH_LAUNCHABLE"; case LOCK_TASK_AUTH_WHITELISTED: return "LOCK_TASK_AUTH_WHITELISTED"; case LOCK_TASK_AUTH_LAUNCHABLE_PRIV: return "LOCK_TASK_AUTH_LAUNCHABLE_PRIV"; default: return "unknown=" + mLockTaskAuth; } } void setLockTaskAuth() { if (!mPrivileged && (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS || mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { // Non-priv apps are not allowed to use always or never, fall back to default mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; } switch (mLockTaskMode) { case LOCK_TASK_LAUNCH_MODE_DEFAULT: mLockTaskAuth = isLockTaskWhitelistedLocked() ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE; break; case LOCK_TASK_LAUNCH_MODE_NEVER: mLockTaskAuth = LOCK_TASK_AUTH_DONT_LOCK; break; case LOCK_TASK_LAUNCH_MODE_ALWAYS: mLockTaskAuth = LOCK_TASK_AUTH_LAUNCHABLE_PRIV; break; case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED: mLockTaskAuth = isLockTaskWhitelistedLocked() ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE; break; } if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "setLockTaskAuth: task=" + this + " mLockTaskAuth=" + lockTaskAuthToString()); } boolean isLockTaskWhitelistedLocked() { String pkg = (realActivity != null) ? realActivity.getPackageName() : null; if (pkg == null) { return false; } String[] packages = mService.mLockTaskPackages.get(userId); if (packages == null) { return false; } for (int i = packages.length - 1; i >= 0; --i) { if (pkg.equals(packages[i])) { return true; } } return false; } boolean isHomeTask() { return taskType == HOME_ACTIVITY_TYPE; } boolean isApplicationTask() { return taskType == APPLICATION_ACTIVITY_TYPE; } boolean isOverHomeStack() { return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE; } /** * Find the activity in the history stack within the given task. Returns * the index within the history at which it's found, or < 0 if not found. */ final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) { final ComponentName realActivity = r.realActivity; for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) { ActivityRecord candidate = mActivities.get(activityNdx); if (candidate.finishing) { continue; } if (candidate.realActivity.equals(realActivity)) { return candidate; } } return null; } /** Updates the last task description values. */ void updateTaskDescription() { // Traverse upwards looking for any break between main task activities and // utility activities. int activityNdx; final int numActivities = mActivities.size(); final boolean relinquish = numActivities == 0 ? false : (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0; for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) { // This will be the top activity for determining taskDescription. Pre-inc to // overcome initial decrement below. ++activityNdx; break; } if (r.intent != null && (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { break; } } if (activityNdx > 0) { // Traverse downwards starting below break looking for set label, icon. // Note that if there are activities in the task but none of them set the // recent activity values, then we do not fall back to the last set // values in the TaskRecord. String label = null; String iconFilename = null; int colorPrimary = 0; for (--activityNdx; activityNdx >= 0; --activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (r.taskDescription != null) { if (label == null) { label = r.taskDescription.getLabel(); } if (iconFilename == null) { iconFilename = r.taskDescription.getIconFilename(); } if (colorPrimary == 0) { colorPrimary = r.taskDescription.getPrimaryColor(); } } } lastTaskDescription = new TaskDescription(label, colorPrimary, iconFilename); // Update the task affiliation color if we are the parent of the group if (taskId == mAffiliatedTaskId) { mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor(); } } } int findEffectiveRootIndex() { int effectiveNdx = 0; final int topActivityNdx = mActivities.size() - 1; for (int activityNdx = 0; activityNdx <= topActivityNdx; ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (r.finishing) { continue; } effectiveNdx = activityNdx; if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) { break; } } return effectiveNdx; } void updateEffectiveIntent() { final int effectiveRootIndex = findEffectiveRootIndex(); final ActivityRecord r = mActivities.get(effectiveRootIndex); setIntent(r); } void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { if (DEBUG_RECENTS) Slog.i(TAG_RECENTS, "Saving task=" + this); out.attribute(null, ATTR_TASKID, String.valueOf(taskId)); if (realActivity != null) { out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString()); } if (origActivity != null) { out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString()); } // Write affinity, and root affinity if it is different from affinity. // We use the special string "@" for a null root affinity, so we can identify // later whether we were given a root affinity or should just make it the // same as the affinity. if (affinity != null) { out.attribute(null, ATTR_AFFINITY, affinity); if (!affinity.equals(rootAffinity)) { out.attribute(null, ATTR_ROOT_AFFINITY, rootAffinity != null ? rootAffinity : "@"); } } else if (rootAffinity != null) { out.attribute(null, ATTR_ROOT_AFFINITY, rootAffinity != null ? rootAffinity : "@"); } out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset)); out.attribute(null, ATTR_AUTOREMOVERECENTS, String.valueOf(autoRemoveRecents)); out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode)); out.attribute(null, ATTR_USERID, String.valueOf(userId)); out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid)); out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType)); out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime)); out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime)); out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved)); out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity)); if (lastDescription != null) { out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString()); } if (lastTaskDescription != null) { lastTaskDescription.saveToXml(out); } out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor)); out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId)); out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId)); out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId)); out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid)); out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage); out.attribute(null, ATTR_RESIZEABLE, String.valueOf(mResizeable)); out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged)); if (affinityIntent != null) { out.startTag(null, TAG_AFFINITYINTENT); affinityIntent.saveToXml(out); out.endTag(null, TAG_AFFINITYINTENT); } out.startTag(null, TAG_INTENT); intent.saveToXml(out); out.endTag(null, TAG_INTENT); final ArrayList<ActivityRecord> activities = mActivities; final int numActivities = activities.size(); for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = activities.get(activityNdx); if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() || ((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) && activityNdx > 0) { // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET). break; } out.startTag(null, TAG_ACTIVITY); r.saveToXml(out); out.endTag(null, TAG_ACTIVITY); } } static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException { Intent intent = null; Intent affinityIntent = null; ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); ComponentName realActivity = null; ComponentName origActivity = null; String affinity = null; String rootAffinity = null; boolean hasRootAffinity = false; boolean rootHasReset = false; boolean autoRemoveRecents = false; boolean askedCompatMode = false; int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; int userId = 0; int effectiveUid = -1; String lastDescription = null; long firstActiveTime = -1; long lastActiveTime = -1; long lastTimeOnTop = 0; boolean neverRelinquishIdentity = true; int taskId = INVALID_TASK_ID; final int outerDepth = in.getDepth(); TaskDescription taskDescription = new TaskDescription(); int taskAffiliation = INVALID_TASK_ID; int taskAffiliationColor = 0; int prevTaskId = INVALID_TASK_ID; int nextTaskId = INVALID_TASK_ID; int callingUid = -1; String callingPackage = ""; boolean resizeable = false; boolean privileged = false; for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) { final String attrName = in.getAttributeName(attrNdx); final String attrValue = in.getAttributeValue(attrNdx); if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" + attrName + " value=" + attrValue); if (ATTR_TASKID.equals(attrName)) { if (taskId == INVALID_TASK_ID) taskId = Integer.valueOf(attrValue); } else if (ATTR_REALACTIVITY.equals(attrName)) { realActivity = ComponentName.unflattenFromString(attrValue); } else if (ATTR_ORIGACTIVITY.equals(attrName)) { origActivity = ComponentName.unflattenFromString(attrValue); } else if (ATTR_AFFINITY.equals(attrName)) { affinity = attrValue; } else if (ATTR_ROOT_AFFINITY.equals(attrName)) { rootAffinity = attrValue; hasRootAffinity = true; } else if (ATTR_ROOTHASRESET.equals(attrName)) { rootHasReset = Boolean.valueOf(attrValue); } else if (ATTR_AUTOREMOVERECENTS.equals(attrName)) { autoRemoveRecents = Boolean.valueOf(attrValue); } else if (ATTR_ASKEDCOMPATMODE.equals(attrName)) { askedCompatMode = Boolean.valueOf(attrValue); } else if (ATTR_USERID.equals(attrName)) { userId = Integer.valueOf(attrValue); } else if (ATTR_EFFECTIVE_UID.equals(attrName)) { effectiveUid = Integer.valueOf(attrValue); } else if (ATTR_TASKTYPE.equals(attrName)) { taskType = Integer.valueOf(attrValue); } else if (ATTR_FIRSTACTIVETIME.equals(attrName)) { firstActiveTime = Long.valueOf(attrValue); } else if (ATTR_LASTACTIVETIME.equals(attrName)) { lastActiveTime = Long.valueOf(attrValue); } else if (ATTR_LASTDESCRIPTION.equals(attrName)) { lastDescription = attrValue; } else if (ATTR_LASTTIMEMOVED.equals(attrName)) { lastTimeOnTop = Long.valueOf(attrValue); } else if (ATTR_NEVERRELINQUISH.equals(attrName)) { neverRelinquishIdentity = Boolean.valueOf(attrValue); } else if (attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) { taskDescription.restoreFromXml(attrName, attrValue); } else if (ATTR_TASK_AFFILIATION.equals(attrName)) { taskAffiliation = Integer.valueOf(attrValue); } else if (ATTR_PREV_AFFILIATION.equals(attrName)) { prevTaskId = Integer.valueOf(attrValue); } else if (ATTR_NEXT_AFFILIATION.equals(attrName)) { nextTaskId = Integer.valueOf(attrValue); } else if (ATTR_TASK_AFFILIATION_COLOR.equals(attrName)) { taskAffiliationColor = Integer.valueOf(attrValue); } else if (ATTR_CALLING_UID.equals(attrName)) { callingUid = Integer.valueOf(attrValue); } else if (ATTR_CALLING_PACKAGE.equals(attrName)) { callingPackage = attrValue; } else if (ATTR_RESIZEABLE.equals(attrName)) { resizeable = Boolean.valueOf(attrValue); } else if (ATTR_PRIVILEGED.equals(attrName)) { privileged = Boolean.valueOf(attrValue); } else { Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName); } } int event; while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { if (event == XmlPullParser.START_TAG) { final String name = in.getName(); if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" + name); if (TAG_AFFINITYINTENT.equals(name)) { affinityIntent = Intent.restoreFromXml(in); } else if (TAG_INTENT.equals(name)) { intent = Intent.restoreFromXml(in); } else if (TAG_ACTIVITY.equals(name)) { ActivityRecord activity = ActivityRecord.restoreFromXml(in, stackSupervisor); if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" + activity); if (activity != null) { activities.add(activity); } } else { Slog.e(TAG, "restoreTask: Unexpected name=" + name); XmlUtils.skipCurrentTag(in); } } } if (!hasRootAffinity) { rootAffinity = affinity; } else if ("@".equals(rootAffinity)) { rootAffinity = null; } if (effectiveUid <= 0) { Intent checkIntent = intent != null ? intent : affinityIntent; effectiveUid = 0; if (checkIntent != null) { IPackageManager pm = AppGlobals.getPackageManager(); try { ApplicationInfo ai = pm.getApplicationInfo( checkIntent.getComponent().getPackageName(), PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS, userId); if (ai != null) { effectiveUid = ai.uid; } } catch (RemoteException e) { } } Slog.w(TAG, "Updating task #" + taskId + " for " + checkIntent + ": effectiveUid=" + effectiveUid); } final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent, affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset, autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription, activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid, callingPackage, resizeable, privileged); for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) { activities.get(activityNdx).task = task; } if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Restored task=" + task); return task; } void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); pw.print(" mCallingUid="); UserHandle.formatUid(pw, mCallingUid); pw.print(" mCallingPackage="); pw.println(mCallingPackage); if (affinity != null || rootAffinity != null) { pw.print(prefix); pw.print("affinity="); pw.print(affinity); if (affinity == null || !affinity.equals(rootAffinity)) { pw.print(" root="); pw.println(rootAffinity); } else { pw.println(); } } if (voiceSession != null || voiceInteractor != null) { pw.print(prefix); pw.print("VOICE: session=0x"); pw.print(Integer.toHexString(System.identityHashCode(voiceSession))); pw.print(" interactor=0x"); pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor))); } if (intent != null) { StringBuilder sb = new StringBuilder(128); sb.append(prefix); sb.append("intent={"); intent.toShortString(sb, false, true, false, true); sb.append('}'); pw.println(sb.toString()); } if (affinityIntent != null) { StringBuilder sb = new StringBuilder(128); sb.append(prefix); sb.append("affinityIntent={"); affinityIntent.toShortString(sb, false, true, false, true); sb.append('}'); pw.println(sb.toString()); } if (origActivity != null) { pw.print(prefix); pw.print("origActivity="); pw.println(origActivity.flattenToShortString()); } if (realActivity != null) { pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); } if (autoRemoveRecents || isPersistable || taskType != 0 || mTaskToReturnTo != 0 || numFullscreen != 0) { pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents); pw.print(" isPersistable="); pw.print(isPersistable); pw.print(" numFullscreen="); pw.print(numFullscreen); pw.print(" taskType="); pw.print(taskType); pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo); } if (rootWasReset || mNeverRelinquishIdentity || mReuseTask || mLockTaskAuth != LOCK_TASK_AUTH_PINNABLE) { pw.print(prefix); pw.print("rootWasReset="); pw.print(rootWasReset); pw.print(" mNeverRelinquishIdentity="); pw.print(mNeverRelinquishIdentity); pw.print(" mReuseTask="); pw.print(mReuseTask); pw.print(" mLockTaskAuth="); pw.println(lockTaskAuthToString()); } if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != INVALID_TASK_ID || mPrevAffiliate != null || mNextAffiliateTaskId != INVALID_TASK_ID || mNextAffiliate != null) { pw.print(prefix); pw.print("affiliation="); pw.print(mAffiliatedTaskId); pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId); pw.print(" ("); if (mPrevAffiliate == null) { pw.print("null"); } else { pw.print(Integer.toHexString(System.identityHashCode(mPrevAffiliate))); } pw.print(") nextAffiliation="); pw.print(mNextAffiliateTaskId); pw.print(" ("); if (mNextAffiliate == null) { pw.print("null"); } else { pw.print(Integer.toHexString(System.identityHashCode(mNextAffiliate))); } pw.println(")"); } pw.print(prefix); pw.print("Activities="); pw.println(mActivities); if (!askedCompatMode || !inRecents || !isAvailable) { pw.print(prefix); pw.print("askedCompatMode="); pw.print(askedCompatMode); pw.print(" inRecents="); pw.print(inRecents); pw.print(" isAvailable="); pw.println(isAvailable); } pw.print(prefix); pw.print("lastThumbnail="); pw.print(mLastThumbnail); pw.print(" lastThumbnailFile="); pw.println(mLastThumbnailFile); if (lastDescription != null) { pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription); } if (stack != null) { pw.print(prefix); pw.print("stackId="); pw.println(stack.mStackId); } pw.print(prefix); pw.print("hasBeenVisible="); pw.print(hasBeenVisible); pw.print(" mResizeable="); pw.print(mResizeable); pw.print(" firstActiveTime="); pw.print(lastActiveTime); pw.print(" lastActiveTime="); pw.print(lastActiveTime); pw.print(" (inactive for "); pw.print((getInactiveDuration()/1000)); pw.println("s)"); } @Override public String toString() { StringBuilder sb = new StringBuilder(128); if (stringName != null) { sb.append(stringName); sb.append(" U="); sb.append(userId); sb.append(" sz="); sb.append(mActivities.size()); sb.append('}'); return sb.toString(); } sb.append("TaskRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" #"); sb.append(taskId); if (affinity != null) { sb.append(" A="); sb.append(affinity); } else if (intent != null) { sb.append(" I="); sb.append(intent.getComponent().flattenToShortString()); } else if (affinityIntent != null) { sb.append(" aI="); sb.append(affinityIntent.getComponent().flattenToShortString()); } else { sb.append(" ??"); } stringName = sb.toString(); return toString(); } }