OSDN Git Service

Update icon badges to match spec
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / popup / PopupDataProvider.java
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.Collections;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42
43 /**
44  * Provides data for the popup menu that appears after long-clicking on apps.
45  */
46 public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {
47
48     private static final boolean LOGD = false;
49     private static final String TAG = "PopupDataProvider";
50
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(),
55     };
56
57     private final Launcher mLauncher;
58
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<>();
63
64     public PopupDataProvider(Launcher launcher) {
65         mLauncher = launcher;
66     }
67
68     @Override
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;
79             } else {
80                 badgeShouldBeRefreshed = false;
81             }
82         } else {
83             badgeShouldBeRefreshed = shouldBeFilteredOut
84                     ? badgeInfo.removeNotificationKey(notificationKey)
85                     : badgeInfo.addOrUpdateNotificationKey(notificationKey);
86             if (badgeInfo.getNotificationCount() == 0) {
87                 mPackageUserToBadgeInfos.remove(postedPackageUserKey);
88             }
89         }
90         updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey),
91                 badgeShouldBeRefreshed);
92     }
93
94     @Override
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);
101             }
102             updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey));
103
104             PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
105             if (openContainer != null) {
106                 openContainer.trimNotifications(mPackageUserToBadgeInfos);
107             }
108         }
109     }
110
111     @Override
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);
123             }
124             badgeInfo.addOrUpdateNotificationKey(NotificationKeyData
125                     .fromNotification(notification));
126         }
127
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);
134             } else {
135                 if (!prevBadge.shouldBeInvalidated(newBadge)) {
136                     updatedBadges.remove(packageUserKey);
137                 }
138             }
139         }
140
141         if (!updatedBadges.isEmpty()) {
142             updateLauncherIconBadges(updatedBadges.keySet());
143         }
144
145         PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
146         if (openContainer != null) {
147             openContainer.trimNotifications(updatedBadges);
148         }
149     }
150
151     private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges) {
152         updateLauncherIconBadges(updatedBadges, true);
153     }
154
155     /**
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.
162      */
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.
171                 iterator.remove();
172             }
173         }
174         if (!updatedBadges.isEmpty()) {
175             mLauncher.updateIconBadges(updatedBadges);
176         }
177     }
178
179     /**
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).
184      */
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;
197                 }
198             }
199         }
200         badgeInfo.setNotificationToShow(notificationInfo);
201         return hadNotificationToShow || badgeInfo.hasNotificationToShow();
202     }
203
204     public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
205         mDeepShortcutMap = deepShortcutMapCopy;
206         if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
207     }
208
209     public List<String> getShortcutIdsForItem(ItemInfo info) {
210         if (!DeepShortcutManager.supportsShortcuts(info)) {
211             return Collections.EMPTY_LIST;
212         }
213         ComponentName component = info.getTargetComponent();
214         if (component == null) {
215             return Collections.EMPTY_LIST;
216         }
217
218         List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
219         return ids == null ? Collections.EMPTY_LIST : ids;
220     }
221
222     public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
223         if (!DeepShortcutManager.supportsShortcuts(info)) {
224             return null;
225         }
226
227         return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
228     }
229
230     public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
231         BadgeInfo badgeInfo = getBadgeInfoForItem(info);
232         return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.getNotificationKeys();
233     }
234
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);
241     }
242
243     public void cancelNotification(String notificationKey) {
244         NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
245         if (notificationListener == null) {
246             return;
247         }
248         notificationListener.cancelNotification(notificationKey);
249     }
250 }