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