Launcher restore state fixes:

> Creating PendingRequestArgs to store generic request info across
activity instances
> Storing the CALL_PHONE request in PendingRequestArgs

> Handling onActivityResult only after the workspace has loaded.
onActivityResult sometimes modifies launcher db when removing empty
screens, and any add action is also deferred until bindComplete.
This simplifies this logic.

> Always binding the restored page irrespective of pending result.
Before starting a request activity (bind widget permission, or
widget config activity or shortcut picker), workspace commits the
pending screen id to the DB. Hence the restore page is valid when
restore is called (onResume loads with currentPage instead of
restored page)

Bug: 28573143
Change-Id: I34be603cbeb2145f5caf0d18e016f50029e07df8
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c919db8..0444737 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -35,7 +35,6 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.DialogInterface;
@@ -115,10 +114,12 @@
 import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PendingRequestArgs;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -183,12 +184,10 @@
     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
     // Type: int
     private static final String RUNTIME_STATE = "launcher.state";
-    // Type: Content Values / parcelable
-    private static final String RUNTIME_STATE_PENDING_ADD_ITEM = "launcher.add_item";
-    // Type: parcelable
-    private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
-    // Type: parcelable
-    private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
+    // Type: PendingRequestArgs
+    private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
+    // Type: ActivityResultInfo
+    private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
 
     static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
 
@@ -236,10 +235,6 @@
     private AppWidgetManagerCompat mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
 
-    @Thunk final ItemInfo mPendingAddInfo = new ItemInfo();
-    private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
-    private int mPendingAddWidgetId = -1;
-
     private int[] mTmpAddItemCellCoordinates = new int[2];
 
     @Thunk Hotseat mHotseat;
@@ -269,8 +264,6 @@
     @Thunk boolean mWorkspaceLoading = true;
 
     private boolean mPaused = true;
-    private boolean mRestoring;
-    private boolean mWaitingForResult;
     private boolean mOnResumeNeedsLoad;
 
     private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
@@ -307,7 +300,6 @@
     private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
 
     private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
-    private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
@@ -351,17 +343,13 @@
         }
     };
 
-    private static PendingAddArguments sPendingAddItem;
-
-    @Thunk static class PendingAddArguments {
-        int requestCode;
-        Intent intent;
-        long container;
-        long screenId;
-        int cellX;
-        int cellY;
-        int appWidgetId;
-    }
+    // Activity result which needs to be processed after workspace has loaded.
+    private ActivityResultInfo mPendingActivityResult;
+    /**
+     * Holds extra information required to handle a result from an external call, like
+     * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)}
+     */
+    private PendingRequestArgs mPendingRequestArgs;
 
     private UserEventDispatcher mUserEventDispatcher;
 
@@ -452,20 +440,14 @@
             Trace.endSection();
         }
 
-        if (!mRestoring) {
-            if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
-                // If the user leaves launcher, then we should just load items asynchronously when
-                // they return.
-                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
-            } else {
-                // We only load the page synchronously if the user rotates (or triggers a
-                // configuration change) while launcher is in the foreground
-                if (!mModel.startLoader(mWorkspace.getRestorePage())) {
-                    // If we are not binding synchronously, show a fade in animation when
-                    // the first page bind completes.
-                    mDragLayer.setAlpha(0);
-                }
-            }
+        // We only load the page synchronously if the user rotates (or triggers a
+        // configuration change) while launcher is in the foreground
+        if (!mModel.startLoader(mWorkspace.getRestorePage())) {
+            // If we are not binding synchronously, show a fade in animation when
+            // the first page bind completes.
+            mDragLayer.setAlpha(0);
+        } else {
+            setWorkspaceLoading(true);
         }
 
         // For handling default keys
@@ -658,53 +640,61 @@
      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
      * a configuration step, this allows the proper animations to run after other transitions.
      */
