1 /*
2  * Copyright (C) 2014 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.example.notificationlistener;
17 
18 
19 import android.app.Notification;
20 import android.app.PendingIntent;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.service.notification.NotificationListenerService;
30 import android.service.notification.NotificationListenerService.Ranking;
31 import android.service.notification.NotificationListenerService.RankingMap;
32 import android.service.notification.StatusBarNotification;
33 import android.support.v4.content.LocalBroadcastManager;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.Comparator;
40 import java.util.List;
41 
42 public class Listener extends NotificationListenerService {
43     private static final String TAG = "SampleListener";
44 
45     // Message tags
46     private static final int MSG_NOTIFY = 1;
47     private static final int MSG_CANCEL = 2;
48     private static final int MSG_STARTUP = 3;
49     private static final int MSG_ORDER = 4;
50     private static final int MSG_DISMISS = 5;
51     private static final int MSG_LAUNCH = 6;
52     private static final int MSG_SNOOZE = 7;
53 
54     static final String ACTION_DISMISS = "com.android.example.notificationlistener.DISMISS";
55     static final String ACTION_LAUNCH = "com.android.example.notificationlistener.LAUNCH";
56     static final String ACTION_REFRESH = "com.android.example.notificationlistener.REFRESH";
57     static final String ACTION_STATE_CHANGE = "com.android.example.notificationlistener.STATE";
58     static final String EXTRA_KEY = "key";
59 
60     private static ArrayList<StatusBarNotification> sNotifications;
61     private static boolean sConnected;
62 
getNotifications()63     public static List<StatusBarNotification> getNotifications() {
64         return sNotifications;
65     }
66 
isConnected()67     public static boolean isConnected() {
68         return sConnected;
69     }
70 
toggleSnooze(Context context)71     public static void toggleSnooze(Context context) {
72         if (sConnected) {
73             Log.d(TAG, "scheduling snooze");
74             if (sHandler != null) {
75                 sHandler.sendEmptyMessage(MSG_SNOOZE);
76             }
77         } else {
78             Log.d(TAG, "trying to unsnooze");
79             try {
80                 NotificationListenerService.requestRebind(
81                         ComponentName.createRelative(context.getPackageName(),
82                                 Listener.class.getCanonicalName()));
83             } catch (RemoteException e) {
84                 Log.e(TAG, "failed to rebind service", e);
85             }
86         }
87     }
88 
89     private final Ranking mTmpRanking = new Ranking();
90 
91     private static Handler sHandler;
92 
93     private RankingMap mRankingMap;
94 
95     private class Delta {
96         final StatusBarNotification mSbn;
97         final RankingMap mRankingMap;
98 
Delta(StatusBarNotification sbn, RankingMap rankingMap)99         public Delta(StatusBarNotification sbn, RankingMap rankingMap) {
100             mSbn = sbn;
101             mRankingMap = rankingMap;
102         }
103     }
104 
105     private final Comparator<StatusBarNotification> mRankingComparator =
106             new Comparator<StatusBarNotification>() {
107 
108                 private final Ranking mLhsRanking = new Ranking();
109                 private final Ranking mRhsRanking = new Ranking();
110 
111                 @Override
112                 public int compare(StatusBarNotification lhs, StatusBarNotification rhs) {
113                     mRankingMap.getRanking(lhs.getKey(), mLhsRanking);
114                     mRankingMap.getRanking(rhs.getKey(), mRhsRanking);
115                     return Integer.compare(mLhsRanking.getRank(), mRhsRanking.getRank());
116                 }
117             };
118 
119     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
120         @Override
121         public void onReceive(Context context, Intent intent) {
122             String key = intent.getStringExtra(EXTRA_KEY);
123             int what = MSG_DISMISS;
124             if (ACTION_LAUNCH.equals(intent.getAction())) {
125                 what = MSG_LAUNCH;
126             }
127             Log.d(TAG, "received an action broadcast " + intent.getAction());
128             if (!TextUtils.isEmpty(key)) {
129                 Log.d(TAG, "  on " + key);
130                 Message.obtain(sHandler, what, key).sendToTarget();
131             }
132         }
133     };
134 
135     @Override
onCreate()136     public void onCreate() {
137         super.onCreate();
138         sHandler = new Handler() {
139             @Override
140             public void handleMessage(Message msg) {
141                 Delta delta = null;
142                 if (msg.obj instanceof Delta) {
143                     delta = (Delta) msg.obj;
144                 }
145 
146                 switch (msg.what) {
147                     case MSG_NOTIFY:
148                         Log.i(TAG, "notify: " + delta.mSbn.getKey());
149                         synchronized (sNotifications) {
150                             boolean exists = mRankingMap.getRanking(delta.mSbn.getKey(), mTmpRanking);
151                             if (!exists) {
152                                 sNotifications.add(delta.mSbn);
153                             } else {
154                                 int position = mTmpRanking.getRank();
155                                 sNotifications.set(position, delta.mSbn);
156                             }
157                             mRankingMap = delta.mRankingMap;
158                             Collections.sort(sNotifications, mRankingComparator);
159                             Log.i(TAG, "finish with: " + sNotifications.size());
160                         }
161                         LocalBroadcastManager.getInstance(Listener.this)
162                                 .sendBroadcast(new Intent(ACTION_REFRESH)
163                                         .putExtra(EXTRA_KEY, delta.mSbn.getKey()));
164                         break;
165 
166                     case MSG_CANCEL:
167                         final String cancelKey = delta.mSbn.getKey();
168                         Log.i(TAG, "remove: " + cancelKey);
169                         synchronized (sNotifications) {
170                             boolean exists = mRankingMap.getRanking(cancelKey, mTmpRanking);
171                             if (exists) {
172                                 sNotifications.remove(mTmpRanking.getRank());
173                             }
174                             mRankingMap = delta.mRankingMap;
175                             Collections.sort(sNotifications, mRankingComparator);
176                         }
177                         LocalBroadcastManager.getInstance(Listener.this)
178                                 .sendBroadcast(new Intent(ACTION_REFRESH)
179                                         .putExtra(EXTRA_KEY, cancelKey));
180                         break;
181 
182                     case MSG_ORDER:
183                         Log.i(TAG, "reorder");
184                         synchronized (sNotifications) {
185                             mRankingMap = delta.mRankingMap;
186                             Collections.sort(sNotifications, mRankingComparator);
187                         }
188                         LocalBroadcastManager.getInstance(Listener.this)
189                                 .sendBroadcast(new Intent(ACTION_REFRESH));
190                         break;
191 
192                     case MSG_STARTUP:
193                         sConnected = true;
194                         fetchActive();
195                         Log.i(TAG, "start with: " + sNotifications.size() + " notifications.");
196                         LocalBroadcastManager.getInstance(Listener.this)
197                                 .sendBroadcast(new Intent(ACTION_REFRESH));
198                         LocalBroadcastManager.getInstance(Listener.this)
199                                 .sendBroadcast(new Intent(ACTION_STATE_CHANGE));
200                         break;
201 
202                     case MSG_DISMISS:
203                         if (msg.obj instanceof String) {
204                             final String key = (String) msg.obj;
205                             mRankingMap.getRanking(key, mTmpRanking);
206                             StatusBarNotification sbn = sNotifications.get(mTmpRanking.getRank());
207                             if ((sbn.getNotification().flags & Notification.FLAG_AUTO_CANCEL) != 0 &&
208                                     sbn.getNotification().contentIntent != null) {
209                                 try {
210                                     sbn.getNotification().contentIntent.send();
211                                 } catch (PendingIntent.CanceledException e) {
212                                     Log.d(TAG, "failed to send intent for " + sbn.getKey(), e);
213                                 }
214                             }
215                             cancelNotification(key);
216                         }
217                         break;
218 
219                     case MSG_LAUNCH:
220                         if (msg.obj instanceof String) {
221                             final String key = (String) msg.obj;
222                             mRankingMap.getRanking(key, mTmpRanking);
223                             StatusBarNotification sbn = sNotifications.get(mTmpRanking.getRank());
224                             if (sbn.getNotification().contentIntent != null) {
225                                 try {
226                                     sbn.getNotification().contentIntent.send();
227                                 } catch (PendingIntent.CanceledException e) {
228                                     Log.d(TAG, "failed to send intent for " + sbn.getKey(), e);
229                                 }
230                             }
231                             if ((sbn.getNotification().flags & Notification.FLAG_AUTO_CANCEL) != 0) {
232                                 cancelNotification(key);
233                             }
234                         }
235                         break;
236 
237                     case MSG_SNOOZE:
238                         Log.d(TAG, "trying to snooze");
239                         try {
240                             requestUnbind();
241                         } catch (RemoteException e) {
242                             Log.e(TAG, "failed to unbind service", e);
243                         }
244                         break;
245                 }
246             }
247         };
248         Log.d(TAG, "registering broadcast listener");
249         final IntentFilter intentFilter = new IntentFilter();
250         intentFilter.addAction(ACTION_DISMISS);
251         intentFilter.addAction(ACTION_LAUNCH);
252         LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
253     }
254 
255     @Override
onDestroy()256     public void onDestroy() {
257         sConnected = false;
258         LocalBroadcastManager.getInstance(Listener.this)
259                 .sendBroadcast(new Intent(ACTION_STATE_CHANGE));
260         sHandler = null;
261         LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
262         super.onDestroy();
263     }
264 
265     @Override
onListenerConnected()266     public void onListenerConnected() {
267         Log.w(TAG, "onListenerConnected: ");
268         Message.obtain(sHandler, MSG_STARTUP).sendToTarget();
269     }
270 
271     @Override
onListenerDisconnected()272     public void onListenerDisconnected() {
273         Log.w(TAG, "onListenerDisconnected: ");
274     }
275 
276     @Override
onNotificationRankingUpdate(RankingMap rankingMap)277     public void onNotificationRankingUpdate(RankingMap rankingMap) {
278         Log.w(TAG, "onNotificationRankingUpdate");
279         Message.obtain(sHandler, MSG_ORDER,
280                 new Delta(null, rankingMap)).sendToTarget();
281     }
282 
283     @Override
onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)284     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
285         Log.w(TAG, "onNotificationPosted: " + sbn.getKey());
286         Message.obtain(sHandler, MSG_NOTIFY,
287                 new Delta(sbn, rankingMap)).sendToTarget();
288     }
289 
290     @Override
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap)291     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
292         Log.w(TAG, "onNotificationRemoved: " + sbn.getKey());
293         Message.obtain(sHandler, MSG_CANCEL,
294                 new Delta(sbn, rankingMap)).sendToTarget();
295     }
296 
fetchActive()297     private void fetchActive() {
298         mRankingMap = getCurrentRanking();
299         sNotifications = new ArrayList<StatusBarNotification>();
300         for (StatusBarNotification sbn : getActiveNotifications()) {
301             sNotifications.add(sbn);
302             Log.w(TAG, "startup poll: " + sbn.getKey());
303         }
304         Collections.sort(sNotifications, mRankingComparator);
305     }
306 }
307