Merge "Fixing loadWorkspace" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 974b0df..5588289 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -76,11 +76,20 @@
             android:process=":wallpaper_chooser">
         </service>
 
+        <service android:name="com.android.launcher3.badging.NotificationListener"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
         <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:excludeFromRecents="true"
+            android:autoRemoveFromRecents="true"
             android:label="@string/action_add_to_workspace" >
             <intent-filter>
                 <action android:name="android.content.pm.action.CONFIRM_PIN_ITEM" />
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
deleted file mode 100644
index 052e5d0..0000000
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 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.content.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-
-/**
- * Drop target used when another window (i.e. another process) has accepted a global system drag.
- * If the accepted item was a shortcut, we delete it from Launcher.
- */
-public class AnotherWindowDropTarget implements DropTarget {
-    final Launcher mLauncher;
-
-    public AnotherWindowDropTarget (Context context) { mLauncher = Launcher.getLauncher(context); }
-
-    @Override
-    public boolean isDropEnabled() { return true; }
-
-    @Override
-    public void onDrop(DragObject dragObject) {
-        dragObject.deferDragViewCleanupPostAnimation = false;
-        LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo);
-    }
-
-    @Override
-    public void onDragEnter(DragObject dragObject) {}
-
-    @Override
-    public void onDragOver(DragObject dragObject) {}
-
-    @Override
-    public void onDragExit(DragObject dragObject) {}
-
-    @Override
-    public boolean acceptDrop(DragObject dragObject) {
-        return dragObject.dragInfo instanceof ShortcutInfo;
-    }
-
-    @Override
-    public void prepareAccessibilityDrop() {}
-
-    // These methods are implemented in Views
-    @Override
-    public void getHitRectRelativeToDragLayer(Rect outRect) {}
-}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index d9e9c7b..47a5b4f 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -39,14 +39,16 @@
 
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
-import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.badging.NotificationInfo;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.model.PackageItemInfo;
 
 import java.text.NumberFormat;
+import java.util.List;
 
 /**
  * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
@@ -168,6 +170,8 @@
         if (promiseStateChanged || info.isPromise()) {
             applyPromiseState(promiseStateChanged);
         }
+
+        applyBadgeState(info);
     }
 
     public void applyFromApplicationInfo(AppInfo info) {
@@ -178,6 +182,8 @@
 
         // Verify high res immediately
         verifyHighRes();
+
+        applyBadgeState(info);
     }
 
     public void applyFromPackageItemInfo(PackageItemInfo info) {
@@ -502,8 +508,9 @@
         }
     }
 
-    public void applyBadgeState(BadgeInfo badgeInfo) {
+    public void applyBadgeState(ItemInfo itemInfo) {
         if (mIcon instanceof FastBitmapDrawable) {
+            BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
             BadgeRenderer badgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
             ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer);
         }
@@ -634,7 +641,8 @@
      * Returns true if the view can show custom shortcuts.
      */
     public boolean hasDeepShortcuts() {
-        return !mLauncher.getShortcutIdsForItem((ItemInfo) getTag()).isEmpty();
+        return !mLauncher.getPopupDataProvider().getShortcutIdsForItem((ItemInfo) getTag())
+                .isEmpty();
     }
 
     /**
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index b3e59f9..587d445 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -30,12 +30,13 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.animation.DecelerateInterpolator;
 
-import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.graphics.IconPalette;
 
 public class FastBitmapDrawable extends Drawable {
     private static final float DISABLED_DESATURATION = 1f;
@@ -123,13 +124,17 @@
     }
 
     public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) {
+        boolean wasBadged = mBadgeInfo != null;
+        boolean isBadged = badgeInfo != null;
         mBadgeInfo = badgeInfo;
         mBadgeRenderer = badgeRenderer;
-        if (mIconPalette == null) {
-            mIconPalette = IconPalette.fromDominantColor(Utilities
-                    .findDominantColorByHue(mBitmap, 20));
+        if (wasBadged || isBadged) {
+            if (mBadgeInfo != null && mIconPalette == null) {
+                mIconPalette = IconPalette.fromDominantColor(Utilities
+                        .findDominantColorByHue(mBitmap, 20));
+            }
+            invalidateSelf();
         }
-        invalidateSelf();
     }
 
     @Override
@@ -157,7 +162,7 @@
     }
 
     private boolean hasBadge() {
-        return mBadgeInfo != null && mBadgeInfo.getNotificationCount() != null;
+        return mBadgeInfo != null && mBadgeInfo.getNotificationCount() != 0;
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8d8a70c..26e388d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -49,6 +49,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.StrictMode;
 import android.os.SystemClock;
@@ -84,6 +85,8 @@
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DefaultAppSearchController;
 import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.badging.NotificationListener;
+import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PinItemRequestCompat;
@@ -94,6 +97,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.PinItemDragListener;
 import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -117,6 +121,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
@@ -131,10 +136,10 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Default launcher application.
@@ -261,8 +266,7 @@
     private boolean mHasFocus = false;
     private boolean mAttached = false;
 
-    /** Maps launcher activity components to their list of shortcut ids. */
-    private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+    private PopupDataProvider mPopupDataProvider;
 
     private View.OnTouchListener mHapticFeedbackTouchListener;
 
@@ -395,6 +399,8 @@
         mExtractedColors = new ExtractedColors();
         loadExtractedColorsAndColorItems();
 
+        mPopupDataProvider = new PopupDataProvider(this);
+
         ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
                 .addAccessibilityStateChangeListener(this);
 
@@ -653,6 +659,10 @@
         return (int) info.id;
     }
 
