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