Improving restored widget behavior

> Clicking a broken widget install shows a dialog similar
to an app icon
> Clicking remove on the dialog removed all components
for the corresponding package
> Widget pending view shows 'Setup' text instead of icon,
if there is enough space

Change-Id: I82ec0a1ee9542c1e3b860e6e00798a80450dce3c
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e134d1b..1475001 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2455,9 +2455,9 @@
     /**
      * Event handler for the app widget view which has not fully restored.
      */
-    public void onClickPendingWidget(PendingAppWidgetHostView v) {
+    public void onClickPendingWidget(final PendingAppWidgetHostView v) {
+        final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
         if (v.isReadyForClickSetup()) {
-            LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
             int widgetId = info.appWidgetId;
             AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
             if (appWidgetInfo != null) {
@@ -2468,6 +2468,19 @@
                 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
                         info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
             }
+        } else if (info.installProgress < 0) {
+            // The install has not been queued
+            final String packageName = info.providerName.getPackageName();
+            showBrokenAppInstallDialog(packageName,
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
+                    }
+                });
+        } else {
+            // Download has started.
+            final String packageName = info.providerName.getPackageName();
+            startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
         }
     }
 
@@ -2526,6 +2539,23 @@
         }
     }
 
+    private void showBrokenAppInstallDialog(final String packageName,
+            DialogInterface.OnClickListener onSearchClickListener) {
+        new AlertDialog.Builder(this)
+            .setTitle(R.string.abandoned_promises_title)
+            .setMessage(R.string.abandoned_promise_explanation)
+            .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
+            .setNeutralButton(R.string.abandoned_clean_this,
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        final UserHandleCompat user = UserHandleCompat.myUserHandle();
+                        mWorkspace.removeAbandonedPromise(packageName, user);
+                    }
+                })
+            .create().show();
+        return;
+    }
+
     /**
      * Event handler for an app shortcut click.
      *
@@ -2557,25 +2587,13 @@
 
         // Check for abandoned promise
         if (shortcut.isAbandoned() && v instanceof BubbleTextView) {
-            AlertDialog.Builder builder = new AlertDialog.Builder(this);
-            builder.setTitle(R.string.abandoned_promises_title);
-            builder.setMessage(R.string.abandoned_promise_explanation);
-            builder.setPositiveButton(R.string.abandoned_search,
+            showBrokenAppInstallDialog(
+                    shortcut.getRestoredIntent().getComponent().getPackageName(),
                     new DialogInterface.OnClickListener() {
                         public void onClick(DialogInterface dialog, int id) {
                             startAppShortcutOrInfoActivity(v);
                         }
-                    }
-            );
-            builder.setNeutralButton(R.string.abandoned_clean_this,
-                    new DialogInterface.OnClickListener() {
-                        public void onClick(DialogInterface dialog, int id) {
-                            final BubbleTextView bubble = (BubbleTextView) v;
-                            final UserHandleCompat user = UserHandleCompat.myUserHandle();
-                            mWorkspace.removeAbandonedPromise(bubble, user);
-                        }
                     });
-            builder.create().show();
             return;
         }
 
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 4755482..50528b1 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -70,7 +70,7 @@
     /**
      * Indicates the installation progress of the widget provider
      */
