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