Merge "Add isInMultiWindowMode bool to all logs." into ub-launcher3-dorval
diff --git a/res/drawable/ic_instant_app.xml b/res/drawable/ic_instant_app.xml
deleted file mode 100644
index be5a3e0..0000000
--- a/res/drawable/ic_instant_app.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="@dimen/badge_size"
-    android:height="@dimen/badge_size"
-    android:viewportWidth="18"
-    android:viewportHeight="18">
-
-    <path
-        android:fillColor="@android:color/black"
-        android:fillType="evenOdd"
-        android:strokeWidth="1"
-        android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:fillType="evenOdd"
-        android:strokeWidth="1"
-        android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:fillType="evenOdd"
-        android:strokeWidth="1"
-        android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
-    <path
-        android:fillColor="@android:color/black"
-        android:fillAlpha="0.87"
-        android:fillType="evenOdd"
-        android:strokeWidth="1"
-        android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
-</vector>
diff --git a/res/layout/all_apps_discovery_item.xml b/res/layout/all_apps_discovery_item.xml
index 2b21ef5..1a7eaa7 100644
--- a/res/layout/all_apps_discovery_item.xml
+++ b/res/layout/all_apps_discovery_item.xml
@@ -27,18 +27,6 @@
         android:padding="8dp"
         android:scaleType="fitCenter"/>
 
-    <ImageView
-        android:id="@+id/badge"
-        android:layout_width="16dp"
-        android:layout_height="16dp"
-        android:scaleType="fitCenter"
-        android:src="@drawable/ic_instant_app"
-        android:layout_marginRight="6dp"
-        android:layout_marginBottom="6dp"
-        android:layout_alignRight="@+id/image"
-        android:layout_alignBottom="@+id/image"
-        android:clickable="false"/>
-
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 2e017df..c4086a8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -195,7 +195,7 @@
         try {
             return parseLayout(mLayoutId, screenIds);
         } catch (Exception e) {
-            Log.w(TAG, "Got exception parsing layout.", e);
+            Log.e(TAG, "Error parsing layout: " + e);
             return -1;
         }
     }
@@ -362,7 +362,7 @@
                     return addShortcut(info.loadLabel(mPackageManager).toString(),
                             intent, Favorites.ITEM_TYPE_APPLICATION);
                 } catch (PackageManager.NameNotFoundException e) {
-                    Log.e(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
+                    Log.e(TAG, "Favorite not found: " + packageName + "/" + className);
                 }
                 return -1;
             } else {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2dd3198..9e214d1 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -29,7 +29,6 @@
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -188,23 +187,6 @@
         }
     }
 
