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