Listener.java revision 7d59ec824cde9611834aa1383d2cdc8341809e3a
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 */
16package com.android.example.notificationlistener;
17
18
19import android.app.Notification;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.os.Handler;
27import android.os.Message;
28import android.os.RemoteException;
29import android.service.notification.NotificationListenerService;
30import android.service.notification.NotificationListenerService.Ranking;
31import android.service.notification.NotificationListenerService.RankingMap;
32import android.service.notification.StatusBarNotification;
33import android.support.v4.content.LocalBroadcastManager;
34import android.text.TextUtils;
35import android.util.Log;
36
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.Comparator;
40import java.util.List;
41
42public 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
63    public static List<StatusBarNotification> getNotifications() {
64        return sNotifications;
65    }
66
67    public static boolean isConnected() {
68        return sConnected;
69    }
70
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
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
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
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
266    public void onListenerConnected() {
267        Log.w(TAG, "onListenerConnected: ");
268        Message.obtain(sHandler, MSG_STARTUP).sendToTarget();
269    }
270
271    @Override
272    public void onListenerDisconnected() {
273        Log.w(TAG, "onListenerDisconnected: ");
274    }
275
276    @Override
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
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
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
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