+    public PopupDataProvider getPopupDataProvider() {
+        return mPopupDataProvider;
+    }
+
     /**
      * 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.
@@ -826,7 +836,7 @@
     }
 
     @Override
-    protected void onActivityResult(
+    public void onActivityResult(
             final int requestCode, final int resultCode, final Intent data) {
         handleActivityResult(requestCode, resultCode, data);
         if (mLauncherCallbacks != null) {
@@ -928,6 +938,8 @@
         if (Utilities.ATLEAST_NOUGAT_MR1) {
             mAppWidgetHost.stopListening();
         }
+
+        NotificationListener.removeNotificationsChangedListener();
     }
 
     @Override
@@ -942,6 +954,10 @@
         if (Utilities.ATLEAST_NOUGAT_MR1) {
             mAppWidgetHost.startListening();
         }
+
+        if (!isWorkspaceLoading()) {
+            NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+        }
     }
 
     @Override
@@ -1566,6 +1582,19 @@
         }
     };
 
+    public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
+        Runnable r = new Runnable() {
+            @Override
+            public void run() {
+                mWorkspace.updateIconBadges(updatedBadges);
+                mAppsView.updateIconBadges(updatedBadges);
+            }
+        };
+        if (!waitUntilResume(r)) {
+            r.run();
+        }
+    }
+
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -1752,6 +1781,14 @@
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onHomeIntent();
             }
+
+            Parcelable dragExtra = intent
+                    .getParcelableExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER);
+            if (dragExtra instanceof PinItemDragListener) {
+                PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
+                dragListener.setLauncher(this);
+                mDragLayer.setOnDragListener(dragListener);
+            }
         }
 
         if (mLauncherCallbacks != null) {
@@ -3646,6 +3683,8 @@
 
         InstallShortcutReceiver.disableAndFlushInstallQueue(this);
 
+        NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.finishBindingItems(false);
         }
@@ -3715,21 +3754,7 @@
      */
     @Override
     public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