-    public void dumpDisplayInfo(Context context) {
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        Display display = wm.getDefaultDisplay();
-        DisplayMetrics dm = new DisplayMetrics();
-        display.getMetrics(dm);
-
-        Point smallestSize = new Point();
-        Point largestSize = new Point();
-        display.getCurrentSizeRange(smallestSize, largestSize);
-
-        FileLog.e("DisplayInfo", "Default Density: " + DisplayMetrics.DENSITY_DEFAULT);
-        FileLog.e("DisplayInfo", "Density: " + dm.densityDpi);
-        FileLog.e("DisplayInfo", "Smallest size: " + smallestSize);
-        FileLog.e("DisplayInfo", "Largest size: " + largestSize);
-        FileLog.e("DisplayInfo", "minWidth/Height DPS: " + minWidthDps + ", " + minHeightDps);
-    }
-
     ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
         ArrayList<InvariantDeviceProfile> profiles = new ArrayList<>();
         try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 660037e..84a5930 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2270,6 +2270,9 @@
 
         if (v instanceof Workspace) {
             if (mWorkspace.isInOverviewMode()) {
+                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
+                        LauncherLogProto.Action.Direction.NONE,
+                        LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
                 showWorkspace(true);
             }
             return;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 2e75579..180c202 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -28,7 +28,6 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dynamicui.ExtractionUtils;
-import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TestingUtils;
@@ -54,8 +53,6 @@
         if (INSTANCE == null) {
             if (Looper.myLooper() == Looper.getMainLooper()) {
                 INSTANCE = new LauncherAppState(context.getApplicationContext());
-                GridSizeMigrationTask.logDeviceProfileIfChanged(
-                        INSTANCE.getInvariantDeviceProfile(), context);
             } else {
                 try {
                     return new MainThreadExecutor().submit(new Callable<LauncherAppState>() {
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index e8bf0a5..203bc25 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -19,14 +19,17 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.List;
 
@@ -35,9 +38,13 @@
  */
 public class SessionCommitReceiver extends BroadcastReceiver {
 
+    private static final long SESSION_IGNORE_DURATION = 3 * 60 * 60 * 1000; // 3 hours
+
     // Preference key for automatically adding icon to homescreen.
     public static final String ADD_ICON_PREFERENCE_KEY = "pref_add_icon_to_home";
 
+    private static final String KEY_FIRST_TIME = "first_session_broadcast_time";
+
     @Override
     public void onReceive(Context context, Intent intent) {
         if (!isEnabled(context)) {
@@ -57,6 +64,17 @@
             return;
         }
 
+        // STOPSHIP: Remove this workaround when we start getting proper install reason
+        SharedPreferences prefs = context
+                .getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0);
+        long now = System.currentTimeMillis();
+        long firstTime = prefs.getLong(KEY_FIRST_TIME, now);
+        prefs.edit().putLong(KEY_FIRST_TIME, firstTime).apply();
+        if ((now - firstTime) < SESSION_IGNORE_DURATION) {
+            Log.d("SessionCommitReceiver", "Temporarily ignoring session broadcast");
+            return;
+        }
+
         List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
                 .getActivityList(info.getAppPackageName(), user);
         if (activities == null || activities.isEmpty()) {
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index f228470..f5cf7ef 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -292,7 +292,7 @@
                 mDiscoveredApps.clear();
                 break;
             case UPDATE:
-                mDiscoveredApps.add(new AppDiscoveryAppInfo(app, mLauncher));
+                mDiscoveredApps.add(new AppDiscoveryAppInfo(app));
                 break;
         }
         updateAdapterItems();
@@ -494,10 +494,13 @@
         if (hasFilter()) {
             if (isAppDiscoveryRunning() || mDiscoveredApps.size() > 0) {
                 mAdapterItems.add(AdapterItem.asLoadingDivider(position++));
-
                 // Append all app discovery results
                 for (int i = 0; i < mDiscoveredApps.size(); i++) {
                     AppDiscoveryAppInfo appDiscoveryAppInfo = mDiscoveredApps.get(i);
+                    if (appDiscoveryAppInfo.isRecent) {
+                        // already handled in getFilteredAppInfos()
+                        continue;
+                    }
                     AdapterItem item = AdapterItem.asDiscoveryItem(position++,
                             "", appDiscoveryAppInfo, appIndex++);
                     mAdapterItems.add(item);
@@ -589,6 +592,17 @@
                 result.add(match);
             }
         }
+
+        // adding recently used instant apps
+        if (mDiscoveredApps.size() > 0) {
+            for (int i = 0; i < mDiscoveredApps.size(); i++) {
+                AppDiscoveryAppInfo discoveryAppInfo = mDiscoveredApps.get(i);
+                if (discoveryAppInfo.isRecent) {
+                    result.add(discoveryAppInfo);
+                }
+            }
+            Collections.sort(result, mAppNameComparator);
+        }
         return result;
     }
 
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
index 532396c..08d8ad4 100644
--- a/src/com/android/launcher3/badge/BadgeInfo.java
+++ b/src/com/android/launcher3/badge/BadgeInfo.java
@@ -25,6 +25,7 @@
 import android.support.annotation.Nullable;
 
 import com.android.launcher3.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
@@ -35,6 +36,8 @@
  */
 public class BadgeInfo {
 
+    public static final int MAX_COUNT = 999;
+
     /** Used to link this BadgeInfo to icons on the workspace and all apps */
     private PackageUserKey mPackageUserKey;
 
@@ -42,7 +45,13 @@
      * The keys of the notifications that this badge represents. These keys can later be
      * used to retrieve {@link NotificationInfo}'s.
      */
-    private List<String> mNotificationKeys;
+    private List<NotificationKeyData> mNotificationKeys;
+
+    /**
+     * The current sum of the counts in {@link #mNotificationKeys},
+     * updated whenever a key is added or removed.
+     */
+    private int mTotalCount;
 
     /** This will only be initialized if the badge should display the notification icon. */
     private NotificationInfo mNotificationInfo;
@@ -59,28 +68,46 @@
     }
 
     /**
-     * Returns whether the notification was added (false if it already existed).
+     * Returns whether the notification was added or its count changed.
      */
-    public boolean addNotificationKeyIfNotExists(String notificationKey) {
-        if (mNotificationKeys.contains(notificationKey)) {
-            return false;
+    public boolean addOrUpdateNotificationKey(NotificationKeyData notificationKey) {
+        int indexOfPrevKey = mNotificationKeys.indexOf(notificationKey);
+        NotificationKeyData prevKey = indexOfPrevKey == -1 ? null
+                : mNotificationKeys.get(indexOfPrevKey);
+        if (prevKey != null) {
+            if (prevKey.count == notificationKey.count) {
+                return false;
+            }
+            // Notification was updated with a new count.
+            mTotalCount -= prevKey.count;
+            mTotalCount += notificationKey.count;
+            prevKey.count = notificationKey.count;
+            return true;
         }
-        return mNotificationKeys.add(notificationKey);
+        boolean added = mNotificationKeys.add(notificationKey);
+        if (added) {
+            mTotalCount += notificationKey.count;
+        }
+        return added;
     }
 
     /**
      * Returns whether the notification was removed (false if it didn't exist).
      */
-    public boolean removeNotificationKey(String notificationKey) {
-        return mNotificationKeys.remove(notificationKey);
+    public boolean removeNotificationKey(NotificationKeyData notificationKey) {
+        boolean removed = mNotificationKeys.remove(notificationKey);
+        if (removed) {
+            mTotalCount -= notificationKey.count;
+        }
+        return removed;
     }
 
-    public List<String> getNotificationKeys() {
+    public List<NotificationKeyData> getNotificationKeys() {
         return mNotificationKeys;
     }
 
     public int getNotificationCount() {
-        return mNotificationKeys.size();
+        return Math.min(mTotalCount, MAX_COUNT);
     }
 
     public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) {
diff --git a/src/com/android/launcher3/badge/FolderBadgeInfo.java b/src/com/android/launcher3/badge/FolderBadgeInfo.java
index 4d1e5c2..f7c64aa 100644
--- a/src/com/android/launcher3/badge/FolderBadgeInfo.java
+++ b/src/com/android/launcher3/badge/FolderBadgeInfo.java
@@ -18,8 +18,6 @@
 
 import com.android.launcher3.Utilities;
 
-import static com.android.launcher3.Utilities.boundToRange;
-
 /**
  * Subclass of BadgeInfo that only contains the badge count,
  * which is the sum of all the Folder's items' counts.
@@ -27,7 +25,6 @@
 public class FolderBadgeInfo extends BadgeInfo {
 
     private static final int MIN_COUNT = 0;
-    private static final int MAX_COUNT = 999;
 
     private int mTotalNotificationCount;
 
@@ -41,7 +38,7 @@
         }
         mTotalNotificationCount += badgeToAdd.getNotificationCount();
         mTotalNotificationCount = Utilities.boundToRange(
-                mTotalNotificationCount, MIN_COUNT, MAX_COUNT);
+                mTotalNotificationCount, MIN_COUNT, BadgeInfo.MAX_COUNT);
     }
 
     public void subtractBadgeInfo(BadgeInfo badgeToSubtract) {
@@ -50,7 +47,7 @@
         }
         mTotalNotificationCount -= badgeToSubtract.getNotificationCount();
         mTotalNotificationCount = Utilities.boundToRange(
-                mTotalNotificationCount, MIN_COUNT, MAX_COUNT);
+                mTotalNotificationCount, MIN_COUNT, BadgeInfo.MAX_COUNT);
     }
 
     @Override
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
index 50e979a..06493b2 100644
--- a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -18,24 +18,18 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 
 public class AppDiscoveryAppInfo extends AppInfo {
 
-    private final @NonNull Launcher mLauncher;
-
     public final boolean showAsDiscoveryItem;
     public final boolean isInstantApp;
+    public final boolean isRecent;
     public final float rating;
     public final long reviewCount;
     public final @NonNull String publisher;
@@ -43,14 +37,14 @@
     public final @NonNull Intent launchIntent;
     public final @Nullable String priceFormatted;
 
-    public AppDiscoveryAppInfo(AppDiscoveryItem item, Launcher launcher) {
-        this.mLauncher = launcher;
+    public AppDiscoveryAppInfo(AppDiscoveryItem item) {
         this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
         this.title = item.title;
         this.iconBitmap = item.bitmap;
         this.isDisabled = ShortcutInfo.DEFAULT;
         this.usingLowResIcon = false;
         this.isInstantApp = item.isInstantApp;
+        this.isRecent = item.isRecent;
         this.rating = item.starRating;
         this.showAsDiscoveryItem = true;
         this.publisher = item.publisher != null ? item.publisher : "";
@@ -67,18 +61,7 @@
         if (!isDragAndDropSupported()) {
             throw new RuntimeException("DnD is currently not supported for discovered store apps");
         }
-        ShortcutInfo shortcutInfo = super.makeShortcut();
-        if (isInstantApp) {
-            int iconSize = iconBitmap.getWidth();
-            int badgeSize = mLauncher.getResources().getDimensionPixelOffset(R.dimen.badge_size);
-            Bitmap icon = Bitmap.createBitmap(iconBitmap);
-            Drawable badgeDrawable = mLauncher.getDrawable(R.drawable.ic_instant_app);
-            badgeDrawable.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
-            Canvas canvas = new Canvas(icon);
-            badgeDrawable.draw(canvas);
-            shortcutInfo.iconBitmap = icon;
-        }
-        return shortcutInfo;
+        return super.makeShortcut();
     }
 
     public boolean isDragAndDropSupported() {
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItem.java b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
index 7c10371..09c91ac 100644
--- a/src/com/android/launcher3/discovery/AppDiscoveryItem.java
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
@@ -28,6 +28,7 @@
 
     public final String packageName;
     public final boolean isInstantApp;
+    public final boolean isRecent;
     public final float starRating;
     public final long reviewCount;
     public final Intent launchIntent;
@@ -39,6 +40,7 @@
 
     public AppDiscoveryItem(String packageName,
                             boolean isInstantApp,
+                            boolean isRecent,
                             float starRating,
                             long reviewCount,
                             CharSequence title,
@@ -49,6 +51,7 @@
                             Intent installIntent) {
         this.packageName = packageName;
         this.isInstantApp = isInstantApp;
+        this.isRecent = isRecent;
         this.starRating = starRating;
         this.reviewCount = reviewCount;
         this.launchIntent = launchIntent;
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItemView.java b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
index 6faad87..9bb3b10 100644
--- a/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
@@ -34,7 +34,6 @@
     private static boolean SHOW_REVIEW_COUNT = false;
 
     private ImageView mImage;
-    private ImageView mBadge;
     private TextView mTitle;
     private TextView mRatingText;
     private RatingView mRatingView;
@@ -58,7 +57,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         this.mImage = (ImageView) findViewById(R.id.image);
-        this.mBadge = (ImageView) findViewById(R.id.badge);
         this.mTitle = (TextView) findViewById(R.id.title);
         this.mRatingText = (TextView) findViewById(R.id.rating);
         this.mRatingView = (RatingView) findViewById(R.id.rating_view);
@@ -80,7 +78,6 @@
         mImage.setTag(info);
         mImage.setImageBitmap(info.iconBitmap);
         mImage.setOnLongClickListener(info.isDragAndDropSupported() ? mOnLongClickListener : null);
-        mBadge.setVisibility(info.isInstantApp ? View.VISIBLE : View.GONE);
         mTitle.setText(info.title);
         mPrice.setText(info.priceFormatted != null ? info.priceFormatted : "");
         mReviewCount.setVisibility(SHOW_REVIEW_COUNT ? View.VISIBLE : View.GONE);
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index e50b912..221798b 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -27,7 +27,6 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.LongArrayMap;
 
@@ -890,23 +889,6 @@
                 .apply();
     }
 
-    public static void logDeviceProfileIfChanged(InvariantDeviceProfile idp, Context context) {
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-
-        int oldHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
-        String oldSize = prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString);
-        if (gridSizeString.equals(oldSize) && idp.numHotseatIcons == oldHotseatCount) {
-            // Skip if workspace and hotseat sizes have not changed.
-            return;
-        }
-
-        FileLog.e(TAG, "Grid size changed" + gridSizeString);
-        FileLog.e(TAG, "   oldSize: " + oldSize + "  , hotseat: " + oldHotseatCount);
-        FileLog.e(TAG, "   newSize: " + gridSizeString + "  , hotseat: " + idp.numHotseatIcons);
-        idp.dumpDisplayInfo(context);
-    }
-
     /**
      * Migrates the workspace and hotseat in case their sizes changed.
      * @return false if the migration failed.
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index ba7675c..f6779b3 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -38,7 +38,7 @@
  * 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[])}.
+ * {@link NotificationListener#getNotificationsForKeys(java.util.List)}.
  */
 public class NotificationInfo implements View.OnClickListener {
 
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
new file mode 100644
index 0000000..bf7ae1a
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -0,0 +1,64 @@
+/*
+ * 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.notification;
+
+import android.app.Notification;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The key data associated with the notification, used to determine what to include
+ * in badges and dummy popup views before they are populated.
+ *
+ * @see NotificationInfo for the full data used when populating the dummy views.
+ */
+public class NotificationKeyData {
+    public final String notificationKey;
+    public final String shortcutId;
+    public int count;
+
+    private NotificationKeyData(String notificationKey, String shortcutId, int count) {
+        this.notificationKey = notificationKey;
+        this.shortcutId = shortcutId;
+        this.count = Math.max(1, count);
+    }
+
+    public static NotificationKeyData fromNotification(StatusBarNotification sbn) {
+        Notification notif = sbn.getNotification();
+        return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number);
+    }
+
+    public static List<String> extractKeysOnly(@NonNull List<NotificationKeyData> notificationKeys) {
+        List<String> keysOnly = new ArrayList<>(notificationKeys.size());
+        for (NotificationKeyData notificationKeyData : notificationKeys) {
+            keysOnly.add(notificationKeyData.notificationKey);
+        }
+        return keysOnly;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof NotificationKeyData)) {
+            return false;
+        }
+        // Only compare the keys.
+        return ((NotificationKeyData) obj).notificationKey.equals(notificationKey);
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 16cb5fb..75a1b8a 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -94,8 +94,8 @@
                     break;
                 case MSG_NOTIFICATION_REMOVED:
                     if (sNotificationsChangedListener != null) {
-                        Pair<PackageUserKey, String> pair
-                                = (Pair<PackageUserKey, String>) message.obj;
+                        Pair<PackageUserKey, NotificationKeyData> pair
+                                = (Pair<PackageUserKey, NotificationKeyData>) message.obj;
                         sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second);
                     }
                     break;
@@ -165,12 +165,12 @@
      */
     private class NotificationPostedMsg {
         PackageUserKey packageUserKey;
-        String notificationKey;
+        NotificationKeyData notificationKey;
         boolean shouldBeFilteredOut;
 
         NotificationPostedMsg(StatusBarNotification sbn) {
             packageUserKey = PackageUserKey.fromNotification(sbn);
-            notificationKey = sbn.getKey();
+            notificationKey = NotificationKeyData.fromNotification(sbn);
             shouldBeFilteredOut = shouldBeFilteredOut(sbn);
         }
     }
@@ -178,16 +178,18 @@
     @Override
     public void onNotificationRemoved(final StatusBarNotification sbn) {
         super.onNotificationRemoved(sbn);
-        Pair<PackageUserKey, String> packageUserKeyAndNotificationKey
-                = new Pair<>(PackageUserKey.fromNotification(sbn), sbn.getKey());
+        Pair<PackageUserKey, NotificationKeyData> packageUserKeyAndNotificationKey
+                = new Pair<>(PackageUserKey.fromNotification(sbn),
+                        NotificationKeyData.fromNotification(sbn));
         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) {
+    public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) {
         StatusBarNotification[] notifications = NotificationListener.this
-                .getActiveNotifications(keys);
+                .getActiveNotifications(NotificationKeyData.extractKeysOnly(keys)
+                        .toArray(new String[keys.size()]));
         return notifications == null ? Collections.EMPTY_LIST : Arrays.asList(notifications);
     }
 
@@ -238,9 +240,10 @@
     }
 
     public interface NotificationsChangedListener {
-        void onNotificationPosted(PackageUserKey postedPackageUserKey, String notificationKey,
-                boolean shouldBeFilteredOut);
-        void onNotificationRemoved(PackageUserKey removedPackageUserKey, String notificationKey);
+        void onNotificationPosted(PackageUserKey postedPackageUserKey,
+                NotificationKeyData notificationKey, boolean shouldBeFilteredOut);
+        void onNotificationRemoved(PackageUserKey removedPackageUserKey,
+                NotificationKeyData notificationKey);
         void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications);
     }
 }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 1eac076..b2018b9 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -65,6 +65,7 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.TriangleShape;
 import com.android.launcher3.notification.NotificationItemView;
+import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutsItemView;
 import com.android.launcher3.util.PackageUserKey;
@@ -138,9 +139,9 @@
         }
         ItemInfo itemInfo = (ItemInfo) icon.getTag();
         List<String> shortcutIds = launcher.getPopupDataProvider().getShortcutIdsForItem(itemInfo);
-        String[] notificationKeys = launcher.getPopupDataProvider()
+        List<NotificationKeyData> notificationKeys = launcher.getPopupDataProvider()
                 .getNotificationKeysForItem(itemInfo);
-        if (shortcutIds.size() > 0 || notificationKeys.length > 0) {
+        if (shortcutIds.size() > 0 || notificationKeys.size() > 0) {
             final PopupContainerWithArrow container =
                     (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                             R.layout.popup_container, launcher.getDragLayer(), false);
@@ -153,7 +154,7 @@
     }
 
     public void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
-            final String[] notificationKeys) {
+            final List<NotificationKeyData> notificationKeys) {
         final Resources resources = getResources();
         final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
         final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
@@ -165,7 +166,7 @@
         // Add dummy views first, and populate with real info when ready.
         PopupPopulator.Item[] itemsToPopulate = PopupPopulator
                 .getItemsToPopulate(shortcutIds, notificationKeys);
-        addDummyViews(originalIcon, itemsToPopulate, notificationKeys.length > 1);
+        addDummyViews(originalIcon, itemsToPopulate, notificationKeys.size() > 1);
 
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
@@ -176,7 +177,7 @@
             mNotificationItemView = null;
             mShortcutsItemView = null;
             itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
-            addDummyViews(originalIcon, itemsToPopulate, notificationKeys.length > 1);
+            addDummyViews(originalIcon, itemsToPopulate, notificationKeys.size() > 1);
 
             measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
             orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
@@ -606,7 +607,8 @@
             removeNotification.start();
             return;
         }
-        mNotificationItemView.trimNotifications(badgeInfo.getNotificationKeys());
+        mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
+                badgeInfo.getNotificationKeys()));
     }
 
     private ObjectAnimator createArrowScaleAnim(float scale) {
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index f0ccb1b..9c4faed 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
 import android.util.Log;
 
 import com.android.launcher3.ItemInfo;
@@ -25,6 +26,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.ComponentKey;
@@ -58,33 +60,34 @@
     }
 
     @Override
-    public void onNotificationPosted(PackageUserKey postedPackageUserKey, String notificationKey,
-            boolean shouldBeFilteredOut) {
+    public void onNotificationPosted(PackageUserKey postedPackageUserKey,
+            NotificationKeyData notificationKey, boolean shouldBeFilteredOut) {
         BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey);
-        boolean notificationWasAddedOrRemoved; // As opposed to updated.
+        boolean badgeShouldBeRefreshed;
         if (badgeInfo == null) {
             if (!shouldBeFilteredOut) {
                 BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey);
-                newBadgeInfo.addNotificationKeyIfNotExists(notificationKey);
+                newBadgeInfo.addOrUpdateNotificationKey(notificationKey);
                 mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo);
-                notificationWasAddedOrRemoved = true;
+                badgeShouldBeRefreshed = true;
             } else {
-                notificationWasAddedOrRemoved = false;
+                badgeShouldBeRefreshed = false;
             }
         } else {
-            notificationWasAddedOrRemoved = shouldBeFilteredOut
+            badgeShouldBeRefreshed = shouldBeFilteredOut
                     ? badgeInfo.removeNotificationKey(notificationKey)
-                    : badgeInfo.addNotificationKeyIfNotExists(notificationKey);
+                    : badgeInfo.addOrUpdateNotificationKey(notificationKey);
             if (badgeInfo.getNotificationCount() == 0) {
                 mPackageUserToBadgeInfos.remove(postedPackageUserKey);
             }
         }
         updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey),
-                notificationWasAddedOrRemoved);
+                badgeShouldBeRefreshed);
     }
 
     @Override