-    private long completeAdd(PendingAddArguments args) {
-        long screenId = args.screenId;
-        if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+    private long completeAdd(
+            int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) {
+        long screenId = info.screenId;
+        if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
             // When the screen id represents an actual screen (as opposed to a rank) we make sure
             // that the drop page actually exists.
-            screenId = ensurePendingDropLayoutExists(args.screenId);
+            screenId = ensurePendingDropLayoutExists(info.screenId);
         }
 
-        switch (args.requestCode) {
+        switch (requestCode) {
             case REQUEST_CREATE_SHORTCUT:
-                completeAddShortcut(args.intent, args.container, screenId, args.cellX,
-                        args.cellY);
+                completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY);
                 break;
             case REQUEST_CREATE_APPWIDGET:
-                completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
+                completeAddAppWidget(appWidgetId, info, null, null);
                 break;
             case REQUEST_RECONFIGURE_APPWIDGET:
-                completeRestoreAppWidget(args.appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
+                completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
                 break;
             case REQUEST_BIND_PENDING_APPWIDGET: {
-                int widgetId = args.appWidgetId;
-                LauncherAppWidgetInfo info =
+                int widgetId = appWidgetId;
+                LauncherAppWidgetInfo widgetInfo =
                         completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
-                if (info != null) {
+                if (widgetInfo != null) {
                     // Since the view was just bound, also launch the configure activity if needed
                     LauncherAppWidgetProviderInfo provider = mAppWidgetManager
                             .getLauncherAppWidgetInfo(widgetId);
                     if (provider != null && provider.configure != null) {
-                        startRestoredWidgetReconfigActivity(provider, info);
+                        startRestoredWidgetReconfigActivity(provider, widgetInfo);
                     }
                 }
                 break;
             }
         }
-        // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
-        // if you turned the screen off and then back while in All Apps, Launcher would not
-        // return to the workspace. Clearing mAddInfo.container here fixes this issue
-        resetAddInfo();
+
         return screenId;
     }
 
     private void handleActivityResult(
             final int requestCode, final int resultCode, final Intent data) {
+        if (isWorkspaceLoading()) {
+            // process the result once the workspace has loaded.
+            mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data);
+            return;
+        }
+        mPendingActivityResult = null;
+
         // Reset the startActivity waiting flag
-        setWaitingForResult(false);
-        final int pendingAddWidgetId = mPendingAddWidgetId;
-        mPendingAddWidgetId = -1;
+        final PendingRequestArgs requestArgs = mPendingRequestArgs;
+        setWaitingForResult(null);
+        if (requestArgs == null) {
+            return;
+        }
+
+        final int pendingAddWidgetId = requestArgs.getWidgetId();
 
         Runnable exitSpringLoaded = new Runnable() {
             @Override
@@ -719,12 +709,14 @@
             final int appWidgetId = data != null ?
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
             if (resultCode == RESULT_CANCELED) {
-                completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
+                completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
                 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
             } else if (resultCode == RESULT_OK) {
-                addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
-                        mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
+                addAppWidgetImpl(
+                        appWidgetId, requestArgs, null,
+                        requestArgs.getWidgetProvider(),
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY);
             }
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -740,7 +732,6 @@
         boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
                 requestCode == REQUEST_CREATE_APPWIDGET);
 
-        final boolean workspaceLocked = isWorkspaceLocked();
         // We have special handling for widgets
         if (isWidgetDrop) {
             final int appWidgetId;
@@ -757,46 +748,36 @@
                 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
                         "returned from the widget configuration activity.");
                 result = RESULT_CANCELED;
-                completeTwoStageWidgetDrop(result, appWidgetId);
+                completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
                 final Runnable onComplete = new Runnable() {
                     @Override
                     public void run() {
                         exitSpringLoadedDragModeDelayed(false, 0, null);
                     }
                 };
-                if (workspaceLocked) {
-                    // No need to remove the empty screen if we're mid-binding, as the
-                    // the bind will not add the empty screen.
-                    mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
-                } else {
-                    mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
-                            ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
-                }
-            } else {
-                if (!workspaceLocked) {
-                    if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                        // When the screen id represents an actual screen (as opposed to a rank)
-                        // we make sure that the drop page actually exists.
-                        mPendingAddInfo.screenId =
-                                ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
-                    }
-                    final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
 
-                    dropLayout.setDropPending(true);
-                    final Runnable onComplete = new Runnable() {
-                        @Override
-                        public void run() {
-                            completeTwoStageWidgetDrop(resultCode, appWidgetId);
-                            dropLayout.setDropPending(false);
-                        }
-                    };
-                    mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
-                            ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
-                } else {
-                    PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
-                            mPendingAddInfo);
-                    sPendingAddItem = args;
+                mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+            } else {
+                if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                    // When the screen id represents an actual screen (as opposed to a rank)
+                    // we make sure that the drop page actually exists.
+                    requestArgs.screenId =
+                            ensurePendingDropLayoutExists(requestArgs.screenId);
                 }
