/**
   * Retrieve the state of all activities known by the group. For activities that have previously
   * run and are now stopped or finished, the last saved state is used. For the current running
   * activity, its {@link Activity#onSaveInstanceState} is called to retrieve its current state.
   *
   * @return a Bundle holding the newly created state of all known activities
   * @see #dispatchCreate
   */
  public Bundle saveInstanceState() {
    Bundle state = null;

    // FIXME: child activities will freeze as part of onPaused. Do we
    // need to do this here?
    final int N = mActivityArray.size();
    for (int i = 0; i < N; i++) {
      final LocalActivityRecord r = mActivityArray.get(i);
      if (state == null) {
        state = new Bundle();
      }
      if ((r.instanceState != null || r.curState == RESUMED) && r.activity != null) {
        // We need to save the state now, if we don't currently
        // already have it or the activity is currently resumed.
        final Bundle childState = new Bundle();
        r.activity.performSaveInstanceState(childState);
        r.instanceState = childState;
      }
      if (r.instanceState != null) {
        state.putBundle(r.id, r.instanceState);
      }
    }

    return state;
  }
 private Window performDestroy(LocalActivityRecord r, boolean finish) {
   Window win;
   win = r.window;
   if (r.curState == RESUMED && !finish) {
     performPause(r, finish);
   }
   if (localLOGV) Log.v(TAG, r.id + ": destroying");
   mActivityThread.performDestroyActivity(r, finish);
   r.activity = null;
   r.window = null;
   if (finish) {
     r.instanceState = null;
   }
   r.curState = DESTROYED;
   return win;
 }
 private void performPause(LocalActivityRecord r, boolean finishing) {
   boolean needState = r.instanceState == null;
   Bundle instanceState = mActivityThread.performPauseActivity(r, finishing, needState);
   if (needState) {
     r.instanceState = instanceState;
   }
 }
  /**
   * Restore a state that was previously returned by {@link #saveInstanceState}. This adds to the
   * activity group information about all activity IDs that had previously been saved, even if they
   * have not been started yet, so if the user later navigates to them the correct state will be
   * restored.
   *
   * <p>Note: This does <b>not</b> change the current running activity, or start whatever activity
   * was previously running when the state was saved. That is up to the client to do, in whatever
   * way it thinks is best.
   *
   * @param state a previously saved state; does nothing if this is null
   * @see #saveInstanceState
   */
  public void dispatchCreate(Bundle state) {
    if (state != null) {
      for (String id : state.keySet()) {
        try {
          final Bundle astate = state.getBundle(id);
          LocalActivityRecord r = mActivities.get(id);
          if (r != null) {
            r.instanceState = astate;
          } else {
            r = new LocalActivityRecord(id, null);
            r.instanceState = astate;
            mActivities.put(id, r);
            mActivityArray.add(r);
          }
        } catch (Exception e) {
          // Recover from -all- app errors.
          Log.e(TAG, "Exception thrown when restoring LocalActivityManager state", e);
        }
      }
    }

    mCurState = CREATED;
  }
  /**
   * Start a new activity running in the group. Every activity you start must have a unique string
   * ID associated with it -- this is used to keep track of the activity, so that if you later call
   * startActivity() again on it the same activity object will be retained.
   *
   * <p>When there had previously been an activity started under this id, it may either be destroyed
   * and a new one started, or the current one re-used, based on these conditions, in order:
   *
   * <ul>
   *   <li>If the Intent maps to a different activity component than is currently running, the
   *       current activity is finished and a new one started.
   *   <li>If the current activity uses a non-multiple launch mode (such as singleTop), or the
   *       Intent has the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag set, then the current
   *       activity will remain running and its {@link Activity#onNewIntent(Intent)
   *       Activity.onNewIntent()} method called.
   *   <li>If the new Intent is the same (excluding extras) as the previous one, and the new Intent
   *       does not have the {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} set, then the current activity
   *       will remain running as-is.
   *   <li>Otherwise, the current activity will be finished and a new one started.
   * </ul>
   *
   * <p>If the given Intent can not be resolved to an available Activity, this method throws {@link
   * android.content.ActivityNotFoundException}.
   *
   * <p>Warning: There is an issue where, if the Intent does not include an explicit component, we
   * can restore the state for a different activity class than was previously running when the state
   * was saved (if the set of available activities changes between those points).
   *
   * @param id Unique identifier of the activity to be started
   * @param intent The Intent describing the activity to be started
   * @return Returns the window of the activity. The caller needs to take care of adding this window
   *     to a view hierarchy, and likewise dealing with removing the old window if the activity has
   *     changed.
   * @throws android.content.ActivityNotFoundException
   */
  public Window startActivity(String id, Intent intent) {
    if (mCurState == INITIALIZING) {
      throw new IllegalStateException(
          "Activities can't be added until the containing group has been created.");
    }

    boolean adding = false;
    boolean sameIntent = false;

    ActivityInfo aInfo = null;

    // Already have information about the new activity id?
    LocalActivityRecord r = mActivities.get(id);
    if (r == null) {
      // Need to create it...
      r = new LocalActivityRecord(id, intent);
      adding = true;
    } else if (r.intent != null) {
      sameIntent = r.intent.filterEquals(intent);
      if (sameIntent) {
        // We are starting the same activity.
        aInfo = r.activityInfo;
      }
    }
    if (aInfo == null) {
      aInfo = mActivityThread.resolveActivityInfo(intent);
    }

    // Pause the currently running activity if there is one and only a single
    // activity is allowed to be running at a time.
    if (mSingleMode) {
      LocalActivityRecord old = mResumed;

      // If there was a previous activity, and it is not the current
      // activity, we need to stop it.
      if (old != null && old != r && mCurState == RESUMED) {
        moveToState(old, STARTED);
      }
    }

    if (adding) {
      // It's a brand new world.
      mActivities.put(id, r);
      mActivityArray.add(r);
    } else if (r.activityInfo != null) {
      // If the new activity is the same as the current one, then
      // we may be able to reuse it.
      if (aInfo == r.activityInfo
          || (aInfo.name.equals(r.activityInfo.name)
              && aInfo.packageName.equals(r.activityInfo.packageName))) {
        if (aInfo.launchMode != ActivityInfo.LAUNCH_MULTIPLE
            || (intent.getFlags() & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) {
          // The activity wants onNewIntent() called.
          ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
          intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
          if (localLOGV) Log.v(TAG, r.id + ": new intent");
          mActivityThread.performNewIntents(r, intents);
          r.intent = intent;
          moveToState(r, mCurState);
          if (mSingleMode) {
            mResumed = r;
          }
          return r.window;
        }
        if (sameIntent && (intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_TOP) == 0) {
          // We are showing the same thing, so this activity is
          // just resumed and stays as-is.
          r.intent = intent;
          moveToState(r, mCurState);
          if (mSingleMode) {
            mResumed = r;
          }
          return r.window;
        }
      }

      // The new activity is different than the current one, or it
      // is a multiple launch activity, so we need to destroy what
      // is currently there.
      performDestroy(r, true);
    }

    r.intent = intent;
    r.curState = INITIALIZING;
    r.activityInfo = aInfo;

    moveToState(r, mCurState);

    // When in single mode keep track of the current activity
    if (mSingleMode) {
      mResumed = r;
    }
    return r.window;
  }
  private void moveToState(LocalActivityRecord r, int desiredState) {
    if (r.curState == RESTORED || r.curState == DESTROYED) {
      // startActivity() has not yet been called, so nothing to do.
      return;
    }

    if (r.curState == INITIALIZING) {
      // Get the lastNonConfigurationInstance for the activity
      HashMap<String, Object> lastNonConfigurationInstances =
          mParent.getLastNonConfigurationChildInstances();
      Object instanceObj = null;
      if (lastNonConfigurationInstances != null) {
        instanceObj = lastNonConfigurationInstances.get(r.id);
      }
      Activity.NonConfigurationInstances instance = null;
      if (instanceObj != null) {
        instance = new Activity.NonConfigurationInstances();
        instance.activity = instanceObj;
      }

      // We need to have always created the activity.
      if (localLOGV) Log.v(TAG, r.id + ": starting " + r.intent);
      if (r.activityInfo == null) {
        r.activityInfo = mActivityThread.resolveActivityInfo(r.intent);
      }
      r.activity =
          mActivityThread.startActivityNow(
              mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance);
      if (r.activity == null) {
        return;
      }
      r.window = r.activity.getWindow();
      r.instanceState = null;
      r.curState = STARTED;

      if (desiredState == RESUMED) {
        if (localLOGV) Log.v(TAG, r.id + ": resuming");
        mActivityThread.performResumeActivity(r, true);
        r.curState = RESUMED;
      }

      // Don't do anything more here.  There is an important case:
      // if this is being done as part of onCreate() of the group, then
      // the launching of the activity gets its state a little ahead
      // of our own (it is now STARTED, while we are only CREATED).
      // If we just leave things as-is, we'll deal with it as the
      // group's state catches up.
      return;
    }

    switch (r.curState) {
      case CREATED:
        if (desiredState == STARTED) {
          if (localLOGV) Log.v(TAG, r.id + ": restarting");
          mActivityThread.performRestartActivity(r);
          r.curState = STARTED;
        }
        if (desiredState == RESUMED) {
          if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming");
          mActivityThread.performRestartActivity(r);
          mActivityThread.performResumeActivity(r, true);
          r.curState = RESUMED;
        }
        return;

      case STARTED:
        if (desiredState == RESUMED) {
          // Need to resume it...
          if (localLOGV) Log.v(TAG, r.id + ": resuming");
          mActivityThread.performResumeActivity(r, true);
          r.instanceState = null;
          r.curState = RESUMED;
        }
        if (desiredState == CREATED) {
          if (localLOGV) Log.v(TAG, r.id + ": stopping");
          mActivityThread.performStopActivity(r, false);
          r.curState = CREATED;
        }
        return;

      case RESUMED:
        if (desiredState == STARTED) {
          if (localLOGV) Log.v(TAG, r.id + ": pausing");
          performPause(r, mFinishing);
          r.curState = STARTED;
        }
        if (desiredState == CREATED) {
          if (localLOGV) Log.v(TAG, r.id + ": pausing");
          performPause(r, mFinishing);
          if (localLOGV) Log.v(TAG, r.id + ": stopping");
          mActivityThread.performStopActivity(r, false);
          r.curState = CREATED;
        }
        return;
    }
  }