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.util.Log; 22 23 import androidx.annotation.NonNull; 24 import androidx.annotation.Nullable; 25 26 import com.android.launcher3.dot.DotInfo; 27 import com.android.launcher3.model.WidgetItem; 28 import com.android.launcher3.model.data.ItemInfo; 29 import com.android.launcher3.notification.NotificationKeyData; 30 import com.android.launcher3.notification.NotificationListener; 31 import com.android.launcher3.util.ComponentKey; 32 import com.android.launcher3.util.PackageUserKey; 33 import com.android.launcher3.util.ShortcutUtil; 34 import com.android.launcher3.widget.WidgetListRowEntry; 35 36 import java.io.PrintWriter; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.Iterator; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.function.Consumer; 45 import java.util.function.Predicate; 46 import java.util.stream.Collectors; 47 48 /** 49 * Provides data for the popup menu that appears after long-clicking on apps. 50 */ 51 public class PopupDataProvider implements NotificationListener.NotificationsChangedListener { 52 53 private static final boolean LOGD = false; 54 private static final String TAG = "PopupDataProvider"; 55 56 private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener; 57 58 /** Maps launcher activity components to a count of how many shortcuts they have. */ 59 private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>(); 60 /** Maps packages to their DotInfo's . */ 61 private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>(); 62 /** Maps packages to their Widgets */ 63 private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>(); 64 65 private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE; 66 PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener)67 public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) { 68 mNotificationDotsChangeListener = notificationDotsChangeListener; 69 } 70 updateNotificationDots(Predicate<PackageUserKey> updatedDots)71 private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) { 72 mNotificationDotsChangeListener.accept(updatedDots); 73 mChangeListener.onNotificationDotsUpdated(updatedDots); 74 } 75 76 @Override onNotificationPosted(PackageUserKey postedPackageUserKey, NotificationKeyData notificationKey)77 public void onNotificationPosted(PackageUserKey postedPackageUserKey, 78 NotificationKeyData notificationKey) { 79 DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey); 80 if (dotInfo == null) { 81 dotInfo = new DotInfo(); 82 mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo); 83 } 84 if (dotInfo.addOrUpdateNotificationKey(notificationKey)) { 85 updateNotificationDots(postedPackageUserKey::equals); 86 } 87 } 88 89 @Override onNotificationRemoved(PackageUserKey removedPackageUserKey, NotificationKeyData notificationKey)90 public void onNotificationRemoved(PackageUserKey removedPackageUserKey, 91 NotificationKeyData notificationKey) { 92 DotInfo oldDotInfo = mPackageUserToDotInfos.get(removedPackageUserKey); 93 if (oldDotInfo != null && oldDotInfo.removeNotificationKey(notificationKey)) { 94 if (oldDotInfo.getNotificationKeys().size() == 0) { 95 mPackageUserToDotInfos.remove(removedPackageUserKey); 96 } 97 updateNotificationDots(removedPackageUserKey::equals); 98 trimNotifications(mPackageUserToDotInfos); 99 } 100 } 101 102 @Override onNotificationFullRefresh(List<StatusBarNotification> activeNotifications)103 public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) { 104 if (activeNotifications == null) return; 105 // This will contain the PackageUserKeys which have updated dots. 106 HashMap<PackageUserKey, DotInfo> updatedDots = new HashMap<>(mPackageUserToDotInfos); 107 mPackageUserToDotInfos.clear(); 108 for (StatusBarNotification notification : activeNotifications) { 109 PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification); 110 DotInfo dotInfo = mPackageUserToDotInfos.get(packageUserKey); 111 if (dotInfo == null) { 112 dotInfo = new DotInfo(); 113 mPackageUserToDotInfos.put(packageUserKey, dotInfo); 114 } 115 dotInfo.addOrUpdateNotificationKey(NotificationKeyData.fromNotification(notification)); 116 } 117 118 // Add and remove from updatedDots so it contains the PackageUserKeys of updated dots. 119 for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) { 120 DotInfo prevDot = updatedDots.get(packageUserKey); 121 DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey); 122 if (prevDot == null 123 || prevDot.getNotificationCount() != newDot.getNotificationCount()) { 124 updatedDots.put(packageUserKey, newDot); 125 } else { 126 // No need to update the dot if it already existed (no visual change). 127 // Note that if the dot was removed entirely, we wouldn't reach this point because 128 // this loop only includes active notifications added above. 129 updatedDots.remove(packageUserKey); 130 } 131 } 132 133 if (!updatedDots.isEmpty()) { 134 updateNotificationDots(updatedDots::containsKey); 135 } 136 trimNotifications(updatedDots); 137 } 138 trimNotifications(Map<PackageUserKey, DotInfo> updatedDots)139 private void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) { 140 mChangeListener.trimNotifications(updatedDots); 141 } 142 setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy)143 public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) { 144 mDeepShortcutMap = deepShortcutMapCopy; 145 if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap); 146 } 147 getShortcutCountForItem(ItemInfo info)148 public int getShortcutCountForItem(ItemInfo info) { 149 if (!ShortcutUtil.supportsDeepShortcuts(info)) { 150 return 0; 151 } 152 ComponentName component = info.getTargetComponent(); 153 if (component == null) { 154 return 0; 155 } 156 157 Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user)); 158 return count == null ? 0 : count; 159 } 160 getDotInfoForItem(@onNull ItemInfo info)161 public @Nullable DotInfo getDotInfoForItem(@NonNull ItemInfo info) { 162 if (!ShortcutUtil.supportsShortcuts(info)) { 163 return null; 164 } 165 DotInfo dotInfo = mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info)); 166 if (dotInfo == null) { 167 return null; 168 } 169 List<NotificationKeyData> notifications = getNotificationsForItem( 170 info, dotInfo.getNotificationKeys()); 171 if (notifications.isEmpty()) { 172 return null; 173 } 174 return dotInfo; 175 } 176 getNotificationKeysForItem(ItemInfo info)177 public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) { 178 DotInfo dotInfo = getDotInfoForItem(info); 179 return dotInfo == null ? Collections.EMPTY_LIST 180 : getNotificationsForItem(info, dotInfo.getNotificationKeys()); 181 } 182 cancelNotification(String notificationKey)183 public void cancelNotification(String notificationKey) { 184 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); 185 if (notificationListener == null) { 186 return; 187 } 188 notificationListener.cancelNotificationFromLauncher(notificationKey); 189 } 190 setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets)191 public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) { 192 mAllWidgets = allWidgets; 193 mChangeListener.onWidgetsBound(); 194 } 195 setChangeListener(PopupDataChangeListener listener)196 public void setChangeListener(PopupDataChangeListener listener) { 197 mChangeListener = listener == null ? PopupDataChangeListener.INSTANCE : listener; 198 } 199 getAllWidgets()200 public ArrayList<WidgetListRowEntry> getAllWidgets() { 201 return mAllWidgets; 202 } 203 getWidgetsForPackageUser(PackageUserKey packageUserKey)204 public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { 205 for (WidgetListRowEntry entry : mAllWidgets) { 206 if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { 207 ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets); 208 // Remove widgets not associated with the correct user. 209 Iterator<WidgetItem> iterator = widgets.iterator(); 210 while (iterator.hasNext()) { 211 if (!iterator.next().user.equals(packageUserKey.mUser)) { 212 iterator.remove(); 213 } 214 } 215 return widgets.isEmpty() ? null : widgets; 216 } 217 } 218 return null; 219 } 220 221 /** 222 * Returns a list of notifications that are relevant to given ItemInfo. 223 */ getNotificationsForItem( @onNull ItemInfo info, @NonNull List<NotificationKeyData> notifications)224 public static @NonNull List<NotificationKeyData> getNotificationsForItem( 225 @NonNull ItemInfo info, @NonNull List<NotificationKeyData> notifications) { 226 String shortcutId = ShortcutUtil.getShortcutIdIfPinnedShortcut(info); 227 if (shortcutId == null) { 228 return notifications; 229 } 230 String[] personKeys = ShortcutUtil.getPersonKeysIfPinnedShortcut(info); 231 return notifications.stream().filter((NotificationKeyData notification) -> { 232 if (notification.shortcutId != null) { 233 return notification.shortcutId.equals(shortcutId); 234 } 235 if (notification.personKeysFromNotification.length != 0) { 236 return Arrays.equals(notification.personKeysFromNotification, personKeys); 237 } 238 return false; 239 }).collect(Collectors.toList()); 240 } 241 242 public void dump(String prefix, PrintWriter writer) { 243 writer.println(prefix + "PopupDataProvider:"); 244 writer.println(prefix + "\tmPackageUserToDotInfos:" + mPackageUserToDotInfos); 245 } 246 247 public interface PopupDataChangeListener { 248 249 PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { }; 250 251 default void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) { } 252 253 default void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) { } 254 255 default void onWidgetsBound() { } 256 } 257 } 258