-    public void onNotificationRemoved(PackageUserKey removedPackageUserKey, String notificationKey) {
+    public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
+            NotificationKeyData notificationKey) {
         BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey);
         if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) {
             if (oldBadgeInfo.getNotificationCount() == 0) {
@@ -112,7 +115,8 @@
                 badgeInfo = new BadgeInfo(packageUserKey);
                 mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo);
             }
-            badgeInfo.addNotificationKeyIfNotExists(notification.getKey());
+            badgeInfo.addOrUpdateNotificationKey(NotificationKeyData
+                    .fromNotification(notification));
         }
 
         // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges.
@@ -146,17 +150,17 @@
      * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges.
      * @param updatedBadges The packages whose badges should be refreshed (either a notification was
      *                      added or removed, or the badge should show the notification icon).
-     * @param addedOrRemoved An optional parameter that will allow us to only refresh badges that
-     *                       updated (not added/removed) that have icons. If a badge updated
-     *                       but it doesn't have an icon, then the badge number doesn't change.
+     * @param shouldRefresh An optional parameter that will allow us to only refresh badges that
+     *                      have actually changed. If a notification updated its content but not
+     *                      its count or icon, then the badge doesn't change.
      */
     private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges,
-            boolean addedOrRemoved) {
+            boolean shouldRefresh) {
         Iterator<PackageUserKey> iterator = updatedBadges.iterator();
         while (iterator.hasNext()) {
             BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next());
-            if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !addedOrRemoved) {
-                // The notification icon isn't used, and the badge wasn't added or removed
+            if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !shouldRefresh) {
+                // The notification icon isn't used, and the badge hasn't changed
                 // so there is no update to be made.
                 iterator.remove();
             }
