Adding support for requesting addition of shortcut/widget
on the workspace.

Bug: 33584624
Change-Id: I664366822fe8088742faff2cce006239ab0771bc
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index bbe1f4a..974b0df 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -79,5 +79,13 @@
         <meta-data android:name="android.nfc.disable_beam_default"
                        android:value="true" />
 
+        <activity android:name="com.android.launcher3.dragndrop.AddItemActivity"
+            android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+            android:label="@string/action_add_to_workspace" >
+            <intent-filter>
+                <action android:name="android.content.pm.action.CONFIRM_PIN_ITEM" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/res/drawable/bg_drag_box.xml b/res/drawable/bg_drag_box.xml
new file mode 100644
index 0000000..49b3b86
--- /dev/null
+++ b/res/drawable/bg_drag_box.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+    <stroke android:color="?android:attr/colorAccent"
+        android:dashGap="2dp"
+        android:dashWidth="4dp"
+        android:width="1dp" />
+</shape>
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
new file mode 100644
index 0000000..46d3444
--- /dev/null
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:padding="16dp"
+        android:text="@string/add_item_request_drag_hint"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:layout_margin="10dp"
+        android:gravity="center"
+        android:id="@+id/drag_target"
+        android:background="@drawable/bg_drag_box" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/buttonBarStyle"
+        android:gravity="end" >
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@android:string/cancel"
+            android:onClick="onCancelClick"
+            style="?android:attr/buttonBarButtonStyle" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/place_automatically"
+            android:onClick="onPlaceAutomaticallyClick"
+            style="?android:attr/buttonBarButtonStyle" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a9c970d..f1de623 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -52,6 +52,10 @@
     <string name="widget_dims_format">%1$d \u00d7 %2$d</string>
     <!-- Accessibility spoken message format for the dimensions of a widget in the drawer -->
     <string name="widget_accessible_dims_format">%1$d wide by %2$d high</string>
+    <!-- Message to tell the user to press and hold a widget/icon to add it  -->
+    <string name="add_item_request_drag_hint" translatable="false">Touch &amp; hold to place on home screen</string>
+    <!-- Button label to automatically add icon on home screen [CHAR_LIMIT=50] -->
+    <string name="place_automatically" translatable="false">Place automatically</string>
 
     <!-- All Apps -->
     <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
new file mode 100644
index 0000000..9f4f1f9
--- /dev/null
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.view.View.AccessibilityDelegate;
+
+public abstract class BaseActivity extends Activity {
+
+    protected DeviceProfile mDeviceProfile;
+
+    public DeviceProfile getDeviceProfile() {
+        return mDeviceProfile;
+    }
+
+    public AccessibilityDelegate getAccessibilityDelegate() {
+        return null;
+    }
+
+    public static BaseActivity fromContext(Context context) {
+        if (context instanceof BaseActivity) {
+            return (BaseActivity) context;
+        }
+        return ((BaseActivity) ((ContextWrapper) context).getBaseContext());
+    }
+}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index a35469e..96d6b89 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -498,7 +498,9 @@
                 DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
                 List<ShortcutInfoCompat> si = sm.queryForFullDetails(
                         decoder.launcherIntent.getPackage(),
-                        Arrays.asList(ShortcutInfoCompat.EXTRA_SHORTCUT_ID), decoder.user);
+                        Arrays.asList(decoder.launcherIntent.getStringExtra(
+                                ShortcutInfoCompat.EXTRA_SHORTCUT_ID)),
+                        decoder.user);
                 if (si.isEmpty()) {
                     return null;
                 } else {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 1b0e5b3..e3c6965 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,6 +18,7 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Point;
@@ -326,6 +327,11 @@
         return rank == getAllAppsButtonRank();
     }
 
