blob: 2a1e3c250a00a24f9e230e464469245553d5743f [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;
Dianne Hackborn85d558c2014-11-04 10:31:54 -080025import com.android.internal.content.ReferrerIntent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
27import java.util.ArrayList;
28import java.util.HashMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import java.util.Map;
30
31/**
Dianne Hackborn2f048832011-06-16 13:31:57 -070032 * <p>Helper class for managing multiple running embedded activities in the same
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033 * 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
Dianne Hackborn271c2fe2011-08-09 19:35:13 -070037 *
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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 */
Dianne Hackborn2f048832011-06-16 13:31:57 -070042@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043public class LocalActivityManager {
44 private static final String TAG = "LocalActivityManager";
Romain Guyd315ee92010-06-03 11:03:18 -070045 private static final boolean localLOGV = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046
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
Dianne Hackbornb4bc78b2010-05-12 18:59:50 -0700119 HashMap<String, Object> lastNonConfigurationInstances =
120 mParent.getLastNonConfigurationChildInstances();
121 Object instanceObj = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 if (lastNonConfigurationInstances != null) {
Dianne Hackbornb4bc78b2010-05-12 18:59:50 -0700123 instanceObj = lastNonConfigurationInstances.get(r.id);
124 }
125 Activity.NonConfigurationInstances instance = null;
126 if (instanceObj != null) {
127 instance = new Activity.NonConfigurationInstances();
128 instance.activity = instanceObj;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 }
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");
Wale Ogunwalecd7043e2016-02-27 17:37:46 -0800147 mActivityThread.performResumeActivity(r, true, "moveToState-INITIALIZING");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 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);
Wale Ogunwalecd7043e2016-02-27 17:37:46 -0800170 mActivityThread.performResumeActivity(r, true, "moveToState-CREATED");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 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");
Wale Ogunwalecd7043e2016-02-27 17:37:46 -0800179 mActivityThread.performResumeActivity(r, true, "moveToState-STARTED");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 r.instanceState = null;
181 r.curState = RESUMED;
182 }
183 if (desiredState == CREATED) {
184 if (localLOGV) Log.v(TAG, r.id + ": stopping");
Wale Ogunwalecd7043e2016-02-27 17:37:46 -0800185 mActivityThread.performStopActivity(r, false, "moveToState-STARTED");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 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");
Wale Ogunwalecd7043e2016-02-27 17:37:46 -0800200 mActivityThread.performStopActivity(r, false, "moveToState-RESUMED");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 r.curState = CREATED;
202 }
203 return;
204 }
205 }
206
207 private void performPause(LocalActivityRecord r, boolean finishing) {
Wale Ogunwalecd7043e2016-02-27 17:37:46 -0800208 final boolean needState = r.instanceState == null;
209 final Bundle instanceState = mActivityThread.performPauseActivity(
210 r, finishing, needState, "performPause");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 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.
Dianne Hackborn85d558c2014-11-04 10:31:54 -0800314 ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
315 intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316 if (localLOGV) Log.v(TAG, r.id + ": new intent");
317 mActivityThread.performNewIntents(r, intents);
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) {
Romain Guyd315ee92010-06-03 11:03:18 -0700358 Window win;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 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) {
Romain Guyd315ee92010-06-03 11:03:18 -0700392 mActivities.remove(id);
393 mActivityArray.remove(r);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 }
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) {
Romain Guyd315ee92010-06-03 11:03:18 -0700454 for (String id : state.keySet()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 try {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 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.
Romain Guyd315ee92010-06-03 11:03:18 -0700468 Log.e(TAG, "Exception thrown when restoring LocalActivityManager state", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 }
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();
Martin Hibdon6bcda632010-12-17 16:56:07 -0800502 r.activity.performSaveInstanceState(childState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 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}