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