+    public DeviceProfile getDeviceProfile(Context context) {
+        return context.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile;
+    }
+
     private float weight(float x0, float y0, float x1, float y1, float pow) {
         float d = dist(x0, y0, x1, y1);
         if (Float.compare(d, 0f) == 0) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fc112f3..13534b9 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -22,7 +22,6 @@
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
-import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.SearchManager;
@@ -42,7 +41,6 @@
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -136,7 +134,7 @@
 /**
  * Default launcher application.
  */
-public class Launcher extends Activity
+public class Launcher extends BaseActivity
         implements LauncherExterns, View.OnClickListener, OnLongClickListener,
                    LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
                    AccessibilityManager.AccessibilityStateChangeListener {
@@ -194,7 +192,7 @@
 
     private boolean mIsSafeModeEnabled;
 
-    static final int APPWIDGET_HOST_ID = 1024;
+    public static final int APPWIDGET_HOST_ID = 1024;
     public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
     private static final int ACTIVITY_START_DELAY = 1000;
@@ -273,8 +271,6 @@
     // it from the context.
     private SharedPreferences mSharedPrefs;
 
-    private DeviceProfile mDeviceProfile;
-
     private boolean mMoveToDefaultScreenFromNewIntent;
 
     // This is set to the view that launched the activity that navigated the user away from
@@ -359,11 +355,7 @@
         LauncherAppState app = LauncherAppState.getInstance(this);
 
         // Load configuration-specific DeviceProfile
-        mDeviceProfile =
-                getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
-                        ? app.getInvariantDeviceProfile().landscapeProfile
-                        : app.getInvariantDeviceProfile().portraitProfile;
-
+        mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
         if (Utilities.ATLEAST_NOUGAT && isInMultiWindowMode()) {
             Display display = getWindowManager().getDefaultDisplay();
             Point mwSize = new Point();
@@ -1657,10 +1649,6 @@
         return mSharedPrefs;
     }
 
-    public DeviceProfile getDeviceProfile() {
-        return mDeviceProfile;
-    }
-
     @Override
     protected void onNewIntent(Intent intent) {
         long startTime = 0;
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 0f62b31..83c978e 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -272,7 +272,7 @@
         return null;
     }
 
-    private Bitmap generatePreview(Launcher launcher, WidgetItem item, Bitmap recycle,
+    private Bitmap generatePreview(BaseActivity launcher, WidgetItem item, Bitmap recycle,
             int previewWidth, int previewHeight) {
         if (item.widgetInfo != null) {
             return generateWidgetPreview(launcher, item.widgetInfo,
@@ -294,7 +294,7 @@
      * @param preScaledWidthOut           return the width of the returned bitmap
      * @return
      */
-    public Bitmap generateWidgetPreview(Launcher launcher, LauncherAppWidgetProviderInfo info,
+    public Bitmap generateWidgetPreview(BaseActivity launcher, LauncherAppWidgetProviderInfo info,
             int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
         // Load the preview image if possible
         if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
@@ -415,7 +415,7 @@
     }
 
     private Bitmap generateShortcutPreview(
-            Launcher launcher, ActivityInfo info, int maxWidth, int maxHeight, Bitmap preview) {
+            BaseActivity launcher, ActivityInfo info, int maxWidth, int maxHeight, Bitmap preview) {
         final Canvas c = new Canvas();
         if (preview == null) {
             preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
@@ -591,7 +591,7 @@
                 // which would gets re-written next time.
                 mVersions = getPackageVersion(mKey.componentName.getPackageName());
 
-                Launcher launcher = Launcher.getLauncher(mCaller.getContext());
+                BaseActivity launcher = BaseActivity.fromContext(mCaller.getContext());
 
                 // it's not in the db... we need to generate it
                 preview = generatePreview(launcher, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
diff --git a/src/com/android/launcher3/compat/PinItemRequestCompat.java b/src/com/android/launcher3/compat/PinItemRequestCompat.java
new file mode 100644
index 0000000..956b38c
--- /dev/null
+++ b/src/com/android/launcher3/compat/PinItemRequestCompat.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.compat;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+/**
+ * A wrapper around platform implementation of PinItemRequestCompat until the
+ * updated SDK is available.
+ */
+public class PinItemRequestCompat {
+
+    public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
+
+    public static final int REQUEST_TYPE_SHORTCUT = 1;
+    public static final int REQUEST_TYPE_APPWIDGET = 2;
+
+    private final Parcelable mObject;
+
+    private PinItemRequestCompat(Parcelable object) {
+        mObject = object;
+    }
+
+    public int getRequestType() {
+        return (Integer) invokeMethod("getRequestType");
+    }
+
+    public ShortcutInfo getShortcutInfo() {
+        return (ShortcutInfo) invokeMethod("getShortcutInfo");
+    }
+
+    public AppWidgetProviderInfo getAppWidgetProviderInfo() {
+        return (AppWidgetProviderInfo) invokeMethod("getAppWidgetProviderInfo");
+    }
+
+    public boolean isValid() {
+        return (Boolean) invokeMethod("isValid");
+    }
+
+    public boolean accept() {
+        return (Boolean) invokeMethod("accept");
+    }
+
+    public boolean accept(Bundle options) {
+        try {
+            return (Boolean) mObject.getClass().getDeclaredMethod("accept", Bundle.class)
+                    .invoke(mObject, options);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Object invokeMethod(String methodName) {
+        try {
+            return mObject.getClass().getDeclaredMethod(methodName).invoke(mObject);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static PinItemRequestCompat getPinItemRequest(Intent intent) {
+        Parcelable extra = intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST);
+        return extra == null ? null : new PinItemRequestCompat(extra);
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
new file mode 100644
index 0000000..c48db60
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dragndrop;
+
+import android.annotation.TargetApi;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetHostViewLoader;
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public class AddItemActivity extends BaseActivity {
+
+    private static final int REQUEST_BIND_APPWIDGET = 1;
+    private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id";
+
+    private PinItemRequestCompat mRequest;
+    private LauncherAppState mApp;
+    private InvariantDeviceProfile mIdp;
+
+    private TextView mTextView;
+
+    // Widget request specific options.
+    private AppWidgetHost mAppWidgetHost;
+    private AppWidgetManagerCompat mAppWidgetManager;
+    private PendingAddWidgetInfo mPendingWidgetInfo;
+    private int mPendingBindWidgetId;
+    private Bundle mWidgetOptions;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mRequest = PinItemRequestCompat.getPinItemRequest(getIntent());
+        if (mRequest == null) {
+            finish();
+            return;
+        }
+
+        setContentView(R.layout.add_item_confirmation_activity);
+        mTextView = (TextView) findViewById(R.id.drag_target);
+
+        mApp = LauncherAppState.getInstance(this);
+        mIdp = mApp.getInvariantDeviceProfile();
+
+        // Use the application context to get the device profile, as in multiwindow-mode, the
+        // confirmation activity might be rotated.
+        mDeviceProfile = mIdp.getDeviceProfile(getApplicationContext());
+
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+            setupShortcut();
+        } else {
+            if (!setupWidget()) {
+                // TODO: show error toast?
+                finish();
+            }
+        }
+    }
+
+    private void setupShortcut() {
+        ShortcutInfoCompat shortcut = new ShortcutInfoCompat(mRequest.getShortcutInfo());
+        FastBitmapDrawable d = new FastBitmapDrawable(LauncherIcons.createIconBitmap(
+                DeepShortcutManager.getInstance(this).getShortcutIconDrawable(
+                        shortcut, mIdp.fillResIconDpi), this));
+        d.setFilterBitmap(true);
+        mTextView.setText(TextUtils.isEmpty(shortcut.getLongLabel())
+                ? shortcut.getShortLabel() : shortcut.getLongLabel());
+        mTextView.setCompoundDrawables(null, d, null, null);
+    }
+
+    private boolean setupWidget() {
+        AppWidgetProviderInfo info = mRequest.getAppWidgetProviderInfo();
+        LauncherAppWidgetProviderInfo widgetInfo = AppWidgetManagerCompat.getInstance(this)
+                .findProvider(info.provider, info.getProfile());
+        if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) {
+            // Cannot add widget
+            return false;
+        }
+
+        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
+        mAppWidgetHost = new AppWidgetHost(this, Launcher.APPWIDGET_HOST_ID);
+
+        mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo);
+        mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX);
+        mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY);
+        mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo);
+
+        Bitmap preview = mApp.getWidgetCache().generateWidgetPreview(this, widgetInfo,
+                mPendingWidgetInfo.spanX * mDeviceProfile.cellWidthPx, null, null);
+        FastBitmapDrawable d = new FastBitmapDrawable(preview);
+        d.setFilterBitmap(true);
+        mTextView.setText(widgetInfo.getLabel(getPackageManager()));
+        mTextView.setCompoundDrawables(null, d, null, null);
+        return true;
+    }
+
+    /**
+     * Called when the cancel button is clicked.
+     */
+    public void onCancelClick(View v) {
+        finish();
+    }
+
+    /**
+     * Called when place-automatically button is clicked.
+     */
+    public void onPlaceAutomaticallyClick(View v) {
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+            InstallShortcutReceiver.queueShortcut(
+                    new ShortcutInfoCompat(mRequest.getShortcutInfo()), this);
+            mRequest.accept();
+            finish();
+            return;
+        }
+
+        mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId();
+        boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+                mPendingBindWidgetId, mRequest.getAppWidgetProviderInfo(), mWidgetOptions);
+        if (success) {
+            acceptWidget(mPendingBindWidgetId);
+            return;
+        }
+
+        // request bind widget
+        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER,
+                mPendingWidgetInfo.componentName);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
+                mRequest.getAppWidgetProviderInfo().getProfile());
+        startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+    }
+
+    private void acceptWidget(int widgetId) {
+        InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(), widgetId, this);
+        mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+        mRequest.accept(mWidgetOptions);
+        finish();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_BIND_APPWIDGET) {
+            int widgetId = data != null
+                    ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId)
+                    : mPendingBindWidgetId;
+            if (resultCode == RESULT_OK) {
+                acceptWidget(widgetId);
+            } else {
+                // Simply wait it out.
+                mAppWidgetHost.deleteAppWidgetId(widgetId);
+                mPendingBindWidgetId = -1;
+            }
+            return;
+        }
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        mPendingBindWidgetId = savedInstanceState
+                .getInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
+    }
+}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index a03dd8f..c97b3b5 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -70,7 +70,8 @@
         ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
         synchronized(dataModel) {
             for (ItemInfo item : workspaceApps) {
-                if (item instanceof ShortcutInfo) {
+                if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                        item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                     // Short-circuit this logic if the icon exists somewhere on the workspace
                     if (shortcutExists(dataModel, item.getIntent(), item.user)) {
                         continue;
@@ -143,6 +144,10 @@
      */
     protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) {
         final String intentWithPkg, intentWithoutPkg;
+        if (intent == null) {
+            // Skip items with null intents
+            return true;
+        }
         if (intent.getComponent() != null) {
             // If component is not null, an intent with null package will produce
             // the same result and should also be a match.
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 5eeae87..9e2091b 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -28,8 +27,8 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.StylusEventHelper;
@@ -72,7 +71,7 @@
     private PreviewLoadRequest mActiveRequest;
     private StylusEventHelper mStylusEventHelper;
 
-    private final Launcher mLauncher;
+    private final BaseActivity mActivity;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -85,18 +84,17 @@
     public WidgetCell(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        final Resources r = context.getResources();
-        mLauncher = Launcher.getLauncher(context);
+        mActivity = BaseActivity.fromContext(context);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         setContainerWidth();
         setWillNotDraw(false);
         setClipToPadding(false);
-        setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
+        setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
     }
 
     private void setContainerWidth() {
-        DeviceProfile profile = mLauncher.getDeviceProfile();
+        DeviceProfile profile = mActivity.getDeviceProfile();
         cellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
         mPresetPreviewSize = (int) (cellSize * PREVIEW_SCALE);
     }