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