[go: nahoru, domu]

1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app;
18
19import android.content.Intent;
20import android.content.pm.ActivityInfo;
21import android.os.Binder;
22import android.os.Bundle;
23import android.util.Log;
24import android.view.Window;
25import com.android.internal.content.ReferrerIntent;
26
27import java.util.ArrayList;
28import java.util.HashMap;
29import java.util.Map;
30
31/**
32 * <p>Helper class for managing multiple running embedded activities in the same
33 * 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
37 *
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.
41 */
42@Deprecated
43public class LocalActivityManager {
44    private static final String TAG = "LocalActivityManager";
45    private static final boolean localLOGV = false;
46
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
119            HashMap<String, Object> lastNonConfigurationInstances =
120                    mParent.getLastNonConfigurationChildInstances();
121            Object instanceObj = null;
122            if (lastNonConfigurationInstances != null) {
123                instanceObj = lastNonConfigurationInstances.get(r.id);
124            }
125            Activity.NonConfigurationInstances instance = null;
126            if (instanceObj != null) {
127                instance = new Activity.NonConfigurationInstances();
128                instance.activity = instanceObj;
129            }
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");
147                mActivityThread.performResumeActivity(r, true, "moveToState-INITIALIZING");
148                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);
170                    mActivityThread.performResumeActivity(r, true, "moveToState-CREATED");
171                    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");
179                    mActivityThread.performResumeActivity(r, true, "moveToState-STARTED");
180                    r.instanceState = null;
181                    r.curState = RESUMED;
182                }
183                if (desiredState == CREATED) {
184                    if (localLOGV) Log.v(TAG, r.id + ": stopping");
185                    mActivityThread.performStopActivity(r, false, "moveToState-STARTED");
186                    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");
200                    mActivityThread.performStopActivity(r, false, "moveToState-RESUMED");
201                    r.curState = CREATED;
202                }
203                return;
204        }
205    }
206
207    private void performPause(LocalActivityRecord r, boolean finishing) {
208        final boolean needState = r.instanceState == null;
209        final Bundle instanceState = mActivityThread.performPauseActivity(
210                r, finishing, needState, "performPause");
211        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.
314                    ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
315                    intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
316                    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) {
358        Window win;
359        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) {
392                mActivities.remove(id);
393                mActivityArray.remove(r);
394            }
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) {
454            for (String id : state.keySet()) {
455                try {
456                    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.
468                    Log.e(TAG, "Exception thrown when restoring LocalActivityManager state", e);
469                }
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();
502                r.activity.performSaveInstanceState(childState);
503                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}
634