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