+                final CellLayout dropLayout =
+                        mWorkspace.getScreenWithId(requestArgs.screenId);
+
+                dropLayout.setDropPending(true);
+                final Runnable onComplete = new Runnable() {
+                    @Override
+                    public void run() {
+                        completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs);
+                        dropLayout.setDropPending(false);
+                    }
+                };
+                mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
             }
             return;
         }
@@ -805,13 +786,7 @@
                 || requestCode == REQUEST_BIND_PENDING_APPWIDGET) {
             if (resultCode == RESULT_OK) {
                 // Update the widget view.
-                PendingAddArguments args = preparePendingAddArgs(requestCode, data,
-                        pendingAddWidgetId, mPendingAddInfo);
-                if (workspaceLocked) {
-                    sPendingAddItem = args;
-                } else {
-                    completeAdd(args);
-                }
+                completeAdd(requestCode, data, pendingAddWidgetId, requestArgs);
             }
             // Leave the widget in the pending state if the user canceled the configure.
             return;
@@ -819,23 +794,17 @@
 
         if (requestCode == REQUEST_CREATE_SHORTCUT) {
             // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
-            if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
-                final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
-                        mPendingAddInfo);
-                if (isWorkspaceLocked()) {
-                    sPendingAddItem = args;
-                } else {
-                    completeAdd(args);
-                    mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                            ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
-                }
+            if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
+                completeAdd(requestCode, data, -1, requestArgs);
+                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+
             } else if (resultCode == RESULT_CANCELED) {
                 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
             }
         }
         mDragLayer.clearAnimatedView();
-
     }
 
     @Override
@@ -850,15 +819,18 @@
     /** @Override for MNC */
     public void onRequestPermissionsResult(int requestCode, String[] permissions,
             int[] grantResults) {
-        if (requestCode == REQUEST_PERMISSION_CALL_PHONE && sPendingAddItem != null
-                && sPendingAddItem.requestCode == REQUEST_PERMISSION_CALL_PHONE) {
+        PendingRequestArgs pendingArgs = mPendingRequestArgs;
+        if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null
+                && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) {
+            setWaitingForResult(null);
+
             View v = null;
-            CellLayout layout = getCellLayout(sPendingAddItem.container, sPendingAddItem.screenId);
+            CellLayout layout = getCellLayout(pendingArgs.container, pendingArgs.screenId);
             if (layout != null) {
-                v = layout.getChildAt(sPendingAddItem.cellX, sPendingAddItem.cellY);
+                v = layout.getChildAt(pendingArgs.cellX, pendingArgs.cellY);
             }
-            Intent intent = sPendingAddItem.intent;
-            sPendingAddItem = null;
+            Intent intent = pendingArgs.getPendingIntent();
+
             if (grantResults.length > 0
                     && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                 startActivitySafely(v, intent, null);
@@ -874,19 +846,6 @@
         }
     }
 
-    private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
-            appWidgetId, ItemInfo info) {
-        PendingAddArguments args = new PendingAddArguments();
-        args.requestCode = requestCode;
-        args.intent = data;
-        args.container = info.container;
-        args.screenId = info.screenId;
-        args.cellX = info.cellX;
-        args.cellY = info.cellY;
-        args.appWidgetId = appWidgetId;
-        return args;
-    }
-
     /**
      * Check to see if a given screen id exists. If not, create it at the end, return the new id.
      *
@@ -905,8 +864,9 @@
         }
     }
 
-    @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
-        CellLayout cellLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+    @Thunk void completeTwoStageWidgetDrop(
+            final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) {
+        CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId);
         Runnable onCompleteRunnable = null;
         int animationType = 0;
 
@@ -914,13 +874,12 @@
         if (resultCode == RESULT_OK) {
             animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
             final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
-                    mPendingAddWidgetInfo);
+                    requestArgs.getWidgetProvider());
             boundWidget = layout;
             onCompleteRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
-                            mPendingAddInfo.screenId, layout, null);
+                    completeAddAppWidget(appWidgetId, requestArgs, layout, null);
                     exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
                             EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
                 }
@@ -930,7 +889,7 @@
             animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
         }
         if (mDragLayer.getAnimatedView() != null) {
-            mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
+            mWorkspace.animateWidgetDrop(requestArgs, cellLayout,
                     (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
                     animationType, boundWidget, true);
         } else if (onCompleteRunnable != null) {
@@ -998,10 +957,9 @@
         mOnResumeState = State.NONE;
 
         mPaused = false;
-        if (mRestoring || mOnResumeNeedsLoad) {
+        if (mOnResumeNeedsLoad) {
             setWorkspaceLoading(true);
             mModel.startLoader(getCurrentWorkspaceScreen());
-            mRestoring = false;
             mOnResumeNeedsLoad = false;
         }
         if (mBindOnResumeCallbacks.size() > 0) {
@@ -1316,18 +1274,12 @@
             mWorkspace.setRestorePage(currentScreen);
         }
 
-        ContentValues itemValues = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_ITEM);
-        if (itemValues != null) {
-            mPendingAddInfo.readFromValues(itemValues);
-            AppWidgetProviderInfo info = savedState.getParcelable(
-                    RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
-            mPendingAddWidgetInfo = info == null ?
-                    null : LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
-
-            mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
-            setWaitingForResult(true);
-            mRestoring = true;
+        PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
+        if (requestArgs != null) {
+            setWaitingForResult(requestArgs);
         }
+
+        mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
     }
 
     /**
@@ -1542,10 +1494,8 @@
 
         LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
 
-        if (!mRestoring) {
-            mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
-                    isWorkspaceLocked());
-        }
+        mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
+                isWorkspaceLocked());
     }
 
     /**
@@ -1553,10 +1503,9 @@
      *
      * @param appWidgetId The app widget id
      */
-    @Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId,
+    @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
             AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
 
-        ItemInfo info = mPendingAddInfo;
         if (appWidgetInfo == null) {
             appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId);
         }
@@ -1567,24 +1516,21 @@
 
         LauncherAppWidgetInfo launcherInfo;
         launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
-        launcherInfo.spanX = info.spanX;
-        launcherInfo.spanY = info.spanY;
-        launcherInfo.minSpanX = info.minSpanX;
-        launcherInfo.minSpanY = info.minSpanY;
+        launcherInfo.spanX = itemInfo.spanX;
+        launcherInfo.spanY = itemInfo.spanY;
+        launcherInfo.minSpanX = itemInfo.minSpanX;
+        launcherInfo.minSpanY = itemInfo.minSpanY;
         launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
 
         LauncherModel.addItemToDatabase(this, launcherInfo,
-                container, screenId, info.cellX, info.cellY);
+                itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY);
 
