Home | History | Annotate | Download | only in app
      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 {
     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      */
    104     public LocalActivityManager(Activity parent, boolean singleMode) {
    105         mActivityThread = ActivityThread.currentActivityThread();
    106         mParent = parent;
    107         mSingleMode = singleMode;
    108     }
    109 
    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 
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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