@@ -177,8 +181,9 @@
         NotificationInfo notificationInfo = null;
         NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
         if (notificationListener != null && badgeInfo.getNotificationKeys().size() == 1) {
+            String onlyNotificationKey = badgeInfo.getNotificationKeys().get(0).notificationKey;
             StatusBarNotification[] activeNotifications = notificationListener
-                    .getActiveNotifications(new String[] {badgeInfo.getNotificationKeys().get(0)});
+                    .getActiveNotifications(new String[] {onlyNotificationKey});
             if (activeNotifications.length == 1) {
                 notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]);
                 if (!notificationInfo.shouldShowIconInBadge()) {
@@ -216,15 +221,14 @@
         return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
     }
 
-    public String[] getNotificationKeysForItem(ItemInfo info) {
+    public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
         BadgeInfo badgeInfo = getBadgeInfoForItem(info);
-        if (badgeInfo == null) { return new String[0]; }
-        List<String> notificationKeys = badgeInfo.getNotificationKeys();
-        return notificationKeys.toArray(new String[notificationKeys.size()]);
+        return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.getNotificationKeys();
     }
 
     /** This makes a potentially expensive binder call and should be run on a background thread. */
-    public List<StatusBarNotification> getStatusBarNotificationsForKeys(String[] notificationKeys) {
+    public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys(
+            List<NotificationKeyData> notificationKeys) {
         NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
         return notificationListener == null ? Collections.EMPTY_LIST
                 : notificationListener.getNotificationsForKeys(notificationKeys);
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 39c2db2..9b2141f 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -20,15 +20,18 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
-import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -36,6 +39,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -58,8 +62,9 @@
         }
     }
 
