diff options
8 files changed, 161 insertions, 41 deletions
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java index 532396cdd..d7aa4e8b3 100644 --- a/src/com/android/launcher3/badge/BadgeInfo.java +++ b/src/com/android/launcher3/badge/BadgeInfo.java @@ -25,6 +25,7 @@ import android.graphics.drawable.Drawable; 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; @@ -42,7 +43,7 @@ public class BadgeInfo { * 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; /** This will only be initialized if the badge should display the notification icon. */ private NotificationInfo mNotificationInfo; @@ -61,7 +62,7 @@ public class BadgeInfo { /** * Returns whether the notification was added (false if it already existed). */ - public boolean addNotificationKeyIfNotExists(String notificationKey) { + public boolean addNotificationKeyIfNotExists(NotificationKeyData notificationKey) { if (mNotificationKeys.contains(notificationKey)) { return false; } @@ -71,11 +72,11 @@ public class BadgeInfo { /** * Returns whether the notification was removed (false if it didn't exist). */ - public boolean removeNotificationKey(String notificationKey) { + public boolean removeNotificationKey(NotificationKeyData notificationKey) { return mNotificationKeys.remove(notificationKey); } - public List<String> getNotificationKeys() { + public List<NotificationKeyData> getNotificationKeys() { return mNotificationKeys; } diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java index ba7675c5d..f6779b331 100644 --- a/src/com/android/launcher3/notification/NotificationInfo.java +++ b/src/com/android/launcher3/notification/NotificationInfo.java @@ -38,7 +38,7 @@ import com.android.launcher3.util.PackageUserKey; * 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 000000000..b3ff8dadd --- /dev/null +++ b/src/com/android/launcher3/notification/NotificationKeyData.java @@ -0,0 +1,60 @@ +/* + * 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.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; + + private NotificationKeyData(String notificationKey, String shortcutId) { + this.notificationKey = notificationKey; + this.shortcutId = shortcutId; + } + + public static NotificationKeyData fromNotification(StatusBarNotification sbn) { + return new NotificationKeyData(sbn.getKey(), sbn.getNotification().getShortcutId()); + } + + 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 16cb5fbb0..75a1b8ab5 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -94,8 +94,8 @@ public class NotificationListener extends NotificationListenerService { 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 @@ public class NotificationListener extends NotificationListenerService { */ 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 @@ public class NotificationListener extends NotificationListenerService { @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 class NotificationListener extends NotificationListenerService { } 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 1eac07608..b2018b92b 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.DragLayer; 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 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } 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 class PopupContainerWithArrow extends AbstractFloatingView implements Dra } 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 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra // 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 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra 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 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra 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 f0ccb1bb6..43b2b5299 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -18,6 +18,7 @@ package com.android.launcher3.popup; 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.Launcher; 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,8 +60,8 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan } @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. if (badgeInfo == null) { @@ -84,7 +86,8 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan } @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 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan badgeInfo = new BadgeInfo(packageUserKey); mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo); } - badgeInfo.addNotificationKeyIfNotExists(notification.getKey()); + badgeInfo.addNotificationKeyIfNotExists(NotificationKeyData + .fromNotification(notification)); } // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges. @@ -177,8 +181,9 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan 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 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan 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 39c2db2d5..9b2141f12 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -20,15 +20,18 @@ import android.content.ComponentName; 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 com.android.launcher3.shortcuts.ShortcutInfoCompat; 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 class PopupPopulator { } } - 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 @@ public class PopupPopulator { * 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 class PopupPopulator { 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 @@ public class PopupPopulator { 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 0843d9b59..2ad9b35ae 100644 --- a/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java +++ b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java @@ -58,11 +58,34 @@ public class PopupPopulatorTest { 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 @@ public class PopupPopulatorTest { private class Shortcut extends ShortcutInfoCompat { private boolean mIsStatic; private int mRank; + private String mId; public Shortcut(ShortcutInfo shortcutInfo) { super(shortcutInfo); @@ -122,6 +146,7 @@ public class PopupPopulatorTest { this(null); mIsStatic = isStatic; mRank = rank; + mId = generateId(isStatic, rank); } @Override @@ -138,5 +163,10 @@ public class PopupPopulatorTest { public int getRank() { return mRank; } + + @Override + public String getId() { + return mId; + } } }
\ No newline at end of file |