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