-        mDeepShortcutMap = deepShortcutMapCopy;
-        if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
-    }
-
-    public List<String> getShortcutIdsForItem(ItemInfo info) {
-        if (!DeepShortcutManager.supportsShortcuts(info)) {
-            return Collections.EMPTY_LIST;
-        }
-        ComponentName component = info.getTargetComponent();
-        if (component == null) {
-            return Collections.EMPTY_LIST;
-        }
-
-        List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
-        return ids == null ? Collections.EMPTY_LIST : ids;
+        mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
     }
 
     /**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6eb87f2..e646dd9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -78,6 +78,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.MultiStateAlphaController;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.VerticalFlingDetector;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -86,6 +87,7 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.Set;
 
 /**
  * The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -3957,6 +3959,23 @@
         });
     }
 
+    public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
+        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+        mapOverItems(MAP_RECURSE, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View v) {
+                if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
+                    packageUserKey.updateFromItemInfo(info);
+                    if (updatedBadges.contains(packageUserKey)) {
+                        ((BubbleTextView) v).applyBadgeState(info);
+                    }
+                }
+                // process all the shortcuts
+                return false;
+            }
+        });
+    }
+
     public void removeAbandonedPromise(String packageName, UserHandle user) {
         HashSet<String> packages = new HashSet<>(1);
         packages.add(packageName);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index a2266fe..ec1fa34 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -53,9 +53,11 @@
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * The all apps view container.
@@ -472,4 +474,16 @@
             navBarBg.setVisibility(View.VISIBLE);
         }
     }
+
+    public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
+        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+        for (AlphabeticalAppsList.AdapterItem app : mApps.getAdapterItems()) {
+            if (app.appInfo != null) {
+                packageUserKey.updateFromItemInfo(app.appInfo);
+                if (updatedBadges.contains(packageUserKey)) {
+                    mAdapter.notifyItemChanged(app.position);
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
index 0a9f87c..98d2277 100644
--- a/src/com/android/launcher3/badge/BadgeInfo.java
+++ b/src/com/android/launcher3/badge/BadgeInfo.java
@@ -16,18 +16,60 @@
 
 package com.android.launcher3.badge;
 
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Contains data to be used in an icon badge.
  */
 public class BadgeInfo {
 
-    private int mNotificationCount;
+    /** Used to link this BadgeInfo to icons on the workspace and all apps */
+    private PackageUserKey mPackageUserKey;
+    /**
+     * The keys of the notifications that this badge represents. These keys can later be
+     * used to retrieve {@link com.android.launcher3.badging.NotificationInfo}'s.
+     */
+    private Set<String> mNotificationKeys;
 
-    public void setNotificationCount(int count) {
-        mNotificationCount = count;
+    public BadgeInfo(PackageUserKey packageUserKey) {
+        mPackageUserKey = packageUserKey;
+        mNotificationKeys = new HashSet<>();
     }
 
-    public String getNotificationCount() {
-        return mNotificationCount == 0 ? null : String.valueOf(mNotificationCount);
+    /**
+     * Returns whether the notification was added (false if it already existed).
+     */
+    public boolean addNotificationKey(String notificationKey) {
+        return mNotificationKeys.add(notificationKey);
+    }
+
+    /**
+     * Returns whether the notification was removed (false if it didn't exist).
+     */
+    public boolean removeNotificationKey(String notificationKey) {
+        return mNotificationKeys.remove(notificationKey);
+    }
+
+    public Set<String> getNotificationKeys() {
+        return mNotificationKeys;
+    }
+
+    public int getNotificationCount() {
+        return mNotificationKeys.size();
+    }
+
+    /**
+     * Whether newBadge represents the same PackageUserKey as this badge, and icons with
+     * this badge should be invalidated. So, for instance, if a badge has 3 notifications
+     * and one of those notifications is updated, this method should return false because
+     * the badge still says "3" and the contents of those notifications are only retrieved
+     * upon long-click. This method always returns true when adding or removing notifications.
+     */
+    public boolean shouldBeInvalidated(BadgeInfo newBadge) {
+        return mPackageUserKey.equals(newBadge.mPackageUserKey)
+                && getNotificationCount() != newBadge.getNotificationCount();
     }
 }
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 238b918..787ee72 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -61,7 +61,7 @@
         mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right,
                 iconBounds.top + size);
         canvas.drawOval(mBackgroundRect, mBackgroundPaint);
-        String notificationCount = badgeInfo.getNotificationCount();
+        String notificationCount = String.valueOf(badgeInfo.getNotificationCount());
         canvas.drawText(notificationCount,
                 mBackgroundRect.centerX(),
                 mBackgroundRect.centerY() + mTextHeight / 2,
diff --git a/src/com/android/launcher3/badging/NotificationInfo.java b/src/com/android/launcher3/badging/NotificationInfo.java
new file mode 100644
index 0000000..2590add
--- /dev/null
+++ b/src/com/android/launcher3/badging/NotificationInfo.java
@@ -0,0 +1,82 @@
+/*
+ * 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.badging;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+import com.android.launcher3.util.PackageUserKey;
+
+/**
+ * An object that contains relevant information from a {@link StatusBarNotification}. This should
+ * only be created when we need to show the notification contents on the UI; until then, a
+ * {@link com.android.launcher3.badge.BadgeInfo} with only the notification key should
+ * be passed around, and then this can be constructed using the StatusBarNotification from
+ * {@link NotificationListener#getNotificationsForKeys(String[])}.
+ */
+public class NotificationInfo implements View.OnClickListener {
+
+    public final PackageUserKey packageUserKey;
+    public final String notificationKey;
+    public final CharSequence title;
+    public final CharSequence text;
+    public final Drawable iconDrawable;
+    public final PendingIntent intent;
+    public final boolean autoCancel;
+
+    /**
+     * Extracts the data that we need from the StatusBarNotification.
+     */
+    public NotificationInfo(Context context, StatusBarNotification notification) {
+        packageUserKey = PackageUserKey.fromNotification(notification);
+        notificationKey = notification.getKey();
+        title = notification.getNotification().extras.getCharSequence(Notification.EXTRA_TITLE);
+        text = notification.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT);
+        Icon icon = notification.getNotification().getLargeIcon();
+        if (icon == null) {
+            icon = notification.getNotification().getSmallIcon();
+            iconDrawable = icon.loadDrawable(context);
+            iconDrawable.setTint(notification.getNotification().color);
+        } else {
+            iconDrawable = icon.loadDrawable(context);
+        }
+        intent = notification.getNotification().contentIntent;
+        autoCancel = (notification.getNotification().flags
+                & Notification.FLAG_AUTO_CANCEL) != 0;
+    }
+
+    @Override
+    public void onClick(View view) {
+        final Launcher launcher = Launcher.getLauncher(view.getContext());
+        try {
+            intent.send();
+        } catch (PendingIntent.CanceledException e) {
+            e.printStackTrace();
+        }
+        if (autoCancel) {
+            launcher.getPopupDataProvider().cancelNotification(notificationKey);
+        }
+        DeepShortcutsContainer.getOpen(launcher).close(true);
+    }
+}
diff --git a/src/com/android/launcher3/badging/NotificationListener.java b/src/com/android/launcher3/badging/NotificationListener.java
new file mode 100644
index 0000000..0a85d56
--- /dev/null
+++ b/src/com/android/launcher3/badging/NotificationListener.java
@@ -0,0 +1,213 @@
+/*
+ * 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.badging;
+
+import android.app.Notification;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.Nullable;
+import android.support.v4.util.Pair;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link NotificationListenerService} that sends updates to its
+ * {@link NotificationsChangedListener} when notifications are posted or canceled,
+ * as well and when this service first connects. An instance of NotificationListener,
+ * and its methods for getting notifications, can be obtained via {@link #getInstance()}.
+ */
+public class NotificationListener extends NotificationListenerService {
+
+    private static final int MSG_NOTIFICATION_POSTED = 1;
+    private static final int MSG_NOTIFICATION_REMOVED = 2;
+    private static final int MSG_NOTIFICATION_FULL_REFRESH = 3;
+
+    private static NotificationListener sNotificationListenerInstance = null;
+    private static NotificationsChangedListener sNotificationsChangedListener;
+
+    private final Handler mWorkerHandler;
+    private final Handler mUiHandler;
+
+    private Handler.Callback mWorkerCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_NOTIFICATION_POSTED:
+                    mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
+                    break;
+                case MSG_NOTIFICATION_REMOVED:
+                    mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
+                    break;
+                case MSG_NOTIFICATION_FULL_REFRESH:
+                    final List<StatusBarNotification> activeNotifications
+                            = filterNotifications(getActiveNotifications());
+                    mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
+                    break;
+            }
+            return true;
+        }
+    };
+
+    private Handler.Callback mUiCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_NOTIFICATION_POSTED:
+                    if (sNotificationsChangedListener != null) {
+                        Pair<PackageUserKey, String> pair
+                                = (Pair<PackageUserKey, String>) message.obj;
+                        sNotificationsChangedListener.onNotificationPosted(pair.first, pair.second);
+                    }
+                    break;
+                case MSG_NOTIFICATION_REMOVED:
+                    if (sNotificationsChangedListener != null) {
+                        Pair<PackageUserKey, String> pair
+                                = (Pair<PackageUserKey, String>) message.obj;
+                        sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second);
+                    }
+                    break;
+                case MSG_NOTIFICATION_FULL_REFRESH:
+                    if (sNotificationsChangedListener != null) {
+                        sNotificationsChangedListener.onNotificationFullRefresh(
+                                (List<StatusBarNotification>) message.obj);
+                    }
+                    break;
+            }
+            return true;
+        }
+    };
+
+    public NotificationListener() {
+        super();
+        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback);
+        mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
+    }
+
+    public static @Nullable NotificationListener getInstance() {
+        return sNotificationListenerInstance;
+    }
+
+    public static void setNotificationsChangedListener(NotificationsChangedListener listener) {
+        if (!FeatureFlags.BADGE_ICONS) {
+            return;
+        }
+        sNotificationsChangedListener = listener;
+
+        NotificationListener notificationListener = getInstance();
+        if (notificationListener != null) {
+            notificationListener.onNotificationFullRefresh();
+        }
+    }
+
+    public static void removeNotificationsChangedListener() {
+        sNotificationsChangedListener = null;
+    }
+
+    @Override
+    public void onListenerConnected() {
+        super.onListenerConnected();
+        sNotificationListenerInstance = this;
+        onNotificationFullRefresh();
+    }
+
+    private void onNotificationFullRefresh() {
+        mWorkerHandler.obtainMessage(MSG_NOTIFICATION_FULL_REFRESH).sendToTarget();
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        super.onListenerDisconnected();
+        sNotificationListenerInstance = null;
+    }
+
+    @Override
+    public void onNotificationPosted(final StatusBarNotification sbn) {
+        super.onNotificationPosted(sbn);
+        if (!shouldBeFilteredOut(sbn.getNotification())) {
+            Pair<PackageUserKey, String> packageUserKeyAndNotificationKey
+                    = new Pair<>(PackageUserKey.fromNotification(sbn), sbn.getKey());
+            mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, packageUserKeyAndNotificationKey)
+                    .sendToTarget();
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(final StatusBarNotification sbn) {
+        super.onNotificationRemoved(sbn);
+        if (!shouldBeFilteredOut(sbn.getNotification())) {
+            Pair<PackageUserKey, String> packageUserKeyAndNotificationKey
+                    = new Pair<>(PackageUserKey.fromNotification(sbn), sbn.getKey());
+            mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey)
+                    .sendToTarget();
+        }
+    }
+
+    /** This makes a potentially expensive binder call and should be run on a background thread. */
+    public List<StatusBarNotification> getNotificationsForKeys(String[] keys) {
+        StatusBarNotification[] notifications = NotificationListener.this
+                .getActiveNotifications(keys);
+        return notifications == null ? Collections.EMPTY_LIST : Arrays.asList(notifications);
+    }
+
+    /**
+     * Filter out notifications that don't have an intent
+     * or are headers for grouped notifications.
+     *
+     * TODO: use the system concept of a badged notification instead
+     */
+    private List<StatusBarNotification> filterNotifications(
+            StatusBarNotification[] notifications) {
+        if (notifications == null) return null;
+        Set<Integer> removedNotifications = new HashSet<>();
+        for (int i = 0; i < notifications.length; i++) {
+            if (shouldBeFilteredOut(notifications[i].getNotification())) {
+                removedNotifications.add(i);
+            }
+        }
+        List<StatusBarNotification> filteredNotifications = new ArrayList<>(
+                notifications.length - removedNotifications.size());
+        for (int i = 0; i < notifications.length; i++) {
+            if (!removedNotifications.contains(i)) {
+                filteredNotifications.add(notifications[i]);
+            }
+        }
+        return filteredNotifications;
+    }
+
+    private boolean shouldBeFilteredOut(Notification notification) {
+        boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
+        return (notification.contentIntent == null || isGroupHeader);
+    }
+
+    public interface NotificationsChangedListener {
+        void onNotificationPosted(PackageUserKey postedPackageUserKey, String notificationKey);
+        void onNotificationRemoved(PackageUserKey removedPackageUserKey, String notificationKey);
+        void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications);
+    }
+}
diff --git a/src/com/android/launcher3/compat/PinItemRequestCompat.java b/src/com/android/launcher3/compat/PinItemRequestCompat.java
index 74d7765..481310a 100644
--- a/src/com/android/launcher3/compat/PinItemRequestCompat.java
+++ b/src/com/android/launcher3/compat/PinItemRequestCompat.java
@@ -21,13 +21,14 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
+import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
  * A wrapper around platform implementation of PinItemRequestCompat until the
  * updated SDK is available.
  */
