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