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