-public class PinItemRequestCompat {
+public class PinItemRequestCompat implements Parcelable {
 
     public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
 
@@ -83,6 +84,32 @@
         }
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        parcel.writeParcelable(mObject, i);
+    }
+
+    public Intent toIntent() {
+        return new Intent().putExtra(EXTRA_PIN_ITEM_REQUEST, mObject);
+    }
+
+    public static final Parcelable.Creator<PinItemRequestCompat> CREATOR =
+            new Parcelable.Creator<PinItemRequestCompat>() {
+                public PinItemRequestCompat createFromParcel(Parcel source) {
+                    Parcelable object = source.readParcelable(null);
+                    return new PinItemRequestCompat(object);
+                }
+
+                public PinItemRequestCompat[] newArray(int size) {
+                    return new PinItemRequestCompat[size];
+                }
+            };
+
     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/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
index ece7759..1cfbd20 100644
--- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
@@ -35,6 +35,7 @@
 import android.widget.Toast;
 
 import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
@@ -67,7 +68,7 @@
 
     public abstract Drawable getFullResIcon(IconCache cache);
 
-    public boolean startConfigActivity(Activity activity, int requestCode) {
+    public boolean startConfigActivity(Launcher activity, int requestCode) {
         Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
                 .setComponent(getComponent());
         try {
@@ -136,7 +137,7 @@
         }
 
         @Override
-        public boolean startConfigActivity(Activity activity, int requestCode) {
+        public boolean startConfigActivity(Launcher activity, int requestCode) {
             if (getUser().equals(Process.myUserHandle())) {
                 return super.startConfigActivity(activity, requestCode);
             }
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 6c6f141..423fdab 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -17,12 +17,23 @@
 package com.android.launcher3.dragndrop;
 
 import android.annotation.TargetApi;
+import android.app.ActivityOptions;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
 import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.*;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -38,13 +49,18 @@
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.WidgetHostViewLoader;
+import com.android.launcher3.widget.WidgetImageView;
 
 @TargetApi(Build.VERSION_CODES.N_MR1)
-public class AddItemActivity extends BaseActivity {
+public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
+
+    private static final int SHADOW_SIZE = 10;
 
     private static final int REQUEST_BIND_APPWIDGET = 1;
     private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id";
 
+    private final PointF mLastTouchPos = new PointF();
+
     private PinItemRequestCompat mRequest;
     private LauncherAppState mApp;
     private InvariantDeviceProfile mIdp;
@@ -86,11 +102,54 @@
                 finish();
             }
         }
+
+        mWidgetCell.setOnTouchListener(this);
+        mWidgetCell.setOnLongClickListener(this);
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        mLastTouchPos.set(motionEvent.getX(), motionEvent.getY());
+        return false;
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        // Find the position of the preview relative to the touch location.
+        WidgetImageView img = mWidgetCell.getWidgetView();
+        Rect bounds = img.getBitmapBounds();
+        bounds.offset(img.getLeft() - (int) mLastTouchPos.x, img.getTop() - (int) mLastTouchPos.y);
+
+        // Start home and pass the draw request params
+        PinItemDragListener listener = new PinItemDragListener(mRequest, bounds);
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(getPackageName())
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
+        startActivity(homeIntent,
+                ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+
+        // Start a system drag and drop. We use a transparent bitmap as preview for system drag
+        // as the preview is handled internally by launcher.
+        ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()});
+        ClipData data = new ClipData(description, new ClipData.Item(""));
+        view.startDragAndDrop(data, new DragShadowBuilder(view) {
+
+            @Override
+            public void onDrawShadow(Canvas canvas) { }
+
+            @Override
+            public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+                outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE);
+                outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
+            }
+        }, null, View.DRAG_FLAG_GLOBAL);
+        return false;
     }
 
     private void setupShortcut() {
-        WidgetItem item = new WidgetItem(new PinShortcutRequestActivityInfo(
-                mRequest.getShortcutInfo(), this));
+        WidgetItem item = new WidgetItem(new PinShortcutRequestActivityInfo(mRequest, this));
         mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
         mWidgetCell.ensurePreview();
     }
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
deleted file mode 100644
index 1623010..0000000
--- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 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.content.Context;
-import android.view.View;
-
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
-/**
- * DragSource used when the drag started at another window.
- */
-public class AnotherWindowDragSource implements DragSource {
-
-    private final Context mContext;
-
-    AnotherWindowDragSource(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return false;
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 1;
-    }
-
-    @Override
-    public void onDropCompleted(View target, DragObject d,
-            boolean isFlingToDelete, boolean success) {
-        if (!success) {
-            Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null);
-        }
-
-    }
-
-    @Override
-    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
-        // TODO: Probably log something
-    }
-}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 745776d..80c2860 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
@@ -447,7 +446,8 @@
     /**
      * Call this from a drag source view.
      */
