Implement issue #3189564: New API to create an activity.

Change-Id: I7aef83324d653130eb3b2a148ba089d7347e6ba6
diff --git a/api/current.xml b/api/current.xml
index 24f3b75..4f9ba23 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -23228,6 +23228,17 @@
 <parameter name="exitAnim" type="int">
 </parameter>
 </method>
+<method name="recreate"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="registerForContextMenu"
  return="void"
  abstract="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6b619fb..0a2e031 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -42,6 +42,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.text.Selection;
@@ -78,6 +79,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * An activity is a single, focused thing that the user can do.  Almost all
@@ -842,8 +844,6 @@
      * @see #onPostCreate
      */
     protected void onCreate(Bundle savedInstanceState) {
-        mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
-                com.android.internal.R.styleable.Window_windowNoDisplay, false);
         if (mLastNonConfigurationInstances != null) {
             mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
         }
@@ -3509,6 +3509,22 @@
     }
 
     /**
+     * Cause this Activity to be recreated with a new instance.  This results
+     * in essentially the same flow as when the Activity is created due to
+     * a configuration change -- the current instance will go through its
+     * lifecycle to {@link #onDestroy} and a new instance then created after it.
+     */
+    public void recreate() {
+        if (mParent != null) {
+            throw new IllegalStateException("Can only be called on top-level activity");
+        }
+        if (Looper.myLooper() != mMainThread.getLooper()) {
+            throw new IllegalStateException("Must be called from main thread");
+        }
+        mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, false);
+    }
+
+    /**
      * Call this when your activity is done and should be closed.  The
      * ActivityResult is propagated back to whoever launched you via
      * onActivityResult().
@@ -4262,6 +4278,8 @@
 
     final void performCreate(Bundle icicle) {
         onCreate(icicle);
+        mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
+                com.android.internal.R.styleable.Window_windowNoDisplay, false);
         mFragments.dispatchActivityCreated();
     }
     
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c0714e3..a8f08c2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -224,6 +224,11 @@
 
         boolean startsNotResumed;
         boolean isForward;
+        int pendingConfigChanges;
+        boolean onlyLocalRequest;
+
+        View mPendingRemoveWindow;
+        WindowManager mPendingRemoveWindowManager;
 
         ActivityClientRecord() {
             parent = null;
@@ -444,19 +449,8 @@
         public final void scheduleRelaunchActivity(IBinder token,
                 List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
                 int configChanges, boolean notResumed, Configuration config) {
-            ActivityClientRecord r = new ActivityClientRecord();
-
-            r.token = token;
-            r.pendingResults = pendingResults;
-            r.pendingIntents = pendingNewIntents;
-            r.startsNotResumed = notResumed;
-            r.createdConfig = config;
-
-            synchronized (mPackages) {
-                mRelaunchingActivities.add(r);
-            }
-
-            queueOrSendMessage(H.RELAUNCH_ACTIVITY, r, configChanges);
+            requestRelaunchActivity(token, pendingResults, pendingNewIntents,
+                    configChanges, notResumed, config, true);
         }
 
         public final void scheduleNewIntent(List<Intent> intents, IBinder token) {
@@ -981,7 +975,7 @@
                 } break;
                 case RELAUNCH_ACTIVITY: {
                     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
-                    handleRelaunchActivity(r, msg.arg1);
+                    handleRelaunchActivity(r);
                 } break;
                 case PAUSE_ACTIVITY:
                     handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);
@@ -2183,6 +2177,19 @@
         return r;
     }
 
+    final void cleanUpPendingRemoveWindows(ActivityClientRecord r) {
+        if (r.mPendingRemoveWindow != null) {
+            r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow);
+            IBinder wtoken = r.mPendingRemoveWindow.getWindowToken();
+            if (wtoken != null) {
+                WindowManagerImpl.getDefault().closeAll(wtoken,
+                        r.activity.getClass().getName(), "Activity");
+            }
+        }
+        r.mPendingRemoveWindow = null;
+        r.mPendingRemoveWindowManager = null;
+    }
+
     final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
@@ -2235,6 +2242,9 @@
                 r.hideForNow = true;
             }
 
+            // Get rid of anything left hanging around.
+            cleanUpPendingRemoveWindows(r);
+
             // The window is now visible if it has been added, we are not
             // simply finishing, and we are not starting another activity.
             if (!r.activity.mFinished && willBeVisible
@@ -2267,11 +2277,14 @@
                 }
             }
 
-            r.nextIdle = mNewActivities;
-            mNewActivities = r;
-            if (localLOGV) Slog.v(
-                TAG, "Scheduling idle handler for " + r);
-            Looper.myQueue().addIdleHandler(new Idler());
+            if (!r.onlyLocalRequest) {
+                r.nextIdle = mNewActivities;
+                mNewActivities = r;
+                if (localLOGV) Slog.v(
+                    TAG, "Scheduling idle handler for " + r);
+                Looper.myQueue().addIdleHandler(new Idler());
+            }
+            r.onlyLocalRequest = false;
 
         } else {
             // If an exception was thrown when trying to resume, then
@@ -2728,6 +2741,7 @@
         ActivityClientRecord r = performDestroyActivity(token, finishing,
                 configChanges, getNonConfigInstance);
         if (r != null) {
+            cleanUpPendingRemoveWindows(r);
             WindowManager wm = r.activity.getWindowManager();
             View v = r.activity.mDecor;
             if (v != null) {
@@ -2736,16 +2750,31 @@
                 }
                 IBinder wtoken = v.getWindowToken();
                 if (r.activity.mWindowAdded) {
-                    wm.removeViewImmediate(v);
+                    if (r.onlyLocalRequest) {
+                        // Hold off on removing this until the new activity's
+                        // window is being added.
+                        r.mPendingRemoveWindow = v;
+                        r.mPendingRemoveWindowManager = wm;
+                    } else {
+                        wm.removeViewImmediate(v);
+                    }
                 }
-                if (wtoken != null) {
+                if (wtoken != null && r.mPendingRemoveWindow == null) {
                     WindowManagerImpl.getDefault().closeAll(wtoken,
                             r.activity.getClass().getName(), "Activity");
                 }
                 r.activity.mDecor = null;
             }
-            WindowManagerImpl.getDefault().closeAll(token,
-                    r.activity.getClass().getName(), "Activity");
+            if (r.mPendingRemoveWindow == null) {
+                // If we are delaying the removal of the activity window, then
+                // we can't clean up all windows here.  Note that we can't do
+                // so later either, which means any windows that aren't closed
+                // by the app will leak.  Well we try to warning them a lot
+                // about leaking windows, because that is a bug, so if they are
+                // using this recreate facility then they get to live with leaks.
+                WindowManagerImpl.getDefault().closeAll(token,
+                        r.activity.getClass().getName(), "Activity");
+            }
 
             // Mocked out contexts won't be participating in the normal
             // process lifecycle, but if we're running with a proper
@@ -2766,17 +2795,70 @@
         }
     }
 
-    private final void handleRelaunchActivity(ActivityClientRecord tmp, int configChanges) {
+    public final void requestRelaunchActivity(IBinder token,
+            List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+            int configChanges, boolean notResumed, Configuration config,
+            boolean fromServer) {
+        ActivityClientRecord target = null;
+
+        synchronized (mPackages) {
+            for (int i=0; i<mRelaunchingActivities.size(); i++) {
+                ActivityClientRecord r = mRelaunchingActivities.get(i);
+                if (r.token == token) {
+                    target = r;
+                    if (pendingResults != null) {
+                        if (r.pendingResults != null) {
+                            r.pendingResults.addAll(pendingResults);
+                        } else {
+                            r.pendingResults = pendingResults;
+                        }
+                    }
+                    if (pendingNewIntents != null) {
+                        if (r.pendingIntents != null) {
+                            r.pendingIntents.addAll(pendingNewIntents);
+                        } else {
+                            r.pendingIntents = pendingNewIntents;
+                        }
+                    }
+                    break;
+                }
+            }
+
+            if (target == null) {
+                target = new ActivityClientRecord();
+                target.token = token;
+                target.pendingResults = pendingResults;
+                target.pendingIntents = pendingNewIntents;
+                if (!fromServer) {
+                    ActivityClientRecord existing = mActivities.get(token);
+                    if (existing != null) {
+                        target.startsNotResumed = existing.paused;
+                    }
+                    target.onlyLocalRequest = true;
+                }
+                mRelaunchingActivities.add(target);
+                queueOrSendMessage(H.RELAUNCH_ACTIVITY, target);
+            }
+
+            if (fromServer) {
+                target.startsNotResumed = notResumed;
+                target.onlyLocalRequest = false;
+            }
+            if (config != null) {
+                target.createdConfig = config;
+            }
+            target.pendingConfigChanges |= configChanges;
+        }
+    }
+
+    private final void handleRelaunchActivity(ActivityClientRecord tmp) {
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
 
         Configuration changedConfig = null;
+        int configChanges = 0;
 
-        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
-                + tmp.token + " with configChanges=0x"
-                + Integer.toHexString(configChanges));
-        
         // First: make sure we have the most recent configuration and most
         // recent version of the activity, or skip it if some previous call
         // had taken a more recent version.
@@ -2788,6 +2870,7 @@
                 ActivityClientRecord r = mRelaunchingActivities.get(i);
                 if (r.token == token) {
                     tmp = r;
+                    configChanges |= tmp.pendingConfigChanges;
                     mRelaunchingActivities.remove(i);
                     i--;
                     N--;
@@ -2799,6 +2882,10 @@
                 return;
             }
 
+            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+                    + tmp.token + " with configChanges=0x"
+                    + Integer.toHexString(configChanges));
+
             if (mPendingConfiguration != null) {
                 changedConfig = mPendingConfiguration;
                 mPendingConfiguration = null;
@@ -2834,6 +2921,7 @@
         }
 
         r.activity.mConfigChangeFlags |= configChanges;
+        r.onlyLocalRequest = tmp.onlyLocalRequest;
         Intent currentIntent = r.activity.mIntent;
 
         Bundle savedState = null;