Adding support for app widgets in the InstallQueue

Bug: 32904959
Change-Id: I0d07a0c59d266493ae30a42579c1fa69b805009e
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 46df0b2..46bc3b3 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -67,6 +69,7 @@
 
     private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
     private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut";
+    private static final String APP_WIDGET_TYPE_KEY = "isAppWidget";
     private static final String USER_HANDLE_KEY = "userHandle";
 
     // The set of shortcuts that are pending install
@@ -200,13 +203,17 @@
 
     public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
         PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        return info == null ? null : info.getShortcutInfo();
+        return info == null ? null : (ShortcutInfo) info.getItemInfo();
     }
 
     public static void queueShortcut(ShortcutInfoCompat info, Context context) {
         queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
     }
 
+    public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
+    }
+
     public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
         HashSet<ShortcutKey> result = new HashSet<>();
 
@@ -276,6 +283,7 @@
 
         final LauncherActivityInfoCompat activityInfo;
         final ShortcutInfoCompat shortcutInfo;
+        final AppWidgetProviderInfo providerInfo;
 
         final Intent data;
         final Context mContext;
@@ -287,25 +295,30 @@
          * Initializes a PendingInstallShortcutInfo received from a different app.
          */
         public PendingInstallShortcutInfo(Intent data, UserHandleCompat user, Context context) {
+            activityInfo = null;
+            shortcutInfo = null;
+            providerInfo = null;
+
             this.data = data;
             this.user = user;
             mContext = context;
 
             launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
             label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-            activityInfo = null;
-            shortcutInfo = null;
+
         }
 
         /**
          * Initializes a PendingInstallShortcutInfo to represent a launcher target.
          */
         public PendingInstallShortcutInfo(LauncherActivityInfoCompat info, Context context) {
-            this.data = null;
-            mContext = context;
             activityInfo = info;
             shortcutInfo = null;
+            providerInfo = null;
+
+            data = null;
             user = info.getUser();
+            mContext = context;
 
             launchIntent = AppInfo.makeLaunchIntent(context, info, user);
             label = info.getLabel().toString();
@@ -315,16 +328,36 @@
          * Initializes a PendingInstallShortcutInfo to represent a launcher target.
          */
         public PendingInstallShortcutInfo(ShortcutInfoCompat info, Context context) {
-            this.data = null;
-            shortcutInfo = info;
-            mContext = context;
             activityInfo = null;
+            shortcutInfo = info;
+            providerInfo = null;
+
+            data = null;
+            mContext = context;
             user = info.getUserHandle();
 
             launchIntent = info.makeIntent(context);
             label = info.getShortLabel().toString();
         }
 
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
+        public PendingInstallShortcutInfo(
+                AppWidgetProviderInfo info, int widgetId, Context context) {
+            activityInfo = null;
+            shortcutInfo = null;
+            providerInfo = info;
+
+            data = null;
+            mContext = context;
+            user = UserHandleCompat.fromUser(info.getProfile());
+
+            launchIntent = new Intent().setComponent(info.provider)
+                    .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+            label = info.label;
+        }
+
         public String encodeToString() {
             try {
                 if (activityInfo != null) {
@@ -347,6 +380,16 @@
                             .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
                                     .getSerialNumberForUser(user))
                             .endObject().toString();
+                } else if (providerInfo != null) {
+                    // If it a launcher target, we only need component name, and user to
+                    // recreate this.
+                    return new JSONStringer()
+                            .object()
+                            .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+                            .key(APP_WIDGET_TYPE_KEY).value(true)
+                            .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+                                    .getSerialNumberForUser(user))
+                            .endObject().toString();
                 }
 
                 if (launchIntent.getAction() == null) {
@@ -388,11 +431,24 @@
             }
         }
 
-        public ShortcutInfo getShortcutInfo() {
+        public ItemInfo getItemInfo() {
             if (activityInfo != null) {
                 return new ShortcutInfo(activityInfo, mContext);
             } else if (shortcutInfo != null) {
                 return new ShortcutInfo(shortcutInfo, mContext);
+            } else if (providerInfo != null) {
+                LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
+                        .fromProviderInfo(mContext, providerInfo);
+                LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
+                        launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
+                        info.provider);
+                InvariantDeviceProfile idp = LauncherAppState.getInstance()
+                        .getInvariantDeviceProfile();
+                widgetInfo.minSpanX = info.minSpanX;
+                widgetInfo.minSpanY = info.minSpanY;
+                widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
+                widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
+                return widgetInfo;
             } else {
                 return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
             }
@@ -425,6 +481,16 @@
                 } else {
                     return new PendingInstallShortcutInfo(si.get(0), context);
                 }
+            } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) {
+                int widgetId = decoder.launcherIntent
+                        .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
+                AppWidgetProviderInfo info = AppWidgetManager.getInstance(context)
+                        .getAppWidgetInfo(widgetId);
+                if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) ||
+                        !info.getProfile().equals(decoder.user.getUser())) {
+                    return null;
+                }
+                return new PendingInstallShortcutInfo(info, widgetId, context);
             }
 
             Intent data = new Intent();