-    public boolean onDragEvent(DragEvent event) {
+    public boolean onDragEvent(long dragStartTime, DragEvent event) {
+        mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
         return mDragDriver != null && mDragDriver.onDragEvent(event);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index a90cfff..65c0f29 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -16,20 +16,13 @@
 
 package com.android.launcher3.dragndrop;
 
-import android.content.ClipData;
-import android.content.ClipDescription;
 import android.content.Context;
-import android.content.Intent;
 import android.view.DragEvent;
 import android.view.MotionEvent;
 
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 
-import java.util.ArrayList;
-
 /**
  * Base class for driving a drag/drop operation.
  */
@@ -107,7 +100,6 @@
     private final DragObject mDragObject;
     private final Context mContext;
 
-    boolean mReceivedDropEvent = false;
     float mLastX = 0;
     float mLastY = 0;
 
@@ -149,65 +141,21 @@
             case DragEvent.ACTION_DROP:
                 mLastX = event.getX();
                 mLastY = event.getY();
-                mReceivedDropEvent =
-                        updateInfoFromClipData(event.getClipData(), event.getClipDescription());
-                return mReceivedDropEvent;
-
+                mEventListener.onDriverDragMove(event.getX(), event.getY());
+                mEventListener.onDriverDragEnd(mLastX, mLastY);
+                return true;
             case DragEvent.ACTION_DRAG_EXITED:
                 mEventListener.onDriverDragExitWindow();
                 return true;
 
             case DragEvent.ACTION_DRAG_ENDED:
-                if (mReceivedDropEvent) {
-                    mEventListener.onDriverDragEnd(mLastX, mLastY);
-                } else {
-                    mEventListener.onDriverDragCancel();
-                }
+                mEventListener.onDriverDragCancel();
                 return true;
 
             default:
                 return false;
         }
     }
-
-    private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) {
-        if (data == null) {
-            return false;
-        }
-        ArrayList<Intent> intents = new ArrayList<>();
-        int itemCount = data.getItemCount();
-        for (int i = 0; i < itemCount; i++) {
-            Intent intent = data.getItemAt(i).getIntent();
-            if (intent == null) {
-                continue;
-            }
-
-            // Give preference to shortcut intents.
-            if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
-                intents.add(intent);
-                continue;
-            }
-            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent);
-            if (info != null) {
-                mDragObject.dragInfo = info;
-                return true;
-            }
-            return true;
-        }
-
-        // Try creating shortcuts just using the intent and label
-        Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel());
-        for (Intent intent : intents) {
-            fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
-            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent);
-            if (info != null) {
-                mDragObject.dragInfo = info;
-                return true;
-            }
-        }
-
-        return false;
-    }
 }
 
 /**
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 4279cc3..de416e3 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -21,21 +21,13 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
-import android.content.ClipDescription;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Build;
 import android.util.AttributeSet;
-import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -59,9 +51,7 @@
 import com.android.launcher3.PinchToOverviewListener;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
@@ -373,49 +363,6 @@
         return false;
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
-    private void handleSystemDragStart(DragEvent event) {
-        if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.ATLEAST_NOUGAT) {
-            return;
-        }
-        if (mLauncher.isWorkspaceLocked()) {
-            return;
-        }
-
-        ClipDescription description = event.getClipDescription();
-        if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
-            return;
-        }
-        ShortcutInfo info = new ShortcutInfo();
-        // Set a dummy intent until we get the final value
-        info.intent = new Intent();
-
-        // Since we are not going through the workspace for starting the drag, set drag related
-        // information on the workspace before starting the drag.
-        ExternalDragPreviewProvider previewProvider =
-                new ExternalDragPreviewProvider(mLauncher, info);
-        mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
-
-        DragOptions options = new DragOptions();
-        options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY());
-
-        int halfPadding = previewProvider.previewPadding / 2;
-        mDragController.startDrag(
-                Bitmap.createBitmap(1, 1, Config.ARGB_8888),
-                0, 0,
-                new AnotherWindowDragSource(mLauncher), info,
-                new Point(- halfPadding, halfPadding),
-                previewProvider.getPreviewBounds(), 1f, options);
-    }
-
-    @Override
-    public boolean onDragEvent (DragEvent event) {
-        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
-            handleSystemDragStart(event);
-        }
-        return mDragController.onDragEvent(event);
-    }
-
     /**
      * Determine the rect of the descendant in this DragLayer's coordinates
      *
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
deleted file mode 100644
index e558487..0000000
--- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2016 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.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.HolographicOutlineHelper;
-
-/**
- * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
- * a different window.
- * It just draws an empty circle to a placeholder outline.
- */
-public class ExternalDragPreviewProvider extends DragPreviewProvider {
-
-    private final Launcher mLauncher;
-    private final ItemInfo mAddInfo;
-
-    private final int[] mOutlineSize;
-
-    public ExternalDragPreviewProvider(Launcher launcher, ItemInfo addInfo) {
-        super(null, launcher);
-        mLauncher = launcher;
-        mAddInfo = addInfo;
-
-        mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false, false);
-    }
-
-    public Rect getPreviewBounds() {
-        Rect rect = new Rect();
-        DeviceProfile dp = mLauncher.getDeviceProfile();
-        rect.left = blurSizeOutline / 2;
-        rect.top = (mOutlineSize[1] - dp.cellHeightPx) / 2;
-        rect.right = rect.left + dp.iconSizePx;
-        rect.bottom = rect.top + dp.iconSizePx;
-        return rect;
-    }
-
-    @Override
-    public Bitmap createDragOutline(Canvas canvas) {
-        final Bitmap b = Bitmap.createBitmap(mOutlineSize[0], mOutlineSize[1], Bitmap.Config.ALPHA_8);
-        canvas.setBitmap(b);
-
-        Paint paint = new Paint();
-        paint.setColor(Color.WHITE);
-        paint.setStyle(Paint.Style.FILL);
-
-        // Use 0.9f times the radius for the actual circle to account for icon normalization.
-        float radius = getPreviewBounds().width() * 0.5f;
-        canvas.drawCircle(blurSizeOutline / 2 + radius,
-                blurSizeOutline / 2 + radius, radius * 0.9f, paint);
-
-        HolographicOutlineHelper.getInstance(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
-        canvas.setBitmap(null);
-        return b;
-    }
-}
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index a2aa27d..e794744 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.dragndrop;
 
 import android.graphics.PointF;