-        if (!mRestoring) {
-            if (hostView == null) {
-                // Perform actual inflation because we're live
-                hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
-            }
-            hostView.setVisibility(View.VISIBLE);
-            addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked());
+        if (hostView == null) {
+            // Perform actual inflation because we're live
+            hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
         }
-        resetAddInfo();
+        hostView.setVisibility(View.VISIBLE);
+        addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked());
     }
 
     private void addAppWidgetToWorkspace(
@@ -1615,8 +1561,7 @@
 
                 // Reset AllApps to its initial state only if we are not in the middle of
                 // processing a multi-step drop
-                if (mAppsView != null && mWidgetsView != null &&
-                        mPendingAddInfo.container == ItemInfo.NO_ID) {
+                if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) {
                     if (!showWorkspace(false)) {
                         // If we are already on the workspace, then manually reset all apps
                         mAppsView.reset();
@@ -1828,7 +1773,7 @@
         getWindow().closeAllPanels();
 
         // Whatever we were doing is hereby canceled.
-        setWaitingForResult(false);
+        setWaitingForResult(null);
     }
 
     @Override
@@ -1946,13 +1891,11 @@
         closeFolder(false);
         closeShortcutsContainer(false);
 
-        if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
-                mWaitingForResult) {
-            ContentValues itemValues = new ContentValues();
-            mPendingAddInfo.writeToValues(itemValues);
-            outState.putParcelable(RUNTIME_STATE_PENDING_ADD_ITEM, itemValues);
-            outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
-            outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
+        if (mPendingRequestArgs != null) {
+            outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
+        }
+        if (mPendingActivityResult != null) {
+            outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
         }
 
         if (mLauncherCallbacks != null) {
@@ -2015,14 +1958,12 @@
 
     @Override
     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
-        onStartForResult(requestCode);
         super.startActivityForResult(intent, requestCode, options);
     }
 
     @Override
     public void startIntentSenderForResult (IntentSender intent, int requestCode,
             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
-        onStartForResult(requestCode);
         try {
             super.startIntentSenderForResult(intent, requestCode,
                 fillInIntent, flagsMask, flagsValues, extraFlags, options);
@@ -2031,12 +1972,6 @@
         }
     }
 
-    private void onStartForResult(int requestCode) {
-        if (requestCode >= 0) {
-            setWaitingForResult(true);
-        }
-    }
-
     /**
      * Indicates that we want global search for this activity by setting the globalSearch
      * argument for {@link #startSearch} to true.
@@ -2147,7 +2082,7 @@
     }
 
     public boolean isWorkspaceLocked() {
-        return mWorkspaceLoading || mWaitingForResult;
+        return mWorkspaceLoading || mPendingRequestArgs != null;
     }
 
     public boolean isWorkspaceLoading() {
@@ -2162,9 +2097,9 @@
         }
     }
 
-    private void setWaitingForResult(boolean value) {
+    private void setWaitingForResult(PendingRequestArgs args) {
         boolean isLocked = isWorkspaceLocked();
-        mWaitingForResult = value;
+        mPendingRequestArgs = args;
         if (isLocked != isWorkspaceLocked()) {
             onWorkspaceLockedChanged();
         }
@@ -2176,33 +2111,23 @@
         }
     }
 
-    private void resetAddInfo() {
-        mPendingAddInfo.container = ItemInfo.NO_ID;
-        mPendingAddInfo.screenId = -1;
-        mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
-        mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
-        mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1;
-    }
-
-    void addAppWidgetFromDropImpl(final int appWidgetId, final ItemInfo info, final
-            AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
+    void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
+            LauncherAppWidgetProviderInfo appWidgetInfo) {
         if (LOGD) {
             Log.d(TAG, "Adding widget from drop");
         }
         addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
     }
 
-    void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
-            final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
+    void addAppWidgetImpl(int appWidgetId, ItemInfo info,
+            AppWidgetHostView boundWidget, LauncherAppWidgetProviderInfo appWidgetInfo,
             int delay) {
         if (appWidgetInfo.configure != null) {
-            mPendingAddWidgetInfo = appWidgetInfo;
-            mPendingAddWidgetId = appWidgetId;
+            setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, appWidgetInfo, info));
 
             // Launch over to configure widget, if needed
             mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
                     mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
-
         } else {
             // Otherwise just add it
             Runnable onComplete = new Runnable() {
@@ -2213,8 +2138,7 @@
                             null);
                 }
             };
-            completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
-                    appWidgetInfo);
+            completeAddAppWidget(appWidgetId, info, boundWidget, appWidgetInfo);
             mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
         }
     }
@@ -2227,17 +2151,22 @@
 
     public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
             int[] cell, int spanX, int spanY) {
+        info.container = container;
+        info.screenId = screenId;
+        if (cell != null) {
+            info.cellX = cell[0];
+            info.cellY = cell[1];
+        }
+        info.spanX = spanX;
+        info.spanY = spanY;
+
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                int span[] = new int[2];
-                span[0] = spanX;
-                span[1] = spanY;
-                addAppWidgetFromDrop((PendingAddWidgetInfo) info,
-                        container, screenId, cell, span);
+                addAppWidgetFromDrop((PendingAddWidgetInfo) info);
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                processShortcutFromDrop(info.componentName, container, screenId, cell);
+                processShortcutFromDrop(info);
                 break;
             default:
                 throw new IllegalStateException("Unknown item type: " + info.itemType);
@@ -2246,51 +2175,18 @@
 
     /**
      * Process a shortcut drop.
-     *
-     * @param componentName The name of the component
-     * @param screenId The ID of the screen where it should be added
-     * @param cell The cell it should be added to, optional
      */
-    private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
-            int[] cell) {
-        resetAddInfo();
-        mPendingAddInfo.container = container;
-        mPendingAddInfo.screenId = screenId;
-
-        if (cell != null) {
-            mPendingAddInfo.cellX = cell[0];
-            mPendingAddInfo.cellY = cell[1];
-        }
-
+    private void processShortcutFromDrop(PendingAddItemInfo info) {
+        setWaitingForResult(new PendingRequestArgs(info));
         Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-        createShortcutIntent.setComponent(componentName);
+        createShortcutIntent.setComponent(info.componentName);
         Utilities.startActivityForResultSafely(this, createShortcutIntent, REQUEST_CREATE_SHORTCUT);
     }
 
     /**
      * Process a widget drop.
-     *
-     * @param info The PendingAppWidgetInfo of the widget being added.
-     * @param screenId The ID of the screen where it should be added
-     * @param cell The cell it should be added to, optional
      */
-    private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
-            int[] cell, int[] span) {
-        resetAddInfo();
-        mPendingAddInfo.container = info.container = container;
-        mPendingAddInfo.screenId = info.screenId = screenId;
-        mPendingAddInfo.minSpanX = info.minSpanX;
-        mPendingAddInfo.minSpanY = info.minSpanY;
-
-        if (cell != null) {
-            mPendingAddInfo.cellX = cell[0];
-            mPendingAddInfo.cellY = cell[1];
-        }
-        if (span != null) {
-            mPendingAddInfo.spanX = span[0];
-            mPendingAddInfo.spanY = span[1];
-        }
-
+    private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
         AppWidgetHostView hostView = info.boundWidget;
         int appWidgetId;
         if (hostView != null) {
@@ -2316,11 +2212,11 @@
             if (success) {
                 addAppWidgetFromDropImpl(appWidgetId, info, null, info.info);
             } else {
-                mPendingAddWidgetInfo = info.info;
+                setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, info.info, info));
                 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
-                mAppWidgetManager.getUser(mPendingAddWidgetInfo)
+                mAppWidgetManager.getUser(info.info)
                     .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
                 // TODO: we need to make sure that this accounts for the options bundle.
                 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
@@ -2539,14 +2435,13 @@
                 LauncherAppWidgetProviderInfo appWidgetInfo =
                         mAppWidgetManager.findProvider(info.providerName, info.user);
                 if (appWidgetInfo != null) {
-                    mPendingAddWidgetId = info.appWidgetId;
-                    mPendingAddInfo.copyFrom(info);
-                    mPendingAddWidgetInfo = appWidgetInfo;
+                    setWaitingForResult(PendingRequestArgs
+                            .forWidgetInfo(info.appWidgetId, appWidgetInfo, info));
 
                     Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
-                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingAddWidgetId);
+                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, info.appWidgetId);
                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider);
