Merge "De-dupe shortcuts with the same id as the main notification." into ub-launcher3-dorval

am: c3dfed43eb

Change-Id: I60bbede53d61f117596e90eb3363a4cb5eac7245
This commit is contained in:
Tony Wickham 2017-03-27 17:56:55 +00:00 committed by android-build-merger
commit c455293fa3
8 changed files with 161 additions and 41 deletions

View File

@ -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;
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}
}