+import android.os.SystemClock;
+import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
@@ -53,6 +55,31 @@
         mVelocityTracker.addMovement(ev);
     }
 
+    /**
+     * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
+     * using {@param event} for tracking velocity.
+     */
+    public void recordDragEvent(long dragStartTime, DragEvent event) {
+        final int motionAction;
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                motionAction = MotionEvent.ACTION_DOWN;
+                break;
+            case DragEvent.ACTION_DRAG_LOCATION:
+                motionAction = MotionEvent.ACTION_MOVE;
+                break;
+            case DragEvent.ACTION_DRAG_ENDED:
+                motionAction = MotionEvent.ACTION_UP;
+                break;
+            default:
+                return;
+        }
+        MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
+                motionAction, event.getX(), event.getY(), 0);
+        recordMotionEvent(emulatedEvent);
+        emulatedEvent.recycle();
+    }
+
     public void releaseVelocityTracker() {
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
new file mode 100644
index 0000000..1a99cc8
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -0,0 +1,259 @@
+/*
+ * 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.content.ClipDescription;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.PendingItemPreviewProvider;
+
+import java.util.UUID;
+
+/**
+ * {@link DragSource} for handling drop from from a different window. This object is initialized
+ * in the source window and is passed on to the Launcher activity as an Intent extra.
+ */
+public class PinItemDragListener implements Parcelable, View.OnDragListener, DragSource {
+
+    private static final String TAG = "PinItemDragListener";
+
+    private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/";
+    public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
+
+    private final PinItemRequestCompat mRequest;
+
+    // Position of preview relative to the touch location
+    private final Rect mPreviewRect;
+
+    // Randomly generated id used to verify the drag event.
+    private final String mId;
+
+    private Launcher mLauncher;
+    private DragController mDragController;
+    private long mDragStartTime;
+
+    public PinItemDragListener(PinItemRequestCompat request, Rect previewRect) {
+        mRequest = request;
+        mPreviewRect = previewRect;
+        mId = UUID.randomUUID().toString();
+    }
+
+    private PinItemDragListener(Parcel parcel) {
+        mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel);
+        mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
+        mId = parcel.readString();
+    }
+
+    public String getMimeType() {
+        return MIME_TYPE_PREFIX + mId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        mRequest.writeToParcel(parcel, i);
+        mPreviewRect.writeToParcel(parcel, i);
+        parcel.writeString(mId);
+    }
+
+    public void setLauncher(Launcher launcher) {
+        mLauncher = launcher;
+        mDragController = launcher.getDragController();
+    }
+
+    @Override
+    public boolean onDrag(View view, DragEvent event) {
+        if (mLauncher == null || mDragController == null) {
+            postCleanup();
+            return false;
+        }
+        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+            if (onDragStart(event)) {
+                return true;
+            } else {
+                postCleanup();
+                return false;
+            }
+        }
+        return mDragController.onDragEvent(mDragStartTime, event);
+    }
+
+    private boolean onDragStart(DragEvent event) {
+        if (!mRequest.isValid()) {
+            return false;
+        }
+        ClipDescription desc =  event.getClipDescription();
+        if (desc == null || !desc.hasMimeType(getMimeType())) {
+            Log.e(TAG, "Someone started a dragAndDrop before us.");
+            return false;
+        }
+
+        if (mLauncher.isWorkspaceLocked()) {
+            // TODO: implement wait
+            return false;
+        }
+
+        final PendingAddItemInfo item;
+        final Bitmap preview;
+
+        Point dragShift = new Point(mPreviewRect.left, mPreviewRect.top);
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+            item = new PendingAddShortcutInfo(
+                    new PinShortcutRequestActivityInfo(mRequest, mLauncher));
+
+            ShortcutInfoCompat compat = new ShortcutInfoCompat(mRequest.getShortcutInfo());
+            Bitmap icon = LauncherIcons.createShortcutIcon(compat, mLauncher, false /* badged */);
+
+            // Create a preview same as the workspace cell size and draw the icon at the
+            // appropriate position.
+            int[] size = mLauncher.getWorkspace().estimateItemSize(item, true, false);
+            preview = Bitmap.createBitmap(size[0], size[1], Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(preview);
+            DeviceProfile dp = mLauncher.getDeviceProfile();
+            c.drawBitmap(icon, (size[0] - icon.getWidth()) / 2,
+                    (size[1] - icon.getHeight() - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2,
+                    new Paint(Paint.FILTER_BITMAP_FLAG));
+        } else {
+            PendingAddWidgetInfo info = new PendingAddWidgetInfo(
+                    LauncherAppWidgetProviderInfo.fromProviderInfo(
+                            mLauncher, mRequest.getAppWidgetProviderInfo(mLauncher)));
+            int[] size = mLauncher.getWorkspace().estimateItemSize(info, true, false);
+
+            float minScale = 1.25f;
+            int maxWidth = Math.min((int) (mPreviewRect.width() * minScale), size[0]);
+            int[] previewSizeBeforeScale = new int[1];
+            preview = LauncherAppState.getInstance(mLauncher).getWidgetCache()
+                    .generateWidgetPreview(mLauncher, info.info, maxWidth, null,
+                            previewSizeBeforeScale);
+
+            dragShift.offset(
+                    (mPreviewRect.width() - preview.getWidth()) / 2,
+                    (mPreviewRect.height() - preview.getHeight()) / 2);
+            item = info;
+        }
+
+        PendingItemPreviewProvider previewProvider =
+                new PendingItemPreviewProvider(new View(mLauncher), item, preview);
+
+        // Since we are not going through the workspace for starting the drag, set drag related
+        // information on the workspace before starting the drag.
+        mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
+
+        Point downPos = new Point((int) event.getX(), (int) event.getY());
+        DragOptions options = new DragOptions();
+        options.systemDndStartPoint = downPos;
+
+        int x = downPos.x + dragShift.x;
+        int y = downPos.y + dragShift.y;
+        mDragController.startDrag(
+                preview, x, y, this, item, null, null, 1f, options);
+        mDragStartTime = SystemClock.uptimeMillis();
+        return true;
+    }
+
+    @Override
+    public boolean supportsAppInfoDropTarget() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDeleteDropTarget() {
+        return false;
+    }
+
+    @Override
+    public float getIntrinsicIconScaleFactor() {
+        return 1f;
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+            boolean success) {
+        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
+            // Exit spring loaded mode if we have not successfully dropped or have not handled the
+            // drop in Workspace
+            mLauncher.exitSpringLoadedDragModeDelayed(true,
+                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+        }
+        postCleanup();
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        // TODO: We should probably log something
+    }
+
+    private void postCleanup() {
+        new Handler(Looper.getMainLooper()).post(new Runnable() {
+            @Override
+            public void run() {
+                removeListener();
+            }
+        });
+    }
+
+    public void removeListener() {
+        if (mLauncher != null) {
+            mLauncher.getDragLayer().setOnDragListener(null);
+        }
+    }
+
+    public static final Parcelable.Creator<PinItemDragListener> CREATOR =
+            new Parcelable.Creator<PinItemDragListener>() {
+                public PinItemDragListener createFromParcel(Parcel source) {
+                    return new PinItemDragListener(source);
+                }
+
+                public PinItemDragListener[] newArray(int size) {
+                    return new PinItemDragListener[size];
+                }
+            };
+}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index d1f878a..2121b43 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -26,7 +26,9 @@
 import android.os.Build;
 
 import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.compat.PinItemRequestCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 
 /**
@@ -40,12 +42,15 @@
     // actual existing class.
     private static final String DUMMY_COMPONENT_CLASS = "pinned-shortcut";
 
+    private final PinItemRequestCompat mRequest;
     private final ShortcutInfo mInfo;
     private final Context mContext;
 
-    public PinShortcutRequestActivityInfo(ShortcutInfo info, Context context) {
-        super(new ComponentName(info.getPackage(), DUMMY_COMPONENT_CLASS), info.getUserHandle());
-        mInfo = info;
+    public PinShortcutRequestActivityInfo(PinItemRequestCompat request, Context context) {
+        super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS),
+                request.getShortcutInfo().getUserHandle());
+        mRequest = request;
+        mInfo = request.getShortcutInfo();
         mContext = context;
     }
 
@@ -61,8 +66,9 @@
     }
 
     @Override
-    public boolean startConfigActivity(Activity activity, int requestCode) {
-        throw new RuntimeException("Not supported");
+    public boolean startConfigActivity(Launcher activity, int requestCode) {
+        activity.onActivityResult(requestCode, Activity.RESULT_OK, mRequest.toIntent());
+        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
new file mode 100644
index 0000000..4ed32b5
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -0,0 +1,161 @@
+/*
+ * 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.popup;
+
+import android.content.ComponentName;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.badging.NotificationListener;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides data for the popup menu that appears after long-clicking on apps.
+ */
+public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {
+
+    private static final boolean LOGD = false;
+    private static final String TAG = "PopupDataProvider";
+
+    private final Launcher mLauncher;
+
+    /** Maps launcher activity components to their list of shortcut ids. */
+    private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+    /** Maps packages to their BadgeInfo's . */
+    private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
+
+    public PopupDataProvider(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    @Override
+    public void onNotificationPosted(PackageUserKey postedPackageUserKey, String notificationKey) {
+        BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey);
+        if (oldBadgeInfo == null) {
+            BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey);
+            newBadgeInfo.addNotificationKey(notificationKey);
+            mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo);
+            mLauncher.updateIconBadges(Collections.singleton(postedPackageUserKey));
+        } else if (oldBadgeInfo.addNotificationKey(notificationKey)) {
+            mLauncher.updateIconBadges(Collections.singleton(postedPackageUserKey));
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(PackageUserKey removedPackageUserKey, String notificationKey) {
+        BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey);
+        if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) {
+            if (oldBadgeInfo.getNotificationCount() == 0) {
+                mPackageUserToBadgeInfos.remove(removedPackageUserKey);
+            }
+            mLauncher.updateIconBadges(Collections.singleton(removedPackageUserKey));
+        }
+    }
+
+    @Override
+    public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
+        if (activeNotifications == null) return;
+        // This will contain the PackageUserKeys which have updated badges.
+        HashMap<PackageUserKey, BadgeInfo> updatedBadges = new HashMap<>(mPackageUserToBadgeInfos);
+        mPackageUserToBadgeInfos.clear();
+        for (StatusBarNotification notification : activeNotifications) {
+            PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
+            BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(packageUserKey);
+            if (badgeInfo == null) {
+                badgeInfo = new BadgeInfo(packageUserKey);
+                mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo);
+            }
+            badgeInfo.addNotificationKey(notification.getKey());
+        }
+
+        // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges.
+        for (PackageUserKey packageUserKey : mPackageUserToBadgeInfos.keySet()) {
+            BadgeInfo prevBadge = updatedBadges.get(packageUserKey);
+            BadgeInfo newBadge = mPackageUserToBadgeInfos.get(packageUserKey);
+            if (prevBadge == null) {
+                updatedBadges.put(packageUserKey, newBadge);
+            } else {
+                if (!prevBadge.shouldBeInvalidated(newBadge)) {
+                    updatedBadges.remove(packageUserKey);
+                }
+            }
+        }
+
+        if (!updatedBadges.isEmpty()) {
+            mLauncher.updateIconBadges(updatedBadges.keySet());
+        }
+    }
+
+    public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+        mDeepShortcutMap = deepShortcutMapCopy;
+        if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
+    }
+
+    public List<String> getShortcutIdsForItem(ItemInfo info) {
+        if (!DeepShortcutManager.supportsShortcuts(info)) {
+            return Collections.EMPTY_LIST;
+        }
+        ComponentName component = info.getTargetComponent();
+        if (component == null) {
+            return Collections.EMPTY_LIST;
+        }
+
+        List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
+        return ids == null ? Collections.EMPTY_LIST : ids;
+    }
+
+    public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
+        if (!DeepShortcutManager.supportsShortcuts(info)) {
+            return null;
+        }
+
+        return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
+    }
+
+    public String[] getNotificationKeysForItem(ItemInfo info) {
+        BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
+        Set<String> notificationKeys = badgeInfo.getNotificationKeys();
+        return notificationKeys.toArray(new String[notificationKeys.size()]);
+    }
+
+    /** This makes a potentially expensive binder call and should be run on a background thread. */
+    public List<StatusBarNotification> getStatusBarNotificationsForKeys(String[] notificationKeys) {
+        NotificationListener notificationListener = NotificationListener.getInstance();
+        return notificationListener == null ? Collections.EMPTY_LIST
+                : notificationListener.getNotificationsForKeys(notificationKeys);
+    }
+
+    public void cancelNotification(String notificationKey) {
+        NotificationListener notificationListener = NotificationListener.getInstance();
+        if (notificationListener == null) {
+            return;
+        }
+        notificationListener.cancelNotification(notificationKey);
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index db2654c..5e12a57 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -718,7 +718,8 @@
             icon.clearFocus();
             return null;
         }
