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