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.BubbleTextView; 27 import com.android.launcher3.allapps.ActivityAllAppsContainerView; 28 import com.android.launcher3.dot.DotInfo; 29 import com.android.launcher3.folder.Folder; 30 import com.android.launcher3.folder.FolderIcon; 31 import com.android.launcher3.model.data.FolderInfo; 32 import com.android.launcher3.model.data.ItemInfo; 33 import com.android.launcher3.notification.NotificationKeyData; 34 import com.android.launcher3.notification.NotificationListener; 35 import com.android.launcher3.util.ComponentKey; 36 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; 37 import com.android.launcher3.util.PackageUserKey; 38 import com.android.launcher3.util.ShortcutUtil; 39 import com.android.launcher3.views.ActivityContext; 40 41 import java.io.PrintWriter; 42 import java.util.Arrays; 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.function.Predicate; 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 ActivityContext mContext; 57 58 /** Maps packages to their DotInfo's . */ 59 private final Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>(); 60 61 /** Maps launcher activity components to a count of how many shortcuts they have. */ 62 private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>(); 63 PopupDataProvider(ActivityContext context)64 public PopupDataProvider(ActivityContext context) { 65 mContext = context; 66 } 67 updateNotificationDots(Predicate<PackageUserKey> updatedDots)68 private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) { 69 final PackageUserKey packageUserKey = new PackageUserKey(null, null); 70 Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info) 71 || updatedDots.test(packageUserKey); 72 73 ItemOperator op = (info, v) -> { 74 if (v instanceof BubbleTextView && info != null && matcher.test(info)) { 75 ((BubbleTextView) v).applyDotState(info, true /* animate */); 76 } else if (v instanceof FolderIcon icon 77 && info instanceof FolderInfo fi && fi.anyMatch(matcher)) { 78 icon.updateDotInfo(); 79 } 80 81 // process all the shortcuts 82 return false; 83 }; 84 85 mContext.getContent().mapOverItems(op); 86 Folder folder = Folder.getOpen(mContext); 87 if (folder != null) { 88 folder.mapOverItems(op); 89 } 90 91 ActivityAllAppsContainerView<?> appsView = mContext.getAppsView(); 92 if (appsView != null) { 93 appsView.getAppsStore().updateNotificationDots(updatedDots); 94 } 95 } 96 97 @Override onNotificationPosted(PackageUserKey postedPackageUserKey, NotificationKeyData notificationKey)98 public void onNotificationPosted(PackageUserKey postedPackageUserKey, 99 NotificationKeyData notificationKey) { 100 DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey); 101 if (dotInfo == null) { 102 dotInfo = new DotInfo(); 103 mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo); 104 } 105 if (dotInfo.addOrUpdateNotificationKey(notificationKey)) { 106 updateNotificationDots(postedPackageUserKey::equals); 107 } 108 } 109 110 @Override onNotificationRemoved(PackageUserKey removedPackageUserKey, NotificationKeyData notificationKey)111 public void onNotificationRemoved(PackageUserKey removedPackageUserKey, 112 NotificationKeyData notificationKey) { 113 DotInfo oldDotInfo = mPackageUserToDotInfos.get(removedPackageUserKey); 114 if (oldDotInfo != null && oldDotInfo.removeNotificationKey(notificationKey)) { 115 if (oldDotInfo.getNotificationKeys().size() == 0) { 116 mPackageUserToDotInfos.remove(removedPackageUserKey); 117 } 118 updateNotificationDots(removedPackageUserKey::equals); 119 } 120 } 121 122 @Override onNotificationFullRefresh(List<StatusBarNotification> activeNotifications)123 public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) { 124 if (activeNotifications == null) return; 125 // This will contain the PackageUserKeys which have updated dots. 126 HashMap<PackageUserKey, DotInfo> updatedDots = new HashMap<>(mPackageUserToDotInfos); 127 mPackageUserToDotInfos.clear(); 128 for (StatusBarNotification notification : activeNotifications) { 129 PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification); 130 DotInfo dotInfo = mPackageUserToDotInfos.get(packageUserKey); 131 if (dotInfo == null) { 132 dotInfo = new DotInfo(); 133 mPackageUserToDotInfos.put(packageUserKey, dotInfo); 134 } 135 dotInfo.addOrUpdateNotificationKey(NotificationKeyData.fromNotification(notification)); 136 } 137 138 // Add and remove from updatedDots so it contains the PackageUserKeys of updated dots. 139 for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) { 140 DotInfo prevDot = updatedDots.get(packageUserKey); 141 DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey); 142 if (prevDot == null 143 || prevDot.getNotificationCount() != newDot.getNotificationCount()) { 144 updatedDots.put(packageUserKey, newDot); 145 } else { 146 // No need to update the dot if it already existed (no visual change). 147 // Note that if the dot was removed entirely, we wouldn't reach this point because 148 // this loop only includes active notifications added above. 149 updatedDots.remove(packageUserKey); 150 } 151 } 152 153 if (!updatedDots.isEmpty()) { 154 updateNotificationDots(updatedDots::containsKey); 155 } 156 } 157 setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy)158 public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) { 159 mDeepShortcutMap = deepShortcutMapCopy; 160 if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap); 161 } 162 getShortcutCountForItem(ItemInfo info)163 public int getShortcutCountForItem(ItemInfo info) { 164 if (!ShortcutUtil.supportsDeepShortcuts(info)) { 165 return 0; 166 } 167 ComponentName component = info.getTargetComponent(); 168 if (component == null) { 169 return 0; 170 } 171 172 Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user)); 173 return count == null ? 0 : count; 174 } 175 getDotInfoForItem(@onNull ItemInfo info)176 public @Nullable DotInfo getDotInfoForItem(@NonNull ItemInfo info) { 177 if (!ShortcutUtil.supportsShortcuts(info)) { 178 return null; 179 } 180 DotInfo dotInfo = mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info)); 181 if (dotInfo == null) { 182 return null; 183 } 184 185 // If the item represents a pinned shortcut, ensure that there is a notification 186 // for this shortcut 187 String shortcutId = ShortcutUtil.getShortcutIdIfPinnedShortcut(info); 188 if (shortcutId == null) { 189 return dotInfo; 190 } 191 String[] personKeys = ShortcutUtil.getPersonKeysIfPinnedShortcut(info); 192 return (dotInfo.getNotificationKeys().stream().anyMatch(notification -> { 193 if (notification.shortcutId != null) { 194 return notification.shortcutId.equals(shortcutId); 195 } 196 if (notification.personKeysFromNotification.length != 0) { 197 return Arrays.equals(notification.personKeysFromNotification, personKeys); 198 } 199 return false; 200 })) ? dotInfo : null; 201 } 202 203 public void dump(String prefix, PrintWriter writer) { 204 writer.println(prefix + "PopupDataProvider:"); 205 writer.println(prefix + "\tmPackageUserToDotInfos:" + mPackageUserToDotInfos); 206 } 207 } 208