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