Adding support to restore widgets even for jelly beans.

> Show 'widget-not-ready' until the widget app is installed
> Once the app is installed, bind a new widget id (not required on L if
  id-remap was received).
  **Remove the widget if bind failed
> If the widget has no configuration screen, show the widget, otherwise
  show 'setup-widget'.
> Clicking 'setup-widget' shows the config screen, and updates the
  widget on RESULT_OK.

issue: 10779035

Change-Id: I2f8b06d09dd6acbc498cdd93edc59c26e5ce17af
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0ab665d..111fba8 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -65,7 +65,6 @@
 import android.os.Message;
 import android.os.StrictMode;
 import android.os.SystemClock;
-import android.provider.Settings;
 import android.speech.RecognizerIntent;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
@@ -153,6 +152,7 @@
     private static final int REQUEST_PICK_WALLPAPER = 10;
 
     private static final int REQUEST_BIND_APPWIDGET = 11;
+    private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
 
     /**
      * IntentStarter uses request codes starting with this. This must be greater than all activity
@@ -752,6 +752,9 @@
             case REQUEST_CREATE_APPWIDGET:
                 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
                 break;
+            case REQUEST_RECONFIGURE_APPWIDGET:
+                completeRestoreAppWidget(args.appWidgetId);
+                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
@@ -854,6 +857,21 @@
             return;
         }
 
+        if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
+            if (resultCode == RESULT_OK) {
+                // Update the widget view.
+                PendingAddArguments args = preparePendingAddArgs(requestCode, data,
+                        pendingAddWidgetId, mPendingAddInfo);
+                if (workspaceLocked) {
+                    sPendingAddItem = args;
+                } else {
+                    completeAdd(args);
+                }
+            }
+            // Leave the widget in the pending state if the user canceled the configure.
+            return;
+        }
+
         // The pattern used here is that a user PICKs a specific application,
         // which, depending on the target, might need to CREATE the actual target.
 
@@ -2446,6 +2464,10 @@
             }
         } else if (v == mAllAppsButton) {
             onClickAllAppsButton(v);
+        } else if (tag instanceof LauncherAppWidgetInfo) {
+            if (v instanceof PendingAppWidgetHostView) {
+                onClickPendingWidget((PendingAppWidgetHostView) v);
+            }
         }
     }
 
@@ -2454,6 +2476,27 @@
     }
 
     /**
+     * Event handler for the app widget view which has not fully restored.
+     */
+    public void onClickPendingWidget(PendingAppWidgetHostView v) {
+        if (v.isReadyForClickSetup()) {
+            LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
+            int widgetId = info.appWidgetId;
+            AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
+            if (appWidgetInfo != null) {
+                mPendingAddWidgetInfo = appWidgetInfo;
+                mPendingAddInfo.copyFrom(info);
+                mPendingAddWidgetId = widgetId;
+
+                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+                intent.setComponent(appWidgetInfo.configure);
+                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, info.appWidgetId);
+                Utilities.startActivityForResultSafely(this, intent, REQUEST_RECONFIGURE_APPWIDGET);
+            }
+        }
+    }
+
+    /**
      * Event handler for the search button
      *
      * @param v The view that was clicked.
@@ -4398,7 +4441,64 @@
         }
         final Workspace workspace = mWorkspace;
 
-        final AppWidgetProviderInfo appWidgetInfo;
+        AppWidgetProviderInfo appWidgetInfo;
+        if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) &&
+                ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
+
+            appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName);
+            if (appWidgetInfo == null) {
+                if (DEBUG_WIDGETS) {
+                    Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+                            + " belongs to component " + item.providerName
+                            + ", as the povider is null");
+                }
+                LauncherModel.deleteItemFromDatabase(this, item);
+                return;
+            }
+            // Note: This assumes that the id remap broadcast is received before this step.
+            // If that is not the case, the id remap will be ignored and user may see the
+            // click to setup view.
+            PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null);
+            pendingInfo.spanX = item.spanX;
+            pendingInfo.spanY = item.spanY;
+            pendingInfo.minSpanX = item.minSpanX;
+            pendingInfo.minSpanY = item.minSpanY;
+            Bundle options =
+                    AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
+
+            boolean success = false;
+            int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
+            if (options != null) {
+                success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId,
+                        appWidgetInfo.provider, options);
+            } else {
+                success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId,
+                        appWidgetInfo.provider);
+            }
+
+            // TODO consider showing a permission dialog when the widget is clicked.
+            if (!success) {
+                mAppWidgetHost.deleteAppWidgetId(newWidgetId);
+                if (DEBUG_WIDGETS) {
+                    Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+                            + " belongs to component " + item.providerName
+                            + ", as the launcher is unable to bing a new widget id");
+                }
+                LauncherModel.deleteItemFromDatabase(this, item);
+                return;
+            }
+
+            item.appWidgetId = newWidgetId;
+
+            // If the widget has a configure activity, it is still needs to set it up, otherwise
+            // the widget is ready to go.
+            item.restoreStatus = (appWidgetInfo.configure == null)
+                    ? LauncherAppWidgetInfo.RESTORE_COMPLETED
+                    : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+            LauncherModel.updateItemInDatabase(this, item);
+        }
+
         if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
             final int appWidgetId = item.appWidgetId;
             appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
@@ -4409,8 +4509,9 @@
             item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
         } else {
             appWidgetInfo = null;
-            item.hostView = new LauncherAppWidgetHostView(this, false);
+            item.hostView = new PendingAppWidgetHostView(this, item.restoreStatus);
             item.hostView.updateAppWidget(null);
+            item.hostView.setOnClickListener(this);
         }
 
         item.hostView.setTag(item);
@@ -4428,6 +4529,29 @@
         }
     }
 
+    /**
+     * Restores a pending widget.
+     *
+     * @param appWidgetId The app widget id
+     * @param cellInfo The position on screen where to create the widget.
+     */
+    private void completeRestoreAppWidget(final int appWidgetId) {
+        LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
+        if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
+            Log.e(TAG, "Widget update called, when the widget no longer exists.");
+            return;
+        }
+
+        PendingAppWidgetHostView pendingView = (PendingAppWidgetHostView) view;
+        pendingView.setStatus(LauncherAppWidgetInfo.RESTORE_COMPLETED);
+
+        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) pendingView.getTag();
+        info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+
+        mWorkspace.reinflateWidgetsIfNecessary();
+        LauncherModel.updateItemInDatabase(this, info);
+    }
+
     public void onPageBoundSynchronously(int page) {
         mSynchronouslyBoundPages.add(page);
     }