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