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