-                    mAppWidgetManager.getUser(mPendingAddWidgetInfo)
+                    mAppWidgetManager.getUser(appWidgetInfo)
                             .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
                     startActivityForResult(intent, REQUEST_BIND_PENDING_APPWIDGET);
                 }
@@ -2575,9 +2470,7 @@
 
     private void startRestoredWidgetReconfigActivity(
             LauncherAppWidgetProviderInfo provider, LauncherAppWidgetInfo info) {
-        mPendingAddWidgetInfo = provider;
-        mPendingAddInfo.copyFrom(info);
-        mPendingAddWidgetId = info.appWidgetId;
+        setWaitingForResult(PendingRequestArgs.forWidgetInfo(info.appWidgetId, provider, info));
         mAppWidgetManager.startConfigActivity(provider,
                 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
     }
@@ -2747,6 +2640,7 @@
         int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
         float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
 
+        setWaitingForResult(new PendingRequestArgs(new ItemInfo()));
         Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
                 .setPackage(pickerPackage)
                 .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset);
@@ -2863,9 +2757,9 @@
                     && Intent.ACTION_CALL.equals(intent.getAction())
                     && checkSelfPermission(Manifest.permission.CALL_PHONE) !=
                     PackageManager.PERMISSION_GRANTED) {
-                // TODO: Rename sPendingAddItem to a generic name.
-                sPendingAddItem = preparePendingAddArgs(REQUEST_PERMISSION_CALL_PHONE, intent,
-                        0, info);
+
+                setWaitingForResult(PendingRequestArgs
+                        .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info));
                 requestPermissions(new String[]{Manifest.permission.CALL_PHONE},
                         REQUEST_PERMISSION_CALL_PHONE);
             } else {
@@ -3199,7 +3093,7 @@
             ItemInfo info = (ItemInfo) v.getTag();
             longClickCellInfo = new CellLayout.CellInfo(v, info);
             itemUnderLongClick = longClickCellInfo.cell;
-            resetAddInfo();
+            mPendingRequestArgs = null;
         }
 
         // The hotseat touch handling does not go through Workspace, and we always allow long press
@@ -4074,21 +3968,10 @@
 
         setWorkspaceLoading(false);
 
-        // If we received the result of any pending adds while the loader was running (e.g. the
-        // widget configuration forced an orientation change), process them now.
-        if (sPendingAddItem != null) {
-            final long screenId = completeAdd(sPendingAddItem);
-
-            // TODO: this moves the user to the page where the pending item was added. Ideally,
-            // the screen would be guaranteed to exist after bind, and the page would be set through
-            // the workspace restore process.
-            mWorkspace.post(new Runnable() {
-                @Override
-                public void run() {
-                    mWorkspace.snapToScreenId(screenId);
-                }
-            });
-            sPendingAddItem = null;
+        if (mPendingActivityResult != null) {
+            handleActivityResult(mPendingActivityResult.requestCode,
+                    mPendingActivityResult.resultCode, mPendingActivityResult.data);
+            mPendingActivityResult = null;
         }
 
         InstallShortcutReceiver.disableAndFlushInstallQueue(this);
@@ -4504,8 +4387,8 @@
         Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
         Log.d(TAG, "mSavedState=" + mSavedState);
         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
-        Log.d(TAG, "mRestoring=" + mRestoring);
-        Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
+        Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs);
+        Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult);
         mModel.dumpState();
         // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();