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