-    public static Item[] getItemsToPopulate(List<String> shortcutIds, String[] notificationKeys) {
-        boolean hasNotifications = notificationKeys.length > 0;
+    public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
+            @NonNull List<NotificationKeyData> notificationKeys) {
+        boolean hasNotifications = notificationKeys.size() > 0;
         int numNotificationItems = hasNotifications ? 1 : 0;
         int numItems = Math.min(MAX_ITEMS, shortcutIds.size() + numNotificationItems);
         Item[] items = new Item[numItems];
@@ -105,10 +110,22 @@
      * We want the filter to include both static and dynamic shortcuts, so we always
      * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present.
      *
+     * @param shortcutIdToRemoveFirst An id that should be filtered out first, if any.
      * @return a subset of shortcuts, in sorted order, with size <= MAX_ITEMS.
      */
     public static List<ShortcutInfoCompat> sortAndFilterShortcuts(
-            List<ShortcutInfoCompat> shortcuts) {
+            List<ShortcutInfoCompat> shortcuts, @Nullable String shortcutIdToRemoveFirst) {
+        // Remove up to one specific shortcut before sorting and doing somewhat fancy filtering.
+        if (shortcutIdToRemoveFirst != null) {
+            Iterator<ShortcutInfoCompat> shortcutIterator = shortcuts.iterator();
+            while (shortcutIterator.hasNext()) {
+                if (shortcutIterator.next().getId().equals(shortcutIdToRemoveFirst)) {
+                    shortcutIterator.remove();
+                    break;
+                }
+            }
+        }
+
         Collections.sort(shortcuts, SHORTCUT_RANK_COMPARATOR);
         if (shortcuts.size() <= MAX_ITEMS) {
             return shortcuts;
@@ -145,7 +162,8 @@
     public static Runnable createUpdateRunnable(final Launcher launcher, ItemInfo originalInfo,
             final Handler uiHandler, final PopupContainerWithArrow container,
             final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
-            final String[] notificationKeys, final NotificationItemView notificationView) {
+            final List<NotificationKeyData> notificationKeys,
+            final NotificationItemView notificationView) {
         final ComponentName activity = originalInfo.getTargetComponent();
         final UserHandle user = originalInfo.user;
         return new Runnable() {
@@ -162,9 +180,11 @@
                     uiHandler.post(new UpdateNotificationChild(notificationView, infos));
                 }
 
-                final List<ShortcutInfoCompat> shortcuts = PopupPopulator.sortAndFilterShortcuts(
-                        DeepShortcutManager.getInstance(launcher).queryForShortcutsContainer(
-                                activity, shortcutIds, user));
+                List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
+                        .queryForShortcutsContainer(activity, shortcutIds, user);
+                String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
+                        : notificationKeys.get(0).shortcutId;
+                shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
                 for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
                     final ShortcutInfoCompat shortcut = shortcuts.get(i);
                     ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
diff --git a/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
index 0843d9b..2ad9b35 100644
--- a/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
+++ b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
@@ -58,11 +58,34 @@
                 MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
     }
 
+    @Test
+    public void testDeDupeShortcutId() {
+        // Successfully remove one of the shortcuts
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 2, 0, generateId(true, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 2, generateId(false, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 1, generateId(false, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 1, 2, generateId(true, 1));
+        // Successfully keep all shortcuts when id doesn't exist
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(false, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(true, 4));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(false, 4));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(true, 4));
+    }
+
+    private String generateId(boolean isStatic, int rank) {
+        return (isStatic ? "static" : "dynamic") + rank;
+    }
+
     private void filterShortcutsAndAssertNumStaticAndDynamic(
             List<ShortcutInfoCompat> shortcuts, int expectedStatic, int expectedDynamic) {
+        filterShortcutsAndAssertNumStaticAndDynamic(shortcuts, expectedStatic, expectedDynamic, null);
+    }
+
+    private void filterShortcutsAndAssertNumStaticAndDynamic(List<ShortcutInfoCompat> shortcuts,
+            int expectedStatic, int expectedDynamic, String shortcutIdToRemove) {
         Collections.shuffle(shortcuts);
         List<ShortcutInfoCompat> filteredShortcuts = PopupPopulator.sortAndFilterShortcuts(
-                shortcuts);
+                shortcuts, shortcutIdToRemove);
         assertIsSorted(filteredShortcuts);
 
         int numStatic = 0;
@@ -113,6 +136,7 @@
     private class Shortcut extends ShortcutInfoCompat {
         private boolean mIsStatic;
         private int mRank;
+        private String mId;
 
         public Shortcut(ShortcutInfo shortcutInfo) {
             super(shortcutInfo);
@@ -122,6 +146,7 @@
             this(null);
             mIsStatic = isStatic;
             mRank = rank;
+            mId = generateId(isStatic, rank);
         }
 
         @Override
@@ -138,5 +163,10 @@
         public int getRank() {
             return mRank;
         }
+
+        @Override
+        public String getId() {
+            return mId;
+        }
     }
 }
\ No newline at end of file