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.systemui.statusbar; 18 19 import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput; 20 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; 21 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; 22 23 import android.annotation.NonNull; 24 import android.annotation.SuppressLint; 25 import android.app.NotificationManager; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.os.Handler; 29 import android.os.RemoteException; 30 import android.os.UserHandle; 31 import android.service.notification.StatusBarNotification; 32 import android.util.Log; 33 34 import com.android.systemui.dagger.qualifiers.Main; 35 import com.android.systemui.statusbar.dagger.StatusBarModule; 36 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * This class handles listening to notification updates and passing them along to 43 * NotificationPresenter to be displayed to the user. 44 */ 45 @SuppressLint("OverrideAbstract") 46 public class NotificationListener extends NotificationListenerWithPlugins { 47 private static final String TAG = "NotificationListener"; 48 49 private final Context mContext; 50 private final NotificationManager mNotificationManager; 51 private final Handler mMainHandler; 52 private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>(); 53 private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>(); 54 55 /** 56 * Injected constructor. See {@link StatusBarModule}. 57 */ NotificationListener( Context context, NotificationManager notificationManager, @Main Handler mainHandler)58 public NotificationListener( 59 Context context, 60 NotificationManager notificationManager, 61 @Main Handler mainHandler) { 62 mContext = context; 63 mNotificationManager = notificationManager; 64 mMainHandler = mainHandler; 65 } 66 67 /** Registers a listener that's notified when notifications are added/removed/etc. */ addNotificationHandler(NotificationHandler handler)68 public void addNotificationHandler(NotificationHandler handler) { 69 if (mNotificationHandlers.contains(handler)) { 70 throw new IllegalArgumentException("Listener is already added"); 71 } 72 mNotificationHandlers.add(handler); 73 } 74 75 /** Registers a listener that's notified when any notification-related settings change. */ addNotificationSettingsListener(NotificationSettingsListener listener)76 public void addNotificationSettingsListener(NotificationSettingsListener listener) { 77 mSettingsListeners.add(listener); 78 } 79 80 @Override onListenerConnected()81 public void onListenerConnected() { 82 if (DEBUG) Log.d(TAG, "onListenerConnected"); 83 onPluginConnected(); 84 final StatusBarNotification[] notifications = getActiveNotifications(); 85 if (notifications == null) { 86 Log.w(TAG, "onListenerConnected unable to get active notifications."); 87 return; 88 } 89 final RankingMap currentRanking = getCurrentRanking(); 90 mMainHandler.post(() -> { 91 // There's currently a race condition between the calls to getActiveNotifications() and 92 // getCurrentRanking(). It's possible for the ranking that we store here to not contain 93 // entries for every notification in getActiveNotifications(). To prevent downstream 94 // crashes, we temporarily fill in these missing rankings with stubs. 95 // See b/146011844 for long-term fix 96 final List<Ranking> newRankings = new ArrayList<>(); 97 for (StatusBarNotification sbn : notifications) { 98 newRankings.add(getRankingOrTemporaryStandIn(currentRanking, sbn.getKey())); 99 } 100 final RankingMap completeMap = new RankingMap(newRankings.toArray(new Ranking[0])); 101 102 for (StatusBarNotification sbn : notifications) { 103 for (NotificationHandler listener : mNotificationHandlers) { 104 listener.onNotificationPosted(sbn, completeMap); 105 } 106 } 107 for (NotificationHandler listener : mNotificationHandlers) { 108 listener.onNotificationsInitialized(); 109 } 110 }); 111 onSilentStatusBarIconsVisibilityChanged( 112 mNotificationManager.shouldHideSilentStatusBarIcons()); 113 } 114 115 @Override onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap)116 public void onNotificationPosted(final StatusBarNotification sbn, 117 final RankingMap rankingMap) { 118 if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); 119 if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) { 120 mMainHandler.post(() -> { 121 processForRemoteInput(sbn.getNotification(), mContext); 122 123 for (NotificationHandler handler : mNotificationHandlers) { 124 handler.onNotificationPosted(sbn, rankingMap); 125 } 126 }); 127 } 128 } 129 130 @Override onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)131 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, 132 int reason) { 133 if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason); 134 if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) { 135 mMainHandler.post(() -> { 136 for (NotificationHandler handler : mNotificationHandlers) { 137 handler.onNotificationRemoved(sbn, rankingMap, reason); 138 } 139 }); 140 } 141 } 142 143 @Override onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap)144 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { 145 onNotificationRemoved(sbn, rankingMap, UNDEFINED_DISMISS_REASON); 146 } 147 148 @Override onNotificationRankingUpdate(final RankingMap rankingMap)149 public void onNotificationRankingUpdate(final RankingMap rankingMap) { 150 if (DEBUG) Log.d(TAG, "onRankingUpdate"); 151 if (rankingMap != null) { 152 RankingMap r = onPluginRankingUpdate(rankingMap); 153 mMainHandler.post(() -> { 154 for (NotificationHandler handler : mNotificationHandlers) { 155 handler.onNotificationRankingUpdate(r); 156 } 157 }); 158 } 159 } 160 161 @Override onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons)162 public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) { 163 for (NotificationSettingsListener listener : mSettingsListeners) { 164 listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons); 165 } 166 } 167 unsnoozeNotification(@onNull String key)168 public final void unsnoozeNotification(@NonNull String key) { 169 if (!isBound()) return; 170 try { 171 getNotificationInterface().unsnoozeNotificationFromSystemListener(mWrapper, key); 172 } catch (android.os.RemoteException ex) { 173 Log.v(TAG, "Unable to contact notification manager", ex); 174 } 175 } 176 registerAsSystemService()177 public void registerAsSystemService() { 178 try { 179 registerAsSystemService(mContext, 180 new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), 181 UserHandle.USER_ALL); 182 } catch (RemoteException e) { 183 Log.e(TAG, "Unable to register notification listener", e); 184 } 185 } 186 getRankingOrTemporaryStandIn(RankingMap rankingMap, String key)187 private static Ranking getRankingOrTemporaryStandIn(RankingMap rankingMap, String key) { 188 Ranking ranking = new Ranking(); 189 if (!rankingMap.getRanking(key, ranking)) { 190 ranking.populate( 191 key, 192 0, 193 false, 194 0, 195 0, 196 0, 197 null, 198 null, 199 null, 200 new ArrayList<>(), 201 new ArrayList<>(), 202 false, 203 0, 204 false, 205 0, 206 false, 207 new ArrayList<>(), 208 new ArrayList<>(), 209 false, 210 false, 211 false, 212 null, 213 false 214 ); 215 } 216 return ranking; 217 } 218 219 public interface NotificationSettingsListener { 220 onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons)221 default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { } 222 } 223 224 /** Interface for listening to add/remove events that we receive from NotificationManager. */ 225 public interface NotificationHandler { onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)226 void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap); onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap)227 void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap); onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)228 void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason); onNotificationRankingUpdate(RankingMap rankingMap)229 void onNotificationRankingUpdate(RankingMap rankingMap); 230 231 /** 232 * Called after the listener has connected to NoMan and posted any current notifications. 233 */ onNotificationsInitialized()234 void onNotificationsInitialized(); 235 } 236 } 237