1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.popup; 18 19 import android.content.ComponentName; 20 import android.service.notification.StatusBarNotification; 21 import android.support.annotation.NonNull; 22 import android.util.Log; 23 24 import com.android.launcher3.ItemInfo; 25 import com.android.launcher3.Launcher; 26 import com.android.launcher3.Utilities; 27 import com.android.launcher3.badge.BadgeInfo; 28 import com.android.launcher3.notification.NotificationInfo; 29 import com.android.launcher3.notification.NotificationKeyData; 30 import com.android.launcher3.notification.NotificationListener; 31 import com.android.launcher3.shortcuts.DeepShortcutManager; 32 import com.android.launcher3.util.ComponentKey; 33 import com.android.launcher3.util.MultiHashMap; 34 import com.android.launcher3.util.PackageUserKey; 35 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** 45 * Provides data for the popup menu that appears after long-clicking on apps. 46 */ 47 public class PopupDataProvider implements NotificationListener.NotificationsChangedListener { 48 49 private static final boolean LOGD = false; 50 private static final String TAG = "PopupDataProvider"; 51 52 /** Note that these are in order of priority. */ 53 private static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] { 54 new SystemShortcut.AppInfo(), 55 new SystemShortcut.Widgets(), 56 }; 57 58 private final Launcher mLauncher; 59 60 /** Maps launcher activity components to their list of shortcut ids. */ 61 private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>(); 62 /** Maps packages to their BadgeInfo's . */ 63 private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>(); 64 65 public PopupDataProvider(Launcher launcher) { 66 mLauncher = launcher; 67 } 68 69 @Override 70 public void onNotificationPosted(PackageUserKey postedPackageUserKey, 71 NotificationKeyData notificationKey, boolean shouldBeFilteredOut) { 72 BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey); 73 boolean badgeShouldBeRefreshed; 74 if (badgeInfo == null) { 75 if (!shouldBeFilteredOut) { 76 BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey); 77 newBadgeInfo.addOrUpdateNotificationKey(notificationKey); 78 mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo); 79 badgeShouldBeRefreshed = true; 80 } else { 81 badgeShouldBeRefreshed = false; 82 } 83 } else { 84 badgeShouldBeRefreshed = shouldBeFilteredOut 85 ? badgeInfo.removeNotificationKey(notificationKey) 86 : badgeInfo.addOrUpdateNotificationKey(notificationKey); 87 if (badgeInfo.getNotificationKeys().size() == 0) { 88 mPackageUserToBadgeInfos.remove(postedPackageUserKey); 89 } 90 } 91 updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey), 92 badgeShouldBeRefreshed); 93 } 94 95 @Override 96 public void onNotificationRemoved(PackageUserKey removedPackageUserKey, 97 NotificationKeyData notificationKey) { 98 BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey); 99 if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) { 100 if (oldBadgeInfo.getNotificationKeys().size() == 0) { 101 mPackageUserToBadgeInfos.remove(removedPackageUserKey); 102 } 103 updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey)); 104 105 PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); 106 if (openContainer != null) { 107 openContainer.trimNotifications(mPackageUserToBadgeInfos); 108 } 109 } 110 } 111 112 @Override 113 public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) { 114 if (activeNotifications == null) return; 115 // This will contain the PackageUserKeys which have updated badges. 116 HashMap<PackageUserKey, BadgeInfo> updatedBadges = new HashMap<>(mPackageUserToBadgeInfos); 117 mPackageUserToBadgeInfos.clear(); 118 for (StatusBarNotification notification : activeNotifications) { 119 PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification); 120 BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(packageUserKey); 121 if (badgeInfo == null) { 122 badgeInfo = new BadgeInfo(packageUserKey); 123 mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo); 124 } 125 badgeInfo.addOrUpdateNotificationKey(NotificationKeyData 126 .fromNotification(notification)); 127 } 128 129 // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges. 130 for (PackageUserKey packageUserKey : mPackageUserToBadgeInfos.keySet()) { 131 BadgeInfo prevBadge = updatedBadges.get(packageUserKey); 132 BadgeInfo newBadge = mPackageUserToBadgeInfos.get(packageUserKey); 133 if (prevBadge == null) { 134 updatedBadges.put(packageUserKey, newBadge); 135 } else { 136 if (!prevBadge.shouldBeInvalidated(newBadge)) { 137 updatedBadges.remove(packageUserKey); 138 } 139 } 140 } 141 142 if (!updatedBadges.isEmpty()) { 143 updateLauncherIconBadges(updatedBadges.keySet()); 144 } 145 146 PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); 147 if (openContainer != null) { 148 openContainer.trimNotifications(updatedBadges); 149 } 150 } 151 152 private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges) { 153 updateLauncherIconBadges(updatedBadges, true); 154 } 155 156 /** 157 * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges. 158 * @param updatedBadges The packages whose badges should be refreshed (either a notification was 159 * added or removed, or the badge should show the notification icon). 160 * @param shouldRefresh An optional parameter that will allow us to only refresh badges that 161 * have actually changed. If a notification updated its content but not 162 * its count or icon, then the badge doesn't change. 163 */ 164 private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges, 165 boolean shouldRefresh) { 166 Iterator<PackageUserKey> iterator = updatedBadges.iterator(); 167 while (iterator.hasNext()) { 168 BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next()); 169 if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !shouldRefresh) { 170 // The notification icon isn't used, and the badge hasn't changed 171 // so there is no update to be made. 172 iterator.remove(); 173 } 174 } 175 if (!updatedBadges.isEmpty()) { 176 mLauncher.updateIconBadges(updatedBadges); 177 } 178 } 179 180 /** 181 * Determines whether the badge should show a notification icon rather than a number, 182 * and sets that icon on the BadgeInfo if so. 183 * @param badgeInfo The badge to update with an icon (null if it shouldn't show one). 184 * @return Whether the badge icon potentially changed (true unless it stayed null). 185 */ 186 private boolean updateBadgeIcon(BadgeInfo badgeInfo) { 187 boolean hadNotificationToShow = badgeInfo.hasNotificationToShow(); 188 NotificationInfo notificationInfo = null; 189 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); 190 if (notificationListener != null && badgeInfo.getNotificationKeys().size() >= 1) { 191 // Look for the most recent notification that has an icon that should be shown in badge. 192 for (NotificationKeyData notificationKeyData : badgeInfo.getNotificationKeys()) { 193 String notificationKey = notificationKeyData.notificationKey; 194 StatusBarNotification[] activeNotifications = notificationListener 195 .getActiveNotifications(new String[]{notificationKey}); 196 if (activeNotifications.length == 1) { 197 notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]); 198 if (notificationInfo.shouldShowIconInBadge()) { 199 // Found an appropriate icon. 200 break; 201 } else { 202 // Keep looking. 203 notificationInfo = null; 204 } 205 } 206 } 207 } 208 badgeInfo.setNotificationToShow(notificationInfo); 209 return hadNotificationToShow || badgeInfo.hasNotificationToShow(); 210 } 211 212 public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) { 213 mDeepShortcutMap = deepShortcutMapCopy; 214 if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap); 215 } 216 217 public List<String> getShortcutIdsForItem(ItemInfo info) { 218 if (!DeepShortcutManager.supportsShortcuts(info)) { 219 return Collections.EMPTY_LIST; 220 } 221 ComponentName component = info.getTargetComponent(); 222 if (component == null) { 223 return Collections.EMPTY_LIST; 224 } 225 226 List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user)); 227 return ids == null ? Collections.EMPTY_LIST : ids; 228 } 229 230 public BadgeInfo getBadgeInfoForItem(ItemInfo info) { 231 if (!DeepShortcutManager.supportsShortcuts(info)) { 232 return null; 233 } 234 235 return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info)); 236 } 237 238 public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) { 239 BadgeInfo badgeInfo = getBadgeInfoForItem(info); 240 return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.getNotificationKeys(); 241 } 242 243 /** This makes a potentially expensive binder call and should be run on a background thread. */ 244 public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys( 245 List<NotificationKeyData> notificationKeys) { 246 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); 247 return notificationListener == null ? Collections.EMPTY_LIST 248 : notificationListener.getNotificationsForKeys(notificationKeys); 249 } 250 251 public @NonNull List<SystemShortcut> getEnabledSystemShortcutsForItem(ItemInfo info) { 252 List<SystemShortcut> systemShortcuts = new ArrayList<>(); 253 for (SystemShortcut systemShortcut : SYSTEM_SHORTCUTS) { 254 if (systemShortcut.getOnClickListener(mLauncher, info) != null) { 255 systemShortcuts.add(systemShortcut); 256 } 257 } 258 return systemShortcuts; 259 } 260 261 public void cancelNotification(String notificationKey) { 262 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); 263 if (notificationListener == null) { 264 return; 265 } 266 notificationListener.cancelNotification(notificationKey); 267 } 268 } 269