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