-        List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag());
+        List<String> ids = launcher.getPopupDataProvider().getShortcutIdsForItem(
+                (ItemInfo) icon.getTag());
         if (!ids.isEmpty()) {
             final DeepShortcutsContainer container =
                     (DeepShortcutsContainer) launcher.getLayoutInflater().inflate(
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
new file mode 100644
index 0000000..d08b0e9
--- /dev/null
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -0,0 +1,51 @@
+package com.android.launcher3.util;
+
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import com.android.launcher3.ItemInfo;
+
+import java.util.Arrays;
+
+/** Creates a hash key based on package name and user. */
+public class PackageUserKey {
+
+    private String mPackageName;
+    private UserHandle mUser;
+    private int mHashCode;
+
+    public static PackageUserKey fromItemInfo(ItemInfo info) {
+        return new PackageUserKey(info.getTargetComponent().getPackageName(), info.user);
+    }
+
+    public static PackageUserKey fromNotification(StatusBarNotification notification) {
+        return new PackageUserKey(notification.getPackageName(), notification.getUser());
+    }
+
+    public PackageUserKey(String packageName, UserHandle user) {
+        update(packageName, user);
+    }
+
+    private void update(String packageName, UserHandle user) {
+        mPackageName = packageName;
+        mUser = user;
+        mHashCode = Arrays.hashCode(new Object[] {packageName, user});
+    }
+
+    /** This should only be called to avoid new object creations in a loop. */
+    public void updateFromItemInfo(ItemInfo info) {
+        update(info.getTargetComponent().getPackageName(), info.user);
+    }
+
+    @Override
+    public int hashCode() {
+        return mHashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PackageUserKey)) return false;
+        PackageUserKey otherKey = (PackageUserKey) obj;
+        return mPackageName.equals(otherKey.mPackageName) && mUser.equals(otherKey.mUser);
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 87247f4..455ec4e 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -144,12 +144,8 @@
         }
     }
 
