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