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 17 package android.app; 18 19 import android.app.ActivityThread.ActivityClientRecord; 20 import android.app.servertransaction.PendingTransactionActions; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.Intent; 23 import android.content.pm.ActivityInfo; 24 import android.os.Binder; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.util.Log; 28 import android.view.Window; 29 30 import com.android.internal.content.ReferrerIntent; 31 32 import java.util.ArrayList; 33 import java.util.HashMap; 34 import java.util.Map; 35 36 /** 37 * <p>Helper class for managing multiple running embedded activities in the same 38 * process. This class is not normally used directly, but rather created for 39 * you as part of the {@link android.app.ActivityGroup} implementation. 40 * 41 * @see ActivityGroup 42 * 43 * @deprecated Use the new {@link Fragment} and {@link FragmentManager} APIs 44 * instead; these are also 45 * available on older platforms through the Android compatibility package. 46 */ 47 @Deprecated 48 public class LocalActivityManager { 49 private static final String TAG = "LocalActivityManager"; 50 private static final boolean localLOGV = false; 51 52 // TODO(b/127877792): try to remove this and use {@code ActivityClientRecord} instead. 53 // Internal token for an Activity being managed by LocalActivityManager. 54 private static class LocalActivityRecord extends Binder { LocalActivityRecord(String _id, Intent _intent)55 LocalActivityRecord(String _id, Intent _intent) { 56 id = _id; 57 intent = _intent; 58 } 59 60 final String id; // Unique name of this record. 61 Intent intent; // Which activity to run here. 62 ActivityInfo activityInfo; // Package manager info about activity. 63 Activity activity; // Currently instantiated activity. 64 Window window; // Activity's top-level window. 65 Bundle instanceState; // Last retrieved freeze state. 66 int curState = RESTORED; // Current state the activity is in. 67 } 68 69 static final int RESTORED = 0; // State restored, but no startActivity(). 70 static final int INITIALIZING = 1; // Ready to launch (after startActivity()). 71 static final int CREATED = 2; // Created, not started or resumed. 72 static final int STARTED = 3; // Created and started, not resumed. 73 static final int RESUMED = 4; // Created started and resumed. 74 static final int DESTROYED = 5; // No longer with us. 75 76 /** Thread our activities are running in. */ 77 private final ActivityThread mActivityThread; 78 /** The containing activity that owns the activities we create. */ 79 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q, 80 publicAlternatives = "Use {@code androidx.fragment.app.Fragment} and " 81 + "{@code androidx.fragment.app.FragmentManager} instead") 82 private final Activity mParent; 83 84 /** The activity that is currently resumed. */ 85 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q, 86 publicAlternatives = "Use {@code androidx.fragment.app.Fragment} and " 87 + "{@code androidx.fragment.app.FragmentManager} instead") 88 private LocalActivityRecord mResumed; 89 /** id -> record of all known activities. */ 90 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q, 91 publicAlternatives = "Use {@code androidx.fragment.app.Fragment} and " 92 + "{@code androidx.fragment.app.FragmentManager} instead") 93 private final Map<String, LocalActivityRecord> mActivities 94 = new HashMap<String, LocalActivityRecord>(); 95 /** array of all known activities for easy iterating. */ 96 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q, 97 publicAlternatives = "Use {@code androidx.fragment.app.Fragment} and " 98 + "{@code androidx.fragment.app.FragmentManager} instead") 99 private final ArrayList<LocalActivityRecord> mActivityArray 100 = new ArrayList<LocalActivityRecord>(); 101 102 /** True if only one activity can be resumed at a time */ 103 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q, 104 publicAlternatives = "Use {@code androidx.fragment.app.Fragment} and " 105 + "{@code androidx.fragment.app.FragmentManager} instead") 106 private boolean mSingleMode; 107 108 /** Set to true once we find out the container is finishing. */ 109 private boolean mFinishing; 110 111 /** Current state the owner (ActivityGroup) is in */ 112 private int mCurState = INITIALIZING; 113 114 /** String ids of running activities starting with least recently used. */ 115 // TODO: put back in stopping of activities. 116 //private List<LocalActivityRecord> mLRU = new ArrayList(); 117 118 /** 119 * Create a new LocalActivityManager for holding activities running within 120 * the given <var>parent</var>. 121 * 122 * @param parent the host of the embedded activities 123 * @param singleMode True if the LocalActivityManger should keep a maximum 124 * of one activity resumed 125 */ LocalActivityManager(Activity parent, boolean singleMode)126 public LocalActivityManager(Activity parent, boolean singleMode) { 127 mActivityThread = ActivityThread.currentActivityThread(); 128 mParent = parent; 129 mSingleMode = singleMode; 130 } 131 132 @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q, 133 publicAlternatives = "Use {@code androidx.fragment.app.Fragment} and " 134 + "{@code androidx.fragment.app.FragmentManager} instead") moveToState(LocalActivityRecord r, int desiredState)135 private void moveToState(LocalActivityRecord r, int desiredState) { 136 if (r.curState == RESTORED || r.curState == DESTROYED) { 137 // startActivity() has not yet been called, so nothing to do. 138 return; 139 } 140 141 if (r.curState == INITIALIZING) { 142 // Get the lastNonConfigurationInstance for the activity 143 HashMap<String, Object> lastNonConfigurationInstances = 144 mParent.getLastNonConfigurationChildInstances(); 145 Object instanceObj = null; 146 if (lastNonConfigurationInstances != null) { 147 instanceObj = lastNonConfigurationInstances.get(r.id); 148 } 149 Activity.NonConfigurationInstances instance = null; 150 if (instanceObj != null) { 151 instance = new Activity.NonConfigurationInstances(); 152 instance.activity = instanceObj; 153 } 154 155 // We need to have always created the activity. 156 if (localLOGV) Log.v(TAG, r.id + ": starting " + r.intent); 157 if (r.activityInfo == null) { 158 r.activityInfo = mActivityThread.resolveActivityInfo(r.intent); 159 } 160 r.activity = mActivityThread.startActivityNow( 161 mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance, r, r); 162 if (r.activity == null) { 163 return; 164 } 165 r.window = r.activity.getWindow(); 166 r.instanceState = null; 167 168 final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); 169 final PendingTransactionActions pendingActions; 170 171 if (!r.activity.mFinished) { 172 // This matches pending actions set in ActivityThread#handleLaunchActivity 173 pendingActions = new PendingTransactionActions(); 174 pendingActions.setOldState(clientRecord.state); 175 pendingActions.setRestoreInstanceState(true); 176 pendingActions.setCallOnPostCreate(true); 177 } else { 178 pendingActions = null; 179 } 180 181 mActivityThread.handleStartActivity(clientRecord, pendingActions, 182 null /* activityOptions */); 183 r.curState = STARTED; 184 185 if (desiredState == RESUMED) { 186 if (localLOGV) Log.v(TAG, r.id + ": resuming"); 187 mActivityThread.performResumeActivity(clientRecord, true, 188 "moveToState-INITIALIZING"); 189 r.curState = RESUMED; 190 } 191 192 // Don't do anything more here. There is an important case: 193 // if this is being done as part of onCreate() of the group, then 194 // the launching of the activity gets its state a little ahead 195 // of our own (it is now STARTED, while we are only CREATED). 196 // If we just leave things as-is, we'll deal with it as the 197 // group's state catches up. 198 return; 199 } 200 201 final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); 202 if (clientRecord == null) { 203 Log.w(TAG, "Can't get activity record for " + r.id); 204 return; 205 } 206 207 switch (r.curState) { 208 case CREATED: 209 if (desiredState == STARTED) { 210 if (localLOGV) Log.v(TAG, r.id + ": restarting"); 211 mActivityThread.performRestartActivity(clientRecord, true /* start */); 212 r.curState = STARTED; 213 } 214 if (desiredState == RESUMED) { 215 if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming"); 216 mActivityThread.performRestartActivity(clientRecord, true /* start */); 217 mActivityThread.performResumeActivity(clientRecord, true, 218 "moveToState-CREATED"); 219 r.curState = RESUMED; 220 } 221 return; 222 223 case STARTED: 224 if (desiredState == RESUMED) { 225 // Need to resume it... 226 if (localLOGV) Log.v(TAG, r.id + ": resuming"); 227 mActivityThread.performResumeActivity(clientRecord, true, 228 "moveToState-STARTED"); 229 r.instanceState = null; 230 r.curState = RESUMED; 231 } 232 if (desiredState == CREATED) { 233 if (localLOGV) Log.v(TAG, r.id + ": stopping"); 234 mActivityThread.performStopActivity(r, false, "moveToState-STARTED"); 235 r.curState = CREATED; 236 } 237 return; 238 239 case RESUMED: 240 if (desiredState == STARTED) { 241 if (localLOGV) Log.v(TAG, r.id + ": pausing"); 242 performPause(r, mFinishing); 243 r.curState = STARTED; 244 } 245 if (desiredState == CREATED) { 246 if (localLOGV) Log.v(TAG, r.id + ": pausing"); 247 performPause(r, mFinishing); 248 if (localLOGV) Log.v(TAG, r.id + ": stopping"); 249 mActivityThread.performStopActivity(r, false, "moveToState-RESUMED"); 250 r.curState = CREATED; 251 } 252 return; 253 } 254 } 255 performPause(LocalActivityRecord r, boolean finishing)256 private void performPause(LocalActivityRecord r, boolean finishing) { 257 final boolean needState = r.instanceState == null; 258 final Bundle instanceState = mActivityThread.performPauseActivity(r, finishing, 259 "performPause", null /* pendingActions */); 260 if (needState) { 261 r.instanceState = instanceState; 262 } 263 } 264 265 /** 266 * Start a new activity running in the group. Every activity you start 267 * must have a unique string ID associated with it -- this is used to keep 268 * track of the activity, so that if you later call startActivity() again 269 * on it the same activity object will be retained. 270 * 271 * <p>When there had previously been an activity started under this id, 272 * it may either be destroyed and a new one started, or the current 273 * one re-used, based on these conditions, in order:</p> 274 * 275 * <ul> 276 * <li> If the Intent maps to a different activity component than is 277 * currently running, the current activity is finished and a new one 278 * started. 279 * <li> If the current activity uses a non-multiple launch mode (such 280 * as singleTop), or the Intent has the 281 * {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag set, then the current 282 * activity will remain running and its 283 * {@link Activity#onNewIntent(Intent) Activity.onNewIntent()} method 284 * called. 285 * <li> If the new Intent is the same (excluding extras) as the previous 286 * one, and the new Intent does not have the 287 * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} set, then the current activity 288 * will remain running as-is. 289 * <li> Otherwise, the current activity will be finished and a new 290 * one started. 291 * </ul> 292 * 293 * <p>If the given Intent can not be resolved to an available Activity, 294 * this method throws {@link android.content.ActivityNotFoundException}. 295 * 296 * <p>Warning: There is an issue where, if the Intent does not 297 * include an explicit component, we can restore the state for a different 298 * activity class than was previously running when the state was saved (if 299 * the set of available activities changes between those points). 300 * 301 * @param id Unique identifier of the activity to be started 302 * @param intent The Intent describing the activity to be started 303 * 304 * @return Returns the window of the activity. The caller needs to take 305 * care of adding this window to a view hierarchy, and likewise dealing 306 * with removing the old window if the activity has changed. 307 * 308 * @throws android.content.ActivityNotFoundException 309 */ startActivity(String id, Intent intent)310 public Window startActivity(String id, Intent intent) { 311 if (mCurState == INITIALIZING) { 312 throw new IllegalStateException( 313 "Activities can't be added until the containing group has been created."); 314 } 315 316 boolean adding = false; 317 boolean sameIntent = false; 318 319 ActivityInfo aInfo = null; 320 321 // Already have information about the new activity id? 322 LocalActivityRecord r = mActivities.get(id); 323 if (r == null) { 324 // Need to create it... 325 r = new LocalActivityRecord(id, intent); 326 adding = true; 327 } else if (r.intent != null) { 328 sameIntent = r.intent.filterEquals(intent); 329 if (sameIntent) { 330 // We are starting the same activity. 331 aInfo = r.activityInfo; 332 } 333 } 334 if (aInfo == null) { 335 aInfo = mActivityThread.resolveActivityInfo(intent); 336 } 337 338 // Pause the currently running activity if there is one and only a single 339 // activity is allowed to be running at a time. 340 if (mSingleMode) { 341 LocalActivityRecord old = mResumed; 342 343 // If there was a previous activity, and it is not the current 344 // activity, we need to stop it. 345 if (old != null && old != r && mCurState == RESUMED) { 346 moveToState(old, STARTED); 347 } 348 } 349 350 if (adding) { 351 // It's a brand new world. 352 mActivities.put(id, r); 353 mActivityArray.add(r); 354 } else if (r.activityInfo != null) { 355 // If the new activity is the same as the current one, then 356 // we may be able to reuse it. 357 if (aInfo == r.activityInfo || 358 (aInfo.name.equals(r.activityInfo.name) && 359 aInfo.packageName.equals(r.activityInfo.packageName))) { 360 if (aInfo.launchMode != ActivityInfo.LAUNCH_MULTIPLE || 361 (intent.getFlags()&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) { 362 // The activity wants onNewIntent() called. 363 ArrayList<ReferrerIntent> intents = new ArrayList<>(1); 364 intents.add(new ReferrerIntent(intent, mParent.getPackageName())); 365 if (localLOGV) Log.v(TAG, r.id + ": new intent"); 366 final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); 367 mActivityThread.handleNewIntent(clientRecord, intents); 368 r.intent = intent; 369 moveToState(r, mCurState); 370 if (mSingleMode) { 371 mResumed = r; 372 } 373 return r.window; 374 } 375 if (sameIntent && 376 (intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_TOP) == 0) { 377 // We are showing the same thing, so this activity is 378 // just resumed and stays as-is. 379 r.intent = intent; 380 moveToState(r, mCurState); 381 if (mSingleMode) { 382 mResumed = r; 383 } 384 return r.window; 385 } 386 } 387 388 // The new activity is different than the current one, or it 389 // is a multiple launch activity, so we need to destroy what 390 // is currently there. 391 performDestroy(r, true); 392 } 393 394 r.intent = intent; 395 r.curState = INITIALIZING; 396 r.activityInfo = aInfo; 397 398 moveToState(r, mCurState); 399 400 // When in single mode keep track of the current activity 401 if (mSingleMode) { 402 mResumed = r; 403 } 404 return r.window; 405 } 406 performDestroy(LocalActivityRecord r, boolean finish)407 private Window performDestroy(LocalActivityRecord r, boolean finish) { 408 Window win; 409 win = r.window; 410 if (r.curState == RESUMED && !finish) { 411 performPause(r, finish); 412 } 413 if (localLOGV) Log.v(TAG, r.id + ": destroying"); 414 final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); 415 if (clientRecord != null) { 416 mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */, 417 false /* getNonConfigInstance */, "LocalActivityManager::performDestroy"); 418 } 419 r.activity = null; 420 r.window = null; 421 if (finish) { 422 r.instanceState = null; 423 } 424 r.curState = DESTROYED; 425 return win; 426 } 427 428 /** 429 * Destroy the activity associated with a particular id. This activity 430 * will go through the normal lifecycle events and fine onDestroy(), and 431 * then the id removed from the group. 432 * 433 * @param id Unique identifier of the activity to be destroyed 434 * @param finish If true, this activity will be finished, so its id and 435 * all state are removed from the group. 436 * 437 * @return Returns the window that was used to display the activity, or 438 * null if there was none. 439 */ destroyActivity(String id, boolean finish)440 public Window destroyActivity(String id, boolean finish) { 441 LocalActivityRecord r = mActivities.get(id); 442 Window win = null; 443 if (r != null) { 444 win = performDestroy(r, finish); 445 if (finish) { 446 mActivities.remove(id); 447 mActivityArray.remove(r); 448 } 449 } 450 return win; 451 } 452 453 /** 454 * Retrieve the Activity that is currently running. 455 * 456 * @return the currently running (resumed) Activity, or null if there is 457 * not one 458 * 459 * @see #startActivity 460 * @see #getCurrentId 461 */ getCurrentActivity()462 public Activity getCurrentActivity() { 463 return mResumed != null ? mResumed.activity : null; 464 } 465 466 /** 467 * Retrieve the ID of the activity that is currently running. 468 * 469 * @return the ID of the currently running (resumed) Activity, or null if 470 * there is not one 471 * 472 * @see #startActivity 473 * @see #getCurrentActivity 474 */ getCurrentId()475 public String getCurrentId() { 476 return mResumed != null ? mResumed.id : null; 477 } 478 479 /** 480 * Return the Activity object associated with a string ID. 481 * 482 * @see #startActivity 483 * 484 * @return the associated Activity object, or null if the id is unknown or 485 * its activity is not currently instantiated 486 */ getActivity(String id)487 public Activity getActivity(String id) { 488 LocalActivityRecord r = mActivities.get(id); 489 return r != null ? r.activity : null; 490 } 491 492 /** 493 * Restore a state that was previously returned by {@link #saveInstanceState}. This 494 * adds to the activity group information about all activity IDs that had 495 * previously been saved, even if they have not been started yet, so if the 496 * user later navigates to them the correct state will be restored. 497 * 498 * <p>Note: This does <b>not</b> change the current running activity, or 499 * start whatever activity was previously running when the state was saved. 500 * That is up to the client to do, in whatever way it thinks is best. 501 * 502 * @param state a previously saved state; does nothing if this is null 503 * 504 * @see #saveInstanceState 505 */ dispatchCreate(Bundle state)506 public void dispatchCreate(Bundle state) { 507 if (state != null) { 508 for (String id : state.keySet()) { 509 try { 510 final Bundle astate = state.getBundle(id); 511 LocalActivityRecord r = mActivities.get(id); 512 if (r != null) { 513 r.instanceState = astate; 514 } else { 515 r = new LocalActivityRecord(id, null); 516 r.instanceState = astate; 517 mActivities.put(id, r); 518 mActivityArray.add(r); 519 } 520 } catch (Exception e) { 521 // Recover from -all- app errors. 522 Log.e(TAG, "Exception thrown when restoring LocalActivityManager state", e); 523 } 524 } 525 } 526 527 mCurState = CREATED; 528 } 529 530 /** 531 * Retrieve the state of all activities known by the group. For 532 * activities that have previously run and are now stopped or finished, the 533 * last saved state is used. For the current running activity, its 534 * {@link Activity#onSaveInstanceState} is called to retrieve its current state. 535 * 536 * @return a Bundle holding the newly created state of all known activities 537 * 538 * @see #dispatchCreate 539 */ saveInstanceState()540 public Bundle saveInstanceState() { 541 Bundle state = null; 542 543 // FIXME: child activities will freeze as part of onPaused. Do we 544 // need to do this here? 545 final int N = mActivityArray.size(); 546 for (int i=0; i<N; i++) { 547 final LocalActivityRecord r = mActivityArray.get(i); 548 if (state == null) { 549 state = new Bundle(); 550 } 551 if ((r.instanceState != null || r.curState == RESUMED) 552 && r.activity != null) { 553 // We need to save the state now, if we don't currently 554 // already have it or the activity is currently resumed. 555 final Bundle childState = new Bundle(); 556 r.activity.performSaveInstanceState(childState); 557 r.instanceState = childState; 558 } 559 if (r.instanceState != null) { 560 state.putBundle(r.id, r.instanceState); 561 } 562 } 563 564 return state; 565 } 566 567 /** 568 * Called by the container activity in its {@link Activity#onResume} so 569 * that LocalActivityManager can perform the corresponding action on the 570 * activities it holds. 571 * 572 * @see Activity#onResume 573 */ dispatchResume()574 public void dispatchResume() { 575 mCurState = RESUMED; 576 if (mSingleMode) { 577 if (mResumed != null) { 578 moveToState(mResumed, RESUMED); 579 } 580 } else { 581 final int N = mActivityArray.size(); 582 for (int i=0; i<N; i++) { 583 moveToState(mActivityArray.get(i), RESUMED); 584 } 585 } 586 } 587 588 /** 589 * Called by the container activity in its {@link Activity#onPause} so 590 * that LocalActivityManager can perform the corresponding action on the 591 * activities it holds. 592 * 593 * @param finishing set to true if the parent activity has been finished; 594 * this can be determined by calling 595 * Activity.isFinishing() 596 * 597 * @see Activity#onPause 598 * @see Activity#isFinishing 599 */ dispatchPause(boolean finishing)600 public void dispatchPause(boolean finishing) { 601 if (finishing) { 602 mFinishing = true; 603 } 604 mCurState = STARTED; 605 if (mSingleMode) { 606 if (mResumed != null) { 607 moveToState(mResumed, STARTED); 608 } 609 } else { 610 final int N = mActivityArray.size(); 611 for (int i=0; i<N; i++) { 612 LocalActivityRecord r = mActivityArray.get(i); 613 if (r.curState == RESUMED) { 614 moveToState(r, STARTED); 615 } 616 } 617 } 618 } 619 620 /** 621 * Called by the container activity in its {@link Activity#onStop} so 622 * that LocalActivityManager can perform the corresponding action on the 623 * activities it holds. 624 * 625 * @see Activity#onStop 626 */ dispatchStop()627 public void dispatchStop() { 628 mCurState = CREATED; 629 final int N = mActivityArray.size(); 630 for (int i=0; i<N; i++) { 631 LocalActivityRecord r = mActivityArray.get(i); 632 moveToState(r, CREATED); 633 } 634 } 635 636 /** 637 * Call onRetainNonConfigurationInstance on each child activity and store the 638 * results in a HashMap by id. Only construct the HashMap if there is a non-null 639 * object to store. Note that this does not support nested ActivityGroups. 640 * 641 * {@hide} 642 */ dispatchRetainNonConfigurationInstance()643 public HashMap<String,Object> dispatchRetainNonConfigurationInstance() { 644 HashMap<String,Object> instanceMap = null; 645 646 final int N = mActivityArray.size(); 647 for (int i=0; i<N; i++) { 648 LocalActivityRecord r = mActivityArray.get(i); 649 if ((r != null) && (r.activity != null)) { 650 Object instance = r.activity.onRetainNonConfigurationInstance(); 651 if (instance != null) { 652 if (instanceMap == null) { 653 instanceMap = new HashMap<String,Object>(); 654 } 655 instanceMap.put(r.id, instance); 656 } 657 } 658 } 659 return instanceMap; 660 } 661 662 /** 663 * Remove all activities from this LocalActivityManager, performing an 664 * {@link Activity#onDestroy} on any that are currently instantiated. 665 */ removeAllActivities()666 public void removeAllActivities() { 667 dispatchDestroy(true); 668 } 669 670 /** 671 * Called by the container activity in its {@link Activity#onDestroy} so 672 * that LocalActivityManager can perform the corresponding action on the 673 * activities it holds. 674 * 675 * @see Activity#onDestroy 676 */ dispatchDestroy(boolean finishing)677 public void dispatchDestroy(boolean finishing) { 678 final int N = mActivityArray.size(); 679 for (int i=0; i<N; i++) { 680 LocalActivityRecord r = mActivityArray.get(i); 681 if (localLOGV) Log.v(TAG, r.id + ": destroying"); 682 final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); 683 if (clientRecord == null) { 684 if (localLOGV) Log.v(TAG, r.id + ": no corresponding record"); 685 continue; 686 } 687 mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */, 688 false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy"); 689 } 690 mActivities.clear(); 691 mActivityArray.clear(); 692 } 693 } 694