-    public int[] getPreviewSize() {
-        int[] maxSize = new int[2];
-
-        maxSize[0] = mPresetPreviewSize;
-        maxSize[1] = mPresetPreviewSize;
-        return maxSize;
+    public WidgetImageView getWidgetView() {
+        return mWidgetImage;
     }
 
     public void applyPreview(Bitmap bitmap) {
@@ -166,12 +162,8 @@
         if (mActiveRequest != null) {
             return;
         }
-        int[] size = getPreviewSize();
-        if (DEBUG) {
-            Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):",
-                    getTagToString(), size[0], size[1]));
-        }
-        mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, size[0], size[1], this);
+        mActiveRequest = mWidgetPreviewLoader.getPreview(
+                mItem, mPresetPreviewSize, mPresetPreviewSize, this);
     }
 
     @Override
diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java
index 4cad836..ffb86e4 100644
--- a/src_config/com/android/launcher3/config/FeatureFlags.java
+++ b/src_config/com/android/launcher3/config/FeatureFlags.java
@@ -39,4 +39,6 @@
     public static final boolean LIGHT_STATUS_BAR = false;
     // When enabled allows to use any point on the fast scrollbar to start dragging.
     public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
+    // When enabled icons are badged with the number of notifications associated with that app.
+    public static final boolean BADGE_ICONS = true;
 }