@@ -523,7 +589,7 @@
                 }
 
                 // Generate a shortcut info to add into the model
-                installQueue.add(pendingInfo.getShortcutInfo());
+                installQueue.add(pendingInfo.getItemInfo());
             }
             return installQueue;
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2f7a31f..7fce079 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3295,25 +3295,25 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     @Override
-    public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
+    public void bindItems(final ArrayList<ItemInfo> items, final int start, final int end,
                           final boolean forceAnimateIcons) {
         Runnable r = new Runnable() {
             public void run() {
-                bindItems(shortcuts, start, end, forceAnimateIcons);
+                bindItems(items, start, end, forceAnimateIcons);
             }
         };
         if (waitUntilResume(r)) {
             return;
         }
 
-        // Get the list of added shortcuts and intersect them with the set of shortcuts here
+        // Get the list of added items and intersect them with the set of items here
         final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         final Collection<Animator> bounceAnims = new ArrayList<Animator>();
         final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
         Workspace workspace = mWorkspace;
-        long newShortcutsScreenId = -1;
+        long newItemsScreenId = -1;
         for (int i = start; i < end; i++) {
-            final ItemInfo item = shortcuts.get(i);
+            final ItemInfo item = items.get(i);
 
             // Short circuit if we are loading dock items for a configuration which has no dock
             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
@@ -3325,15 +3325,33 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                     ShortcutInfo info = (ShortcutInfo) item;
                     view = createShortcut(info);
                     break;
-                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
                     view = FolderIcon.fromXml(R.layout.folder_icon, this,
                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                             (FolderInfo) item, mIconCache);
                     break;
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
+                    LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
+                    if (mIsSafeModeEnabled) {
+                        view = new PendingAppWidgetHostView(this, info, mIconCache, true);
+                    } else {
+                        LauncherAppWidgetProviderInfo providerInfo =
+                                mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
+                        if (providerInfo == null) {
+                            deleteWidgetInfo(info);
+                            continue;
+                        }
+                        view = mAppWidgetHost.createView(this, info.appWidgetId, providerInfo);
+                    }
+                    prepareAppWidget((AppWidgetHostView) view, info);
+                    break;
+                }
                 default:
                     throw new RuntimeException("Invalid Item Type");
             }
@@ -3364,22 +3382,22 @@
                 view.setScaleX(0f);
                 view.setScaleY(0f);
                 bounceAnims.add(createNewAppBounceAnimation(view, i));
-                newShortcutsScreenId = item.screenId;
+                newItemsScreenId = item.screenId;
             }
         }
 
         if (animateIcons) {
             // Animate to the correct page
-            if (newShortcutsScreenId > -1) {
+            if (newItemsScreenId > -1) {
                 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
-                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
+                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
                 final Runnable startBounceAnimRunnable = new Runnable() {
                     public void run() {
                         anim.playTogether(bounceAnims);
                         anim.start();
                     }
                 };
-                if (newShortcutsScreenId != currentScreenId) {
+                if (newItemsScreenId != currentScreenId) {
                     // We post the animation slightly delayed to prevent slowdowns
                     // when we are loading right after we return to launcher.
                     mWorkspace.postDelayed(new Runnable() {
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 97335cb..4cbb087 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherModel.Callbacks;
@@ -60,8 +61,8 @@
         }
         Context context = app.getContext();
 
-        final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
-        final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+        final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
+        final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
 
         // Get the list of workspace screens.  We need to append to this list and
         // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
@@ -77,13 +78,14 @@
                 }
 
                 // Find appropriate space for the item.
-                Pair<Long, int[]> coords = findSpaceForItem(
-                        app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
+                Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
+                        addedWorkspaceScreensFinal, item.spanX, item.spanY);
                 long screenId = coords.first;
                 int[] cordinates = coords.second;
 
                 ItemInfo itemInfo;
-                if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+                if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
+                        item instanceof LauncherAppWidgetInfo) {
                     itemInfo = item;
                 } else if (item instanceof AppInfo) {
                     itemInfo = ((AppInfo) item).makeShortcut();
@@ -95,23 +97,23 @@
                 addItemToDatabase(context, itemInfo, screenId, cordinates);
 
                 // Save the ShortcutInfo for binding in the workspace
-                addedShortcutsFinal.add(itemInfo);
+                addedItemsFinal.add(itemInfo);
             }
         }
 
         // Update the workspace screens
         updateScreens(context, workspaceScreens);
 
-        if (!addedShortcutsFinal.isEmpty()) {
+        if (!addedItemsFinal.isEmpty()) {
             scheduleCallbackTask(new CallbackTask() {
                 @Override
                 public void execute(Callbacks callbacks) {
                     final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
                     final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
-                    if (!addedShortcutsFinal.isEmpty()) {
-                        ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+                    if (!addedItemsFinal.isEmpty()) {
+                        ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
                         long lastScreenId = info.screenId;
-                        for (ItemInfo i : addedShortcutsFinal) {
+                        for (ItemInfo i : addedItemsFinal) {
                             if (i.screenId == lastScreenId) {
                                 addAnimated.add(i);
                             } else {