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