2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3.popup;
19 import android.content.ComponentName;
20 import android.service.notification.StatusBarNotification;
21 import android.support.annotation.NonNull;
22 import android.util.Log;
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;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.List;
44 * Provides data for the popup menu that appears after long-clicking on apps.
46 public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {
48 private static final boolean LOGD = false;
49 private static final String TAG = "PopupDataProvider";
51 /** Note that these are in order of priority. */
52 public static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] {
53 new SystemShortcut.AppInfo(),
54 new SystemShortcut.Widgets(),
57 private final Launcher mLauncher;
59 /** Maps launcher activity components to their list of shortcut ids. */
60 private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
61 /** Maps packages to their BadgeInfo's . */
62 private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
64 public PopupDataProvider(Launcher launcher) {
69 public void onNotificationPosted(PackageUserKey postedPackageUserKey,
70 NotificationKeyData notificationKey, boolean shouldBeFilteredOut) {
71 BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey);
72 boolean badgeShouldBeRefreshed;
73 if (badgeInfo == null) {
74 if (!shouldBeFilteredOut) {
75 BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey);
76 newBadgeInfo.addOrUpdateNotificationKey(notificationKey);
77 mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo);
78 badgeShouldBeRefreshed = true;
80 badgeShouldBeRefreshed = false;
83 badgeShouldBeRefreshed = shouldBeFilteredOut
84 ? badgeInfo.removeNotificationKey(notificationKey)
85 : badgeInfo.addOrUpdateNotificationKey(notificationKey);
86 if (badgeInfo.getNotificationCount() == 0) {
87 mPackageUserToBadgeInfos.remove(postedPackageUserKey);
90 updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey),
91 badgeShouldBeRefreshed);
95 public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
96 NotificationKeyData notificationKey) {
97 BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey);
98 if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) {
99 if (oldBadgeInfo.getNotificationCount() == 0) {
100 mPackageUserToBadgeInfos.remove(removedPackageUserKey);
102 updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey));
104 PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
105 if (openContainer != null) {
106 openContainer.trimNotifications(mPackageUserToBadgeInfos);
112 public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
113 if (activeNotifications == null) return;
114 // This will contain the PackageUserKeys which have updated badges.
115 HashMap<PackageUserKey, BadgeInfo> updatedBadges = new HashMap<>(mPackageUserToBadgeInfos);
116 mPackageUserToBadgeInfos.clear();
117 for (StatusBarNotification notification : activeNotifications) {
118 PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
119 BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(packageUserKey);
120 if (badgeInfo == null) {
121 badgeInfo = new BadgeInfo(packageUserKey);
122 mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo);
124 badgeInfo.addOrUpdateNotificationKey(NotificationKeyData
125 .fromNotification(notification));
128 // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges.
129 for (PackageUserKey packageUserKey : mPackageUserToBadgeInfos.keySet()) {
130 BadgeInfo prevBadge = updatedBadges.get(packageUserKey);
131 BadgeInfo newBadge = mPackageUserToBadgeInfos.get(packageUserKey);
132 if (prevBadge == null) {
133 updatedBadges.put(packageUserKey, newBadge);
135 if (!prevBadge.shouldBeInvalidated(newBadge)) {
136 updatedBadges.remove(packageUserKey);
141 if (!updatedBadges.isEmpty()) {
142 updateLauncherIconBadges(updatedBadges.keySet());
145 PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
146 if (openContainer != null) {
147 openContainer.trimNotifications(updatedBadges);
151 private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges) {
152 updateLauncherIconBadges(updatedBadges, true);
156 * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges.
157 * @param updatedBadges The packages whose badges should be refreshed (either a notification was
158 * added or removed, or the badge should show the notification icon).
159 * @param shouldRefresh An optional parameter that will allow us to only refresh badges that
160 * have actually changed. If a notification updated its content but not
161 * its count or icon, then the badge doesn't change.
163 private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges,
164 boolean shouldRefresh) {
165 Iterator<PackageUserKey> iterator = updatedBadges.iterator();
166 while (iterator.hasNext()) {
167 BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next());
168 if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !shouldRefresh) {
169 // The notification icon isn't used, and the badge hasn't changed
170 // so there is no update to be made.
174 if (!updatedBadges.isEmpty()) {
175 mLauncher.updateIconBadges(updatedBadges);
180 * Determines whether the badge should show a notification icon rather than a number,
181 * and sets that icon on the BadgeInfo if so.
182 * @param badgeInfo The badge to update with an icon (null if it shouldn't show one).
183 * @return Whether the badge icon potentially changed (true unless it stayed null).
185 private boolean updateBadgeIcon(BadgeInfo badgeInfo) {
186 boolean hadNotificationToShow = badgeInfo.hasNotificationToShow();
187 NotificationInfo notificationInfo = null;
188 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
189 if (notificationListener != null && badgeInfo.getNotificationKeys().size() == 1) {
190 String onlyNotificationKey = badgeInfo.getNotificationKeys().get(0).notificationKey;
191 StatusBarNotification[] activeNotifications = notificationListener
192 .getActiveNotifications(new String[] {onlyNotificationKey});
193 if (activeNotifications.length == 1) {
194 notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]);
195 if (!notificationInfo.shouldShowIconInBadge()) {
196 notificationInfo = null;
200 badgeInfo.setNotificationToShow(notificationInfo);
201 return hadNotificationToShow || badgeInfo.hasNotificationToShow();
204 public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
205 mDeepShortcutMap = deepShortcutMapCopy;
206 if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
209 public List<String> getShortcutIdsForItem(ItemInfo info) {
210 if (!DeepShortcutManager.supportsShortcuts(info)) {
211 return Collections.EMPTY_LIST;
213 ComponentName component = info.getTargetComponent();
214 if (component == null) {
215 return Collections.EMPTY_LIST;
218 List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
219 return ids == null ? Collections.EMPTY_LIST : ids;
222 public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
223 if (!DeepShortcutManager.supportsShortcuts(info)) {
227 return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
230 public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
231 BadgeInfo badgeInfo = getBadgeInfoForItem(info);
232 return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.getNotificationKeys();
235 /** This makes a potentially expensive binder call and should be run on a background thread. */
236 public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys(
237 List<NotificationKeyData> notificationKeys) {
238 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
239 return notificationListener == null ? Collections.EMPTY_LIST
240 : notificationListener.getNotificationsForKeys(notificationKeys);
243 public void cancelNotification(String notificationKey) {
244 NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
245 if (notificationListener == null) {
248 notificationListener.cancelNotification(notificationKey);