-    int installProgress;
+    int installProgress = -1;
 
     private boolean mHasNotifiedInitialWidgetSizeChanged;
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index bcb4501..5e8e2ad 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -1078,44 +1078,72 @@
     }
 
     /**
+     * Removes all the items from the database corresponding to the specified package.
+     */
+    static void deletePackageFromDatabase(Context context, final String pn,
+            final UserHandleCompat user) {
+        ItemInfoFilter filter  = new ItemInfoFilter() {
+            @Override
+            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
+                return cn.getPackageName().equals(pn) && info.user.equals(user);
+            }
+        };
+        ArrayList<ItemInfo> infos = filterItemInfos(sBgItemsIdMap.values(), filter);
+        deleteItemsFromDatabase(context, infos);
+    }
+
+    /**
      * Removes the specified item from the database
      * @param context
      * @param item
      */
     static void deleteItemFromDatabase(Context context, final ItemInfo item) {
+        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+        items.add(item);
+        deleteItemsFromDatabase(context, items);
+    }
+
+    /**
+     * Removes the specified items from the database
+     * @param context
+     * @param item
+     */
+    static void deleteItemsFromDatabase(Context context, final ArrayList<ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
-        final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
 
         Runnable r = new Runnable() {
             public void run() {
-                cr.delete(uriToDelete, null, null);
+                for (ItemInfo item : items) {
+                    final Uri uri = LauncherSettings.Favorites.getContentUri(item.id, false);
+                    cr.delete(uri, null, null);
 
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
-                    switch (item.itemType) {
-                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                            sBgFolders.remove(item.id);
-                            for (ItemInfo info: sBgItemsIdMap.values()) {
-                                if (info.container == item.id) {
-                                    // We are deleting a folder which still contains items that
-                                    // think they are contained by that folder.
-                                    String msg = "deleting a folder (" + item + ") which still " +
-                                            "contains items (" + info + ")";
-                                    Log.e(TAG, msg);
+                    // Lock on mBgLock *after* the db operation
+                    synchronized (sBgLock) {
+                        switch (item.itemType) {
+                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                                sBgFolders.remove(item.id);
+                                for (ItemInfo info: sBgItemsIdMap.values()) {
+                                    if (info.container == item.id) {
+                                        // We are deleting a folder which still contains items that
+                                        // think they are contained by that folder.
+                                        String msg = "deleting a folder (" + item + ") which still " +
+                                                "contains items (" + info + ")";
+                                        Log.e(TAG, msg);
+                                    }
                                 }
-                            }
-                            sBgWorkspaceItems.remove(item);
-                            break;
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                            sBgWorkspaceItems.remove(item);
-                            break;
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                            sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
-                            break;
+                                sBgWorkspaceItems.remove(item);
+                                break;
+                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                                sBgWorkspaceItems.remove(item);
+                                break;
+                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                                sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
+                                break;
+                        }
+                        sBgItemsIdMap.remove(item.id);
+                        sBgDbIconCache.remove(item);
                     }
-                    sBgItemsIdMap.remove(item.id);
-                    sBgDbIconCache.remove(item);
                 }
             }
         };
@@ -2982,17 +3010,12 @@
             }
             // Remove all the components associated with this package
             for (String pn : removedPackageNames) {
-                ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn, mUser);
-                for (ItemInfo i : infos) {
-                    deleteItemFromDatabase(context, i);
-                }
+                deletePackageFromDatabase(context, pn, mUser);
             }
             // Remove all the specific components
             for (AppInfo a : removedApps) {
                 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
-                for (ItemInfo i : infos) {
-                    deleteItemFromDatabase(context, i);
-                }
+                deleteItemsFromDatabase(context, infos);
             }
             if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
                 // Remove any queued items from the install queue
@@ -3101,17 +3124,17 @@
      * to the market page for the item.
      */
     private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
-        final boolean debug = false;
         ComponentName componentName = intent.getComponent();
-        Intent marketIntent = new Intent(Intent.ACTION_VIEW);
-        Uri marketUri = new Uri.Builder()
+        return getMarketIntent(componentName.getPackageName());
+    }
+
+    static Intent getMarketIntent(String packageName) {
+        return new Intent(Intent.ACTION_VIEW)
+            .setData(new Uri.Builder()
                 .scheme("market")
                 .authority("details")
-                .appendQueryParameter("id", componentName.getPackageName())
-                .build();
-        if (debug) Log.d(TAG, "manufactured intent uri: " + marketUri.toString());
-        marketIntent.setData(marketUri);
-        return marketIntent;
+                .appendQueryParameter("id", packageName)
+                .build());
     }
 
     /**
@@ -3236,17 +3259,6 @@
         return new ArrayList<ItemInfo>(filtered);
     }
 
-    private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn,
-            final UserHandleCompat user) {
-        ItemInfoFilter filter  = new ItemInfoFilter() {
-            @Override
-            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
-                return cn.getPackageName().equals(pn) && info.user.equals(user);
-            }
-        };
-        return filterItemInfos(sBgItemsIdMap.values(), filter);
-    }
-
     private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
             final UserHandleCompat user) {
         ItemInfoFilter filter  = new ItemInfoFilter() {
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 0401436..d23a330 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -24,6 +24,10 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.util.TypedValue;
 import android.view.View;
 import android.view.View.OnClickListener;
 
@@ -46,12 +50,19 @@
 
     private boolean mDrawableSizeChanged;
 
+    private final TextPaint mPaint;
+    private Layout mSetupTextLayout;
+
     public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info) {
         super(context);
         mInfo = info;
         mStartState = info.restoreStatus;
         mIconLookupIntent = new Intent().setComponent(info.providerName);
 
+        mPaint = new TextPaint();
+        mPaint.setColor(0xFFFFFFFF);
+        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
+                getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
         setBackgroundResource(R.drawable.quantum_panel_dark);
         setWillNotDraw(false);
     }
@@ -127,7 +138,7 @@
 
     public void applyState() {
         if (mDrawable != null) {
-            mDrawable.setLevel(mInfo.installProgress);
+            mDrawable.setLevel(Math.max(mInfo.installProgress, 0));
         }
     }
 
@@ -165,27 +176,66 @@
             mDrawable.draw(canvas);
         } else if ((mCenterDrawable != null) && (mTopCornerDrawable != null)) {
             if (mDrawableSizeChanged) {
-                int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size);
+                DeviceProfile grid = getDeviceProfile();
+                int iconSize = grid.iconSizePx;
                 int paddingTop = getPaddingTop();
+                int paddingBottom = getPaddingBottom();
                 int paddingLeft = getPaddingLeft();
+                int paddingRight = getPaddingRight();
 
-                int size = Math.min(iconSize, Math.min(
-                        getWidth() - paddingLeft - getPaddingRight(),
-                        getHeight() - paddingTop - getPaddingBottom()));
-                mRect.set(0, 0, size, size);
-                mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
-                mCenterDrawable.setBounds(mRect);
+                int availableWidth = getWidth() - paddingLeft - paddingRight;
+                int availableHeight = getHeight() - paddingTop - paddingBottom;
 
-                size = Math.min(size / 2,
-                        Math.max(mRect.top - paddingTop, mRect.left - paddingLeft));
-                mTopCornerDrawable.setBounds(paddingLeft, paddingTop,
-                        paddingLeft + size, paddingTop + size);
+                // Recreate the setup text.
+                mSetupTextLayout = new StaticLayout(
+                        getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
+                        Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+                if (mSetupTextLayout.getLineCount() == 1) {
+                    // The text fits in a single line. No need to draw the setup icon.
+                    int size = Math.min(iconSize, Math.min(availableWidth,
+                            availableHeight - mSetupTextLayout.getHeight()));
+                    mRect.set(0, 0, size, size);
+                    mRect.offsetTo((getWidth() - mRect.width()) / 2,
+                            (getHeight() - mRect.height() - mSetupTextLayout.getHeight()
+                                    - grid.iconDrawablePaddingPx) / 2);
+
+                    mTopCornerDrawable.setBounds(mRect);
+
+                    // Update left and top to indicate the position where the text will be drawn.
+                    mRect.left = paddingLeft;
+                    mRect.top = mRect.bottom + grid.iconDrawablePaddingPx;
+                } else {
+                    // The text can't be drawn in a single line. Draw a setup icon instead.
+                    mSetupTextLayout = null;
+                    int size = Math.min(iconSize, Math.min(
+                            getWidth() - paddingLeft - paddingRight,
+                            getHeight() - paddingTop - paddingBottom));
+                    mRect.set(0, 0, size, size);
+                    mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
+                    mCenterDrawable.setBounds(mRect);
+
+                    size = Math.min(size / 2,
+                            Math.max(mRect.top - paddingTop, mRect.left - paddingLeft));
+                    mTopCornerDrawable.setBounds(paddingLeft, paddingTop,
+                            paddingLeft + size, paddingTop + size);
+                }
                 mDrawableSizeChanged = false;
             }
 
-            mCenterDrawable.draw(canvas);
-            mTopCornerDrawable.draw(canvas);
+            if (mSetupTextLayout == null) {
+                mCenterDrawable.draw(canvas);
+                mTopCornerDrawable.draw(canvas);
+            } else {
+                canvas.save();
+                canvas.translate(mRect.left, mRect.top);
+                mSetupTextLayout.draw(canvas);
+                canvas.restore();
+                mTopCornerDrawable.draw(canvas);
+            }
         }
     }
 
+    private DeviceProfile getDeviceProfile() {
+        return LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8661251..9d97306 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -4847,16 +4847,11 @@
         restorePendingWidgets(pkgNames);
     }
 
-    public void removeAbandonedPromise(BubbleTextView abandonedIcon, UserHandleCompat user) {
-        if (abandonedIcon.getTag() != null && abandonedIcon.getTag() instanceof ShortcutInfo) {
-            final ShortcutInfo shortcut = (ShortcutInfo) abandonedIcon.getTag();
-            if (shortcut.isAbandoned()) {
-                HashSet<ComponentName> cns = new HashSet<ComponentName>(1);
-                cns.add(shortcut.getRestoredIntent().getComponent());
-                LauncherModel.deleteItemFromDatabase(mLauncher, shortcut);
-                removeItemsByComponentName(cns, user);
-            }
-        }
+    public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
+        ArrayList<String> packages = new ArrayList<String>(1);
+        packages.add(packageName);
+        LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
+        removeItemsByPackageName(packages, user);
     }
 
     public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {