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