• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.launcher3.ItemInfo;
24 import com.android.launcher3.Launcher;
25 import com.android.launcher3.dot.DotInfo;
26 import com.android.launcher3.model.WidgetItem;
27 import com.android.launcher3.notification.NotificationKeyData;
28 import com.android.launcher3.notification.NotificationListener;
29 import com.android.launcher3.shortcuts.DeepShortcutManager;
30 import com.android.launcher3.util.ComponentKey;
31 import com.android.launcher3.util.PackageUserKey;
32 import com.android.launcher3.widget.WidgetListRowEntry;
33 
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.function.Predicate;
41 
42 import androidx.annotation.NonNull;
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     private final Launcher mLauncher;
53 
54     /** Maps launcher activity components to a count of how many shortcuts they have. */
55     private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
56     /** Maps packages to their DotInfo's . */
57     private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
58     /** Maps packages to their Widgets */
59     private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>();
60 
61     private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
62 
PopupDataProvider(Launcher launcher)63     public PopupDataProvider(Launcher launcher) {
64         mLauncher = launcher;
65     }
66 
updateNotificationDots(Predicate<PackageUserKey> updatedDots)67     private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
68         mLauncher.updateNotificationDots(updatedDots);
69         mChangeListener.onNotificationDotsUpdated(updatedDots);
70     }
71 
72     @Override
onNotificationPosted(PackageUserKey postedPackageUserKey, NotificationKeyData notificationKey, boolean shouldBeFilteredOut)73     public void onNotificationPosted(PackageUserKey postedPackageUserKey,
74             NotificationKeyData notificationKey, boolean shouldBeFilteredOut) {
75         DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey);
76         boolean dotShouldBeRefreshed;
77         if (dotInfo == null) {
78             if (!shouldBeFilteredOut) {
79                 DotInfo newDotInfo = new DotInfo();
80                 newDotInfo.addOrUpdateNotificationKey(notificationKey);
81                 mPackageUserToDotInfos.put(postedPackageUserKey, newDotInfo);
82                 dotShouldBeRefreshed = true;
83             } else {
84                 dotShouldBeRefreshed = false;
85             }
86         } else {
87             dotShouldBeRefreshed = shouldBeFilteredOut
88                     ? dotInfo.removeNotificationKey(notificationKey)
89                     : dotInfo.addOrUpdateNotificationKey(notificationKey);
90             if (dotInfo.getNotificationKeys().size() == 0) {
91                 mPackageUserToDotInfos.remove(postedPackageUserKey);
92             }
93         }
94         if (dotShouldBeRefreshed) {
95             updateNotificationDots(t -> postedPackageUserKey.equals(t));
96         }
97     }
98 
99     @Override
onNotificationRemoved(PackageUserKey removedPackageUserKey, NotificationKeyData notificationKey)100     public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
101             NotificationKeyData notificationKey) {
102         DotInfo oldDotInfo = mPackageUserToDotInfos.get(removedPackageUserKey);
103         if (oldDotInfo != null && oldDotInfo.removeNotificationKey(notificationKey)) {
104             if (oldDotInfo.getNotificationKeys().size() == 0) {
105                 mPackageUserToDotInfos.remove(removedPackageUserKey);
106             }
107             updateNotificationDots(t -> removedPackageUserKey.equals(t));
108             trimNotifications(mPackageUserToDotInfos);
109         }
110     }
111 
112     @Override
onNotificationFullRefresh(List<StatusBarNotification> activeNotifications)113     public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
114         if (activeNotifications == null) return;
115         // This will contain the PackageUserKeys which have updated dots.
116         HashMap<PackageUserKey, DotInfo> updatedDots = new HashMap<>(mPackageUserToDotInfos);
117         mPackageUserToDotInfos.clear();
118         for (StatusBarNotification notification : activeNotifications) {
119             PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
120             DotInfo dotInfo = mPackageUserToDotInfos.get(packageUserKey);
121             if (dotInfo == null) {
122                 dotInfo = new DotInfo();
123                 mPackageUserToDotInfos.put(packageUserKey, dotInfo);
124             }
125             dotInfo.addOrUpdateNotificationKey(NotificationKeyData.fromNotification(notification));
126         }
127 
128         // Add and remove from updatedDots so it contains the PackageUserKeys of updated dots.
129         for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) {
130             DotInfo prevDot = updatedDots.get(packageUserKey);
131             DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey);
132             if (prevDot == null) {
133                 updatedDots.put(packageUserKey, newDot);
134             } else {
135                 // No need to update the dot if it already existed (no visual change).
136                 // Note that if the dot was removed entirely, we wouldn't reach this point because
137                 // this loop only includes active notifications added above.
138                 updatedDots.remove(packageUserKey);
139             }
140         }
141 
142         if (!updatedDots.isEmpty()) {
143             updateNotificationDots(updatedDots::containsKey);
144         }
145         trimNotifications(updatedDots);
146     }
147 
trimNotifications(Map<PackageUserKey, DotInfo> updatedDots)148     private void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
149         mChangeListener.trimNotifications(updatedDots);
150     }
151 
setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy)152     public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
153         mDeepShortcutMap = deepShortcutMapCopy;
154         if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
155     }
156 
getShortcutCountForItem(ItemInfo info)157     public int getShortcutCountForItem(ItemInfo info) {
158         if (!DeepShortcutManager.supportsShortcuts(info)) {
159             return 0;
160         }
161         ComponentName component = info.getTargetComponent();
162         if (component == null) {
163             return 0;
164         }
165 
166         Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user));
167         return count == null ? 0 : count;
168     }
169 
getDotInfoForItem(ItemInfo info)170     public DotInfo getDotInfoForItem(ItemInfo info) {
171         if (!DeepShortcutManager.supportsShortcuts(info)) {
172             return null;
173         }
174 
175         return mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info));
176     }
177 
getNotificationKeysForItem(ItemInfo info)178     public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
179         DotInfo dotInfo = getDotInfoForItem(info);
180         return dotInfo == null ? Collections.EMPTY_LIST : dotInfo.getNotificationKeys();
181     }
182 
183     /** This makes a potentially expensive binder call and should be run on a background thread. */
getStatusBarNotificationsForKeys( List<NotificationKeyData> notificationKeys)184     public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys(
185             List<NotificationKeyData> notificationKeys) {
186         NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
187         return notificationListener == null ? Collections.EMPTY_LIST
188                 : notificationListener.getNotificationsForKeys(notificationKeys);
189     }
190 
cancelNotification(String notificationKey)191     public void cancelNotification(String notificationKey) {
192         NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
193         if (notificationListener == null) {
194             return;
195         }
196         notificationListener.cancelNotificationFromLauncher(notificationKey);
197     }
198 
setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets)199     public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
200         mAllWidgets = allWidgets;
201         mChangeListener.onWidgetsBound();
202     }
203 
setChangeListener(PopupDataChangeListener listener)204     public void setChangeListener(PopupDataChangeListener listener) {
205         mChangeListener = listener == null ? PopupDataChangeListener.INSTANCE : listener;
206     }
207 
getAllWidgets()208     public ArrayList<WidgetListRowEntry> getAllWidgets() {
209         return mAllWidgets;
210     }
211 
getWidgetsForPackageUser(PackageUserKey packageUserKey)212     public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
213         for (WidgetListRowEntry entry : mAllWidgets) {
214             if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
215                 ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
216                 // Remove widgets not associated with the correct user.
217                 Iterator<WidgetItem> iterator = widgets.iterator();
218                 while (iterator.hasNext()) {
219                     if (!iterator.next().user.equals(packageUserKey.mUser)) {
220                         iterator.remove();
221                     }
222                 }
223                 return widgets.isEmpty() ? null : widgets;
224             }
225         }
226         return null;
227     }
228 
229     public interface PopupDataChangeListener {
230 
231         PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { };
232 
onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots)233         default void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) { }
234 
trimNotifications(Map<PackageUserKey, DotInfo> updatedDots)235         default void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) { }
236 
onWidgetsBound()237         default void onWidgetsBound() { }
238     }
239 }
240