1 /*
2  * Copyright (C) 2019 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 package com.android.car.notification;
17 
18 import android.util.Log;
19 
20 import com.android.car.assist.client.CarAssistUtils;
21 
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 
31 /**
32  * Keeps track of the additional state of notifications. This class is not thread safe and should
33  * only be called from the main thread.
34  */
35 public class NotificationDataManager {
36 
37     /**
38      * Interface for listeners that want to register for receiving updates to the notification
39      * unseen count.
40      */
41     public interface OnUnseenCountUpdateListener {
42         /**
43          * Called when unseen notification count is changed.
44          */
onUnseenCountUpdate()45         void onUnseenCountUpdate();
46     }
47 
48     private static String TAG = "NotificationDataManager";
49 
50     /**
51      * Map that contains the key of all message notifications, mapped to whether or not the key's
52      * notification should be muted.
53      *
54      * Muted notifications should show an "Unmute" button on their notification and should not
55      * trigger the HUN when new notifications arrive with the same key. Unmuted should show a "Mute"
56      * button on their notification and should trigger the HUN. Both should update the notification
57      * in the Notification Center.
58      */
59     private final Map<String, Boolean> mMessageNotificationToMuteStateMap = new HashMap<>();
60 
61     /**
62      * Map that contains the key of all unseen notifications.
63      */
64     private final Map<String, Boolean> mUnseenNotificationMap = new HashMap<>();
65 
66     /**
67      * List of notifications that are visible to the user.
68      */
69     private final List<AlertEntry> mVisibleNotifications = new ArrayList<>();
70 
71     private OnUnseenCountUpdateListener mOnUnseenCountUpdateListener;
72 
NotificationDataManager()73     public NotificationDataManager() {
74         clearAll();
75     }
76 
77     /**
78      * Sets listener for unseen notification count change event.
79      *
80      * @param listener UnseenCountUpdateListener
81      */
setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener)82     public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) {
83         mOnUnseenCountUpdateListener = listener;
84     }
85 
addNewMessageNotification(AlertEntry alertEntry)86     void addNewMessageNotification(AlertEntry alertEntry) {
87         if (CarAssistUtils.isCarCompatibleMessagingNotification(
88                 alertEntry.getStatusBarNotification())) {
89             mMessageNotificationToMuteStateMap
90                     .putIfAbsent(alertEntry.getKey(), /* muteState= */ false);
91 
92             if (mUnseenNotificationMap.containsKey(alertEntry.getKey())) {
93                 mUnseenNotificationMap.put(alertEntry.getKey(), true);
94 
95                 if (mOnUnseenCountUpdateListener != null) {
96                     mOnUnseenCountUpdateListener.onUnseenCountUpdate();
97                 }
98             }
99         }
100     }
101 
untrackUnseenNotification(AlertEntry alertEntry)102     void untrackUnseenNotification(AlertEntry alertEntry) {
103         if (mUnseenNotificationMap.containsKey(alertEntry.getKey())) {
104             mUnseenNotificationMap.remove(alertEntry.getKey());
105             if (mOnUnseenCountUpdateListener != null) {
106                 mOnUnseenCountUpdateListener.onUnseenCountUpdate();
107             }
108         }
109     }
110 
updateUnseenNotification(List<NotificationGroup> notificationGroups)111     void updateUnseenNotification(List<NotificationGroup> notificationGroups) {
112         Set<String> currentNotificationKeys = new HashSet<>();
113 
114         Collections.addAll(currentNotificationKeys,
115                 mUnseenNotificationMap.keySet().toArray(new String[0]));
116 
117         for (NotificationGroup group : notificationGroups) {
118             for (AlertEntry alertEntry : group.getChildNotifications()) {
119                 // add new notifications
120                 mUnseenNotificationMap.putIfAbsent(alertEntry.getKey(), true);
121 
122                 // sbn exists in both sets.
123                 currentNotificationKeys.remove(alertEntry.getKey());
124             }
125         }
126 
127         // These keys were removed from notificationGroups. Remove from mUnseenNotificationMap.
128         for (String notificationKey : currentNotificationKeys) {
129             mUnseenNotificationMap.remove(notificationKey);
130         }
131 
132         if (mOnUnseenCountUpdateListener != null) {
133             mOnUnseenCountUpdateListener.onUnseenCountUpdate();
134         }
135     }
136 
137     /**
138      * Returns the mute state of the notification, or false if notification does not have a mute
139      * state. Only message notifications can be muted.
140      **/
isMessageNotificationMuted(AlertEntry alertEntry)141     public boolean isMessageNotificationMuted(AlertEntry alertEntry) {
142         if (!mMessageNotificationToMuteStateMap.containsKey(alertEntry.getKey())) {
143             addNewMessageNotification(alertEntry);
144         }
145         return mMessageNotificationToMuteStateMap.getOrDefault(alertEntry.getKey(), false);
146     }
147 
148     /**
149      * If {@param sbn} is a messaging notification, this function will toggle its mute state. This
150      * state determines whether or not a HUN will be shown on future updates to the notification.
151      * It also determines the title of the notification's "Mute" button.
152      **/
toggleMute(AlertEntry alertEntry)153     public void toggleMute(AlertEntry alertEntry) {
154         if (CarAssistUtils.isCarCompatibleMessagingNotification(
155                 alertEntry.getStatusBarNotification())) {
156             String sbnKey = alertEntry.getKey();
157             Boolean currentMute = mMessageNotificationToMuteStateMap.get(sbnKey);
158             if (currentMute != null) {
159                 mMessageNotificationToMuteStateMap.put(sbnKey, !currentMute);
160             } else {
161                 Log.e(TAG, "Msg notification was not initially added to the mute state map: "
162                         + alertEntry.getKey());
163             }
164         }
165     }
166 
167     /**
168      * Clear unseen and mute notification state information.
169      */
clearAll()170     public void clearAll() {
171         mMessageNotificationToMuteStateMap.clear();
172         mUnseenNotificationMap.clear();
173         mVisibleNotifications.clear();
174 
175         if (mOnUnseenCountUpdateListener != null) {
176             mOnUnseenCountUpdateListener.onUnseenCountUpdate();
177         }
178     }
179 
setNotificationsAsSeen(List<AlertEntry> alertEntries)180     void setNotificationsAsSeen(List<AlertEntry> alertEntries) {
181         mVisibleNotifications.clear();
182         for (AlertEntry alertEntry : alertEntries) {
183             if (mUnseenNotificationMap.containsKey(alertEntry.getKey())) {
184                 mUnseenNotificationMap.put(alertEntry.getKey(), false);
185                 mVisibleNotifications.add(alertEntry);
186             }
187         }
188         if (mOnUnseenCountUpdateListener != null) {
189             mOnUnseenCountUpdateListener.onUnseenCountUpdate();
190         }
191     }
192 
193     /**
194      * Returns unseen notification count.
195      */
getUnseenNotificationCount()196     public int getUnseenNotificationCount() {
197         int unseenCount = 0;
198         for (Boolean value : mUnseenNotificationMap.values()) {
199             if (value) {
200                 unseenCount++;
201             }
202         }
203         return unseenCount;
204     }
205 
206     /**
207      * Returns a collection containing all notifications the user should be seeing right now.
208      */
getVisibleNotifications()209     public List<AlertEntry> getVisibleNotifications() {
210         return mVisibleNotifications;
211     }
212 
213     /**
214      * Returns seen notifications.
215      */
getSeenNotifications()216     public String[] getSeenNotifications() {
217         return mUnseenNotificationMap.entrySet()
218                 .stream()
219                 // Seen notifications have value set to false
220                 .filter(map -> !map.getValue())
221                 .map(map -> map.getKey())
222                 .toArray(String[]::new);
223     }
224 }
225