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.model.WidgetItem; 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 import com.android.launcher3.widget.WidgetListRowEntry; 36 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.Iterator; 41 import java.util.List; 42 import java.util.Map; 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 new SystemShortcut.Install() 57 }; 58 59 private final Launcher mLauncher; 60 61 /** Maps launcher activity components to their list of shortcut ids. */ 62 private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>(); 63 /** Maps packages to their BadgeInfo's . */ 64 private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>(); 65 /** Maps packages to their Widgets */ 66 private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>(); 67 68 public PopupDataProvider(Launcher launcher) { 69 mLauncher = launcher; 70 } 71 72 @Override 73 public void onNotificationPosted(PackageUserKey postedPackageUserKey, 74 NotificationKeyData notificationKey, boolean shouldBeFilteredOut) { 75 BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey); 76 boolean badgeShouldBeRefreshed; 77 if (badgeInfo == null) { 78 if (!shouldBeFilteredOut) { 79 BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey); 80 newBadgeInfo.addOrUpdateNotificationKey(notificationKey); 81 mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo); 82 badgeShouldBeRefreshed = true; 83 } else { 84 badgeShouldBeRefreshed = false; 85 } 86 } else { 87 badgeShouldBeRefreshed = shouldBeFilteredOut 88 ? badgeInfo.removeNotificationKey(notificationKey) 89 : badgeInfo.addOrUpdateNotificationKey(notificationKey); 90 if (badgeInfo.getNotificationKeys().size() == 0) { 91 mPackageUserToBadgeInfos.remove(postedPackageUserKey); 92 } 93 } 94 if (badgeShouldBeRefreshed) { 95 mLauncher.updateIconBadges(Utilities.singletonHashSet(postedPackageUserKey)); 96 } 97 } 98 99 @Override 100 public void onNotificationRemoved(PackageUserKey removedPackageUserKey, 101 NotificationKeyData notificationKey) { 102 BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey); 103 if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) { 104 if (oldBadgeInfo.getNotificationKeys().size() == 0) { 105 mPackageUserToBadgeInfos.remove(removedPackageUserKey); 106 } 107 mLauncher.updateIconBadges(Utilities.singletonHashSet(removedPackageUserKey)); 108 trimNotifications(mPackageUserToBadgeInfos); 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 mLauncher.updateIconBadges(updatedBadges.keySet()); 144 } 145 trimNotifications(updatedBadges); 146 } 147 148 private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) { 149 PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); 150 if (openContainer != null) { 151 openContainer.trimNotifications(updatedBadges); 152 } 153 } 154 155 public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) { 156 mDeepShortcutMap = deepShortcutMapCopy; 157 if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap); 158 } 159 160 public List<String> getShortcutIdsForItem(ItemInfo info) { 161 if (!DeepShortcutManager.supportsShortcuts(info)) { 162 return Collections.EMPTY_LIST; 163 } 164 ComponentName component = info.getTargetComponent(); 165 if (component == null) { 166 return Collections.EMPTY_LIST; 167 } 168 169 List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user)); 170 return ids == null ? Collections.EMPTY_LIST : ids; 171 } 172 173 public BadgeInfo getBadgeInfoForItem(ItemInfo info) { 174 if (!DeepShortcutManager.supportsShortcuts(info)) { 175 return null; 176 } 177 178 return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info)); 179 } 180 181 public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) { 182 BadgeInfo badgeInfo = getBadgeInfoForItem(info); 183 return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.getNotificationKeys(); 184 } 185 186 /** This makes a potentially expensive binder call and should be run on a background thread. */ 187 public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys( 188 List<NotificationKeyData> notificationKeys) { 189 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); 190 return notificationListener == null ? Collections.EMPTY_LIST 191 : notificationListener.getNotificationsForKeys(notificationKeys); 192 } 193 194 public @NonNull List<SystemShortcut> getEnabledSystemShortcutsForItem(ItemInfo info) { 195 List<SystemShortcut> systemShortcuts = new ArrayList<>(); 196 for (SystemShortcut systemShortcut : SYSTEM_SHORTCUTS) { 197 if (systemShortcut.getOnClickListener(mLauncher, info) != null) { 198 systemShortcuts.add(systemShortcut); 199 } 200 } 201 return systemShortcuts; 202 } 203 204 public void cancelNotification(String notificationKey) { 205 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); 206 if (notificationListener == null) { 207 return; 208 } 209 notificationListener.cancelNotificationFromLauncher(notificationKey); 210 } 211 212 public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) { 213 mAllWidgets = allWidgets; 214 } 215 216 public ArrayList<WidgetListRowEntry> getAllWidgets() { 217 return mAllWidgets; 218 } 219 220 public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { 221 for (WidgetListRowEntry entry : mAllWidgets) { 222 if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { 223 ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets); 224 // Remove widgets not associated with the correct user. 225 Iterator<WidgetItem> iterator = widgets.iterator(); 226 while (iterator.hasNext()) { 227 if (!iterator.next().user.equals(packageUserKey.mUser)) { 228 iterator.remove(); 229 } 230 } 231 return widgets.isEmpty() ? null : widgets; 232 } 233 } 234 return null; 235 } 236 } 237