blob: bc4b169ce9d5a46a4c9ee5f04baaf257b3b4f902 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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
17package com.android.server;
18
19import android.app.ActivityManagerNative;
20import android.app.IActivityManager;
21import android.app.INotificationManager;
22import android.app.ITransientNotification;
23import android.app.Notification;
24import android.app.PendingIntent;
25import android.app.StatusBarManager;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.content.res.Resources;
33import android.media.AudioManager;
34import android.media.AsyncPlayer;
35import android.media.RingtoneManager;
36import android.net.Uri;
37import android.os.BatteryManager;
38import android.os.Binder;
39import android.os.RemoteException;
40import android.os.Handler;
41import android.os.Hardware;
42import android.os.IBinder;
43import android.os.Message;
44import android.os.Power;
45import android.os.Vibrator;
46import android.provider.Settings;
47import android.util.Config;
48import android.util.EventLog;
49import android.util.Log;
50import android.widget.Toast;
51
52import com.android.server.status.IconData;
53import com.android.server.status.NotificationData;
54import com.android.server.status.StatusBarService;
55
56import java.io.FileDescriptor;
57import java.io.PrintWriter;
58import java.util.ArrayList;
59import java.util.Arrays;
60import java.io.IOException;
61
62class NotificationManagerService extends INotificationManager.Stub
63{
64 private static final String TAG = "NotificationService";
65 private static final boolean DBG = false;
66
67 // message codes
68 private static final int MESSAGE_TIMEOUT = 2;
69
70 private static final int LONG_DELAY = 3500; // 3.5 seconds
71 private static final int SHORT_DELAY = 2000; // 2 seconds
72
73 private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
74
75 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
76
77 final Context mContext;
78 final IActivityManager mAm;
79 final IBinder mForegroundToken = new Binder();
80
81 private WorkerHandler mHandler;
82 private StatusBarService mStatusBarService;
83
84 private NotificationRecord mSoundNotification;
85 private AsyncPlayer mSound;
86 private int mDisabledNotifications;
87
88 private NotificationRecord mVibrateNotification;
89 private Vibrator mVibrator = new Vibrator();
90
91 private ArrayList<NotificationRecord> mNotificationList;
92
93 private ArrayList<ToastRecord> mToastQueue;
94
95 private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
96
97 private boolean mBatteryCharging;
98 private boolean mBatteryLow;
99 private boolean mBatteryFull;
100 private NotificationRecord mLedNotification;
101
102 // Low battery - red, blinking on 0.125s every 3 seconds
103 private static final int BATTERY_LOW_ARGB = 0xFFFF0000;
104 private static final int BATTERY_LOW_ON = 125;
105 private static final int BATTERY_LOW_OFF = 2875;
106
107 // Charging Low - red solid on
108 private static final int CHARGING_LOW_ARGB = 0xFFFF0000;
109 private static final int CHARGING_LOW_ON = 0;
110 private static final int CHARGING_LOW_OFF = 0;
111
112 // Charging - orange solid on
113 private static final int CHARGING_ARGB = 0xFFFFFF00;
114 private static final int CHARGING_ON = 0;
115 private static final int CHARGING_OFF = 0;
116
117 // Charging Full - green solid on
118 private static final int CHARGING_FULL_ARGB = 0xFF00FF00;
119 private static final int CHARGING_FULL_ON = 0;
120 private static final int CHARGING_FULL_OFF = 0;
121
122 // Tag IDs for EventLog.
123 private static final int EVENT_LOG_ENQUEUE = 2750;
124 private static final int EVENT_LOG_CANCEL = 2751;
125 private static final int EVENT_LOG_CANCEL_ALL = 2752;
126
127 private static String idDebugString(Context baseContext, String packageName, int id) {
128 Context c = null;
129
130 if (packageName != null) {
131 try {
132 c = baseContext.createPackageContext(packageName, 0);
133 } catch (NameNotFoundException e) {
134 c = baseContext;
135 }
136 } else {
137 c = baseContext;
138 }
139
140 String pkg;
141 String type;
142 String name;
143
144 Resources r = c.getResources();
145 try {
146 return r.getResourceName(id);
147 } catch (Resources.NotFoundException e) {
148 return "<name unknown>";
149 }
150 }
151
152 private static final class NotificationRecord
153 {
154 String pkg;
155 int id;
156 ITransientNotification callback;
157 int duration;
158 Notification notification;
159 IBinder statusBarKey;
160
161 NotificationRecord(String pkg, int id, Notification notification)
162 {
163 this.pkg = pkg;
164 this.id = id;
165 this.notification = notification;
166 }
167
168 void dump(PrintWriter pw, String prefix, Context baseContext) {
169 pw.println(prefix + this);
170 pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon)
171 + " / " + idDebugString(baseContext, this.pkg, notification.icon));
172 pw.println(prefix + " contentIntent=" + notification.contentIntent);
173 pw.println(prefix + " deleteIntent=" + notification.deleteIntent);
174 pw.println(prefix + " tickerText=" + notification.tickerText);
175 pw.println(prefix + " contentView=" + notification.contentView);
176 pw.println(prefix + " defaults=0x" + Integer.toHexString(notification.defaults));
177 pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags));
178 pw.println(prefix + " sound=" + notification.sound);
179 pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
180 pw.println(prefix + " ledARGB=0x" + Integer.toHexString(notification.ledARGB)
181 + " ledOnMS=" + notification.ledOnMS
182 + " ledOffMS=" + notification.ledOffMS);
183 }
184
185 @Override
186 public final String toString()
187 {
188 return "NotificationRecord{"
189 + Integer.toHexString(System.identityHashCode(this))
190 + " pkg=" + pkg
191 + " id=" + Integer.toHexString(id) + "}";
192 }
193 }
194
195 private static final class ToastRecord
196 {
197 final int pid;
198 final String pkg;
199 final ITransientNotification callback;
200 int duration;
201
202 ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
203 {
204 this.pid = pid;
205 this.pkg = pkg;
206 this.callback = callback;
207 this.duration = duration;
208 }
209
210 void update(int duration) {
211 this.duration = duration;
212 }
213
214 void dump(PrintWriter pw, String prefix) {
215 pw.println(prefix + this);
216 }
217
218 @Override
219 public final String toString()
220 {
221 return "ToastRecord{"
222 + Integer.toHexString(System.identityHashCode(this))
223 + " pkg=" + pkg
224 + " callback=" + callback
225 + " duration=" + duration;
226 }
227 }
228
229 private StatusBarService.NotificationCallbacks mNotificationCallbacks
230 = new StatusBarService.NotificationCallbacks() {
231
232 public void onSetDisabled(int status) {
233 synchronized (mNotificationList) {
234 mDisabledNotifications = status;
235 if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
236 // cancel whatever's going on
237 long identity = Binder.clearCallingIdentity();
238 try {
239 mSound.stop();
240 }
241 finally {
242 Binder.restoreCallingIdentity(identity);
243 }
244
245 identity = Binder.clearCallingIdentity();
246 try {
247 mVibrator.cancel();
248 }
249 finally {
250 Binder.restoreCallingIdentity(identity);
251 }
252 }
253 }
254 }
255
256 public void onClearAll() {
257 cancelAll();
258 }
259
260 public void onNotificationClick(String pkg, int id) {
261 cancelNotification(pkg, id, Notification.FLAG_AUTO_CANCEL);
262 }
263
264 public void onPanelRevealed() {
265 synchronized (mNotificationList) {
266 // sound
267 mSoundNotification = null;
268 long identity = Binder.clearCallingIdentity();
269 try {
270 mSound.stop();
271 }
272 finally {
273 Binder.restoreCallingIdentity(identity);
274 }
275
276 // vibrate
277 mVibrateNotification = null;
278 identity = Binder.clearCallingIdentity();
279 try {
280 mVibrator.cancel();
281 }
282 finally {
283 Binder.restoreCallingIdentity(identity);
284 }
285
286 // light
287 mLights.clear();
288 mLedNotification = null;
289 updateLightsLocked();
290 }
291 }
292 };
293
294 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
295 @Override
296 public void onReceive(Context context, Intent intent) {
297 String action = intent.getAction();
298
299 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
300 boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0);
301 int level = intent.getIntExtra("level", -1);
302 boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD);
303 int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
304 boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90);
305
306 if (batteryCharging != mBatteryCharging ||
307 batteryLow != mBatteryLow ||
308 batteryFull != mBatteryFull) {
309 mBatteryCharging = batteryCharging;
310 mBatteryLow = batteryLow;
311 mBatteryFull = batteryFull;
312 updateLights();
313 }
314 } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
315 || action.equals(Intent.ACTION_PACKAGE_RESTARTED)) {
316 Uri uri = intent.getData();
317 if (uri == null) {
318 return;
319 }
320 String pkgName = uri.getSchemeSpecificPart();
321 if (pkgName == null) {
322 return;
323 }
324 cancelAllNotifications(pkgName);
325 }
326 }
327 };
328
329 NotificationManagerService(Context context, StatusBarService statusBar)
330 {
331 super();
332 mContext = context;
333 mAm = ActivityManagerNative.getDefault();
334 mSound = new AsyncPlayer(TAG);
335 mSound.setUsesWakeLock(context);
336 mToastQueue = new ArrayList<ToastRecord>();
337 mNotificationList = new ArrayList<NotificationRecord>();
338 mHandler = new WorkerHandler();
339 mStatusBarService = statusBar;
340 statusBar.setNotificationCallbacks(mNotificationCallbacks);
341
342 // register for battery changed notifications
343 IntentFilter filter = new IntentFilter();
344 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
345 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
346 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
347 mContext.registerReceiver(mIntentReceiver, filter);
348 }
349
350 // Toasts
351 // ============================================================================
352 public void enqueueToast(String pkg, ITransientNotification callback, int duration)
353 {
354 Log.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
355
356 if (pkg == null || callback == null) {
357 Log.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
358 return ;
359 }
360
361 synchronized (mToastQueue) {
362 int callingPid = Binder.getCallingPid();
363 long callingId = Binder.clearCallingIdentity();
364 try {
365 ToastRecord record;
366 int index = indexOfToastLocked(pkg, callback);
367 // If it's already in the queue, we update it in place, we don't
368 // move it to the end of the queue.
369 if (index >= 0) {
370 record = mToastQueue.get(index);
371 record.update(duration);
372 } else {
373 record = new ToastRecord(callingPid, pkg, callback, duration);
374 mToastQueue.add(record);
375 index = mToastQueue.size() - 1;
376 keepProcessAliveLocked(callingPid);
377 }
378 // If it's at index 0, it's the current toast. It doesn't matter if it's
379 // new or just been updated. Call back and tell it to show itself.
380 // If the callback fails, this will remove it from the list, so don't
381 // assume that it's valid after this.
382 if (index == 0) {
383 showNextToastLocked();
384 }
385 } finally {
386 Binder.restoreCallingIdentity(callingId);
387 }
388 }
389 }
390
391 public void cancelToast(String pkg, ITransientNotification callback) {
392 Log.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
393
394 if (pkg == null || callback == null) {
395 Log.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
396 return ;
397 }
398
399 synchronized (mToastQueue) {
400 long callingId = Binder.clearCallingIdentity();
401 try {
402 int index = indexOfToastLocked(pkg, callback);
403 if (index >= 0) {
404 cancelToastLocked(index);
405 } else {
406 Log.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
407 }
408 } finally {
409 Binder.restoreCallingIdentity(callingId);
410 }
411 }
412 }
413
414 private void showNextToastLocked() {
415 ToastRecord record = mToastQueue.get(0);
416 while (record != null) {
417 if (DBG) Log.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
418 try {
419 record.callback.show();
420 scheduleTimeoutLocked(record, false);
421 return;
422 } catch (RemoteException e) {
423 Log.w(TAG, "Object died trying to show notification " + record.callback
424 + " in package " + record.pkg);
425 // remove it from the list and let the process die
426 int index = mToastQueue.indexOf(record);
427 if (index >= 0) {
428 mToastQueue.remove(index);
429 }
430 keepProcessAliveLocked(record.pid);
431 if (mToastQueue.size() > 0) {
432 record = mToastQueue.get(0);
433 } else {
434 record = null;
435 }
436 }
437 }
438 }
439
440 private void cancelToastLocked(int index) {
441 ToastRecord record = mToastQueue.get(index);
442 try {
443 record.callback.hide();
444 } catch (RemoteException e) {
445 Log.w(TAG, "Object died trying to hide notification " + record.callback
446 + " in package " + record.pkg);
447 // don't worry about this, we're about to remove it from
448 // the list anyway
449 }
450 mToastQueue.remove(index);
451 keepProcessAliveLocked(record.pid);
452 if (mToastQueue.size() > 0) {
453 // Show the next one. If the callback fails, this will remove
454 // it from the list, so don't assume that the list hasn't changed
455 // after this point.
456 showNextToastLocked();
457 }
458 }
459
460 private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
461 {
462 Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
463 long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
464 mHandler.removeCallbacksAndMessages(r);
465 mHandler.sendMessageDelayed(m, delay);
466 }
467
468 private void handleTimeout(ToastRecord record)
469 {
470 if (DBG) Log.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
471 synchronized (mToastQueue) {
472 int index = indexOfToastLocked(record.pkg, record.callback);
473 if (index >= 0) {
474 cancelToastLocked(index);
475 }
476 }
477 }
478
479 // lock on mToastQueue
480 private int indexOfToastLocked(String pkg, ITransientNotification callback)
481 {
482 IBinder cbak = callback.asBinder();
483 ArrayList<ToastRecord> list = mToastQueue;
484 int len = list.size();
485 for (int i=0; i<len; i++) {
486 ToastRecord r = list.get(i);
487 if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
488 return i;
489 }
490 }
491 return -1;
492 }
493
494 // lock on mToastQueue
495 private void keepProcessAliveLocked(int pid)
496 {
497 int toastCount = 0; // toasts from this pid
498 ArrayList<ToastRecord> list = mToastQueue;
499 int N = list.size();
500 for (int i=0; i<N; i++) {
501 ToastRecord r = list.get(i);
502 if (r.pid == pid) {
503 toastCount++;
504 }
505 }
506 try {
507 mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
508 } catch (RemoteException e) {
509 // Shouldn't happen.
510 }
511 }
512
513 private final class WorkerHandler extends Handler
514 {
515 @Override
516 public void handleMessage(Message msg)
517 {
518 switch (msg.what)
519 {
520 case MESSAGE_TIMEOUT:
521 handleTimeout((ToastRecord)msg.obj);
522 break;
523 }
524 }
525 }
526
527
528 // Notifications
529 // ============================================================================
530 public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
531 {
532 // This conditional is a dirty hack to limit the logging done on
533 // behalf of the download manager without affecting other apps.
534 if (!pkg.equals("com.android.providers.downloads")
535 || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
536 EventLog.writeEvent(EVENT_LOG_ENQUEUE, pkg, id, notification.toString());
537 }
538
539 if (pkg == null || notification == null) {
540 throw new IllegalArgumentException("null not allowed: pkg=" + pkg
541 + " id=" + id + " notification=" + notification);
542 }
543 if (notification.icon != 0) {
544 if (notification.contentView == null) {
545 throw new IllegalArgumentException("contentView required: pkg=" + pkg
546 + " id=" + id + " notification=" + notification);
547 }
548 if (notification.contentIntent == null) {
549 throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
550 + " id=" + id + " notification=" + notification);
551 }
552 }
553
554 synchronized (mNotificationList) {
555 NotificationRecord r = new NotificationRecord(pkg, id, notification);
556 NotificationRecord old = null;
557
558 int index = indexOfNotificationLocked(pkg, id);
559 if (index < 0) {
560 mNotificationList.add(r);
561 } else {
562 old = mNotificationList.remove(index);
563 mNotificationList.add(index, r);
564 }
565 if (notification.icon != 0) {
566 IconData icon = IconData.makeIcon(null, pkg, notification.icon,
567 notification.iconLevel,
568 notification.number);
569 CharSequence truncatedTicker = notification.tickerText;
570
571 // TODO: make this restriction do something smarter like never fill
572 // more than two screens. "Why would anyone need more than 80 characters." :-/
573 final int maxTickerLen = 80;
574 if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
575 truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
576 }
577
578 NotificationData n = new NotificationData();
579 n.id = id;
580 n.pkg = pkg;
581 n.when = notification.when;
582 n.tickerText = truncatedTicker;
583 n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
584 if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
585 n.clearable = true;
586 }
587 n.contentView = notification.contentView;
588 n.contentIntent = notification.contentIntent;
589 n.deleteIntent = notification.deleteIntent;
590 if (old != null && old.statusBarKey != null) {
591 r.statusBarKey = old.statusBarKey;
592 long identity = Binder.clearCallingIdentity();
593 try {
594 mStatusBarService.updateIcon(r.statusBarKey, icon, n);
595 }
596 finally {
597 Binder.restoreCallingIdentity(identity);
598 }
599 } else {
600 long identity = Binder.clearCallingIdentity();
601 try {
602 r.statusBarKey = mStatusBarService.addIcon(icon, n);
603 }
604 finally {
605 Binder.restoreCallingIdentity(identity);
606 }
607 }
608 } else {
609 if (old != null && old.statusBarKey != null) {
610 long identity = Binder.clearCallingIdentity();
611 try {
612 mStatusBarService.removeIcon(old.statusBarKey);
613 }
614 finally {
615 Binder.restoreCallingIdentity(identity);
616 }
617 }
618 }
619
620 // If we're not supposed to beep, vibrate, etc. then don't.
621 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
622 && (!(old != null
623 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))) {
624 // sound
625 final boolean useDefaultSound =
626 (notification.defaults & Notification.DEFAULT_SOUND) != 0;
627 if (useDefaultSound || notification.sound != null) {
628 Uri uri;
629 if (useDefaultSound) {
630 uri = Settings.System.DEFAULT_NOTIFICATION_URI;
631 } else {
632 uri = notification.sound;
633 }
634 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
635 int audioStreamType;
636 if (notification.audioStreamType >= 0) {
637 audioStreamType = notification.audioStreamType;
638 } else {
639 audioStreamType = DEFAULT_STREAM_TYPE;
640 }
641 mSoundNotification = r;
642 long identity = Binder.clearCallingIdentity();
643 try {
644 mSound.play(mContext, uri, looping, audioStreamType);
645 }
646 finally {
647 Binder.restoreCallingIdentity(identity);
648 }
649 }
650
651 // vibrate
652 final AudioManager audioManager = (AudioManager) mContext
653 .getSystemService(Context.AUDIO_SERVICE);
654 final boolean useDefaultVibrate =
655 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
656 if ((useDefaultVibrate || notification.vibrate != null)
657 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
658 mVibrateNotification = r;
659
660 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
661 : notification.vibrate,
662 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
663 }
664 }
665
666 // this option doesn't shut off the lights
667
668 // light
669 // the most recent thing gets the light
670 mLights.remove(old);
671 if (mLedNotification == old) {
672 mLedNotification = null;
673 }
674 //Log.i(TAG, "notification.lights="
675 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
676 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
677 mLights.add(r);
678 updateLightsLocked();
679 } else {
680 if (old != null
681 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
682 updateLightsLocked();
683 }
684 }
685 }
686
687 idOut[0] = id;
688 }
689
690 private void cancelNotificationLocked(NotificationRecord r) {
691 // status bar
692 if (r.notification.icon != 0) {
693 long identity = Binder.clearCallingIdentity();
694 try {
695 mStatusBarService.removeIcon(r.statusBarKey);
696 }
697 finally {
698 Binder.restoreCallingIdentity(identity);
699 }
700 r.statusBarKey = null;
701 }
702
703 // sound
704 if (mSoundNotification == r) {
705 mSoundNotification = null;
706 long identity = Binder.clearCallingIdentity();
707 try {
708 mSound.stop();
709 }
710 finally {
711 Binder.restoreCallingIdentity(identity);
712 }
713 }
714
715 // vibrate
716 if (mVibrateNotification == r) {
717 mVibrateNotification = null;
718 long identity = Binder.clearCallingIdentity();
719 try {
720 mVibrator.cancel();
721 }
722 finally {
723 Binder.restoreCallingIdentity(identity);
724 }
725 }
726
727 // light
728 mLights.remove(r);
729 if (mLedNotification == r) {
730 mLedNotification = null;
731 }
732 }
733
734 /**
735 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}.
736 */
737 private void cancelNotification(String pkg, int id, int mustHaveFlags) {
738 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
739
740 synchronized (mNotificationList) {
741 NotificationRecord r = null;
742
743 int index = indexOfNotificationLocked(pkg, id);
744 if (index >= 0) {
745 r = mNotificationList.get(index);
746
747 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
748 return;
749 }
750
751 mNotificationList.remove(index);
752
753 cancelNotificationLocked(r);
754 updateLightsLocked();
755 }
756 }
757 }
758
759 /**
760 * Cancels all notifications from a given package that have all of the
761 * {@code mustHaveFlags}.
762 */
763 private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) {
764 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
765
766 synchronized (mNotificationList) {
767 final int N = mNotificationList.size();
768 boolean canceledSomething = false;
769 for (int i = N-1; i >= 0; --i) {
770 NotificationRecord r = mNotificationList.get(i);
771 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
772 continue;
773 }
774 if (!r.pkg.equals(pkg)) {
775 continue;
776 }
777 mNotificationList.remove(i);
778 cancelNotificationLocked(r);
779 canceledSomething = true;
780 }
781 if (canceledSomething) {
782 updateLightsLocked();
783 }
784 }
785 }
786
787
788 public void cancelNotification(String pkg, int id)
789 {
790 cancelNotification(pkg, id, 0);
791 }
792
793 public void cancelAllNotifications(String pkg)
794 {
795 cancelAllNotificationsInt(pkg, 0);
796 }
797
798 public void cancelAll() {
799 synchronized (mNotificationList) {
800 final int N = mNotificationList.size();
801 for (int i=N-1; i>=0; i--) {
802 NotificationRecord r = mNotificationList.get(i);
803
804 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
805 | Notification.FLAG_NO_CLEAR)) == 0) {
806 if (r.notification.deleteIntent != null) {
807 try {
808 r.notification.deleteIntent.send();
809 } catch (PendingIntent.CanceledException ex) {
810 // do nothing - there's no relevant way to recover, and
811 // no reason to let this propagate
812 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
813 }
814 }
815 mNotificationList.remove(i);
816 cancelNotificationLocked(r);
817 }
818 }
819
820 updateLightsLocked();
821 }
822 }
823
824 private void updateLights() {
825 synchronized (mNotificationList) {
826 updateLightsLocked();
827 }
828 }
829
830 // lock on mNotificationList
831 private void updateLightsLocked()
832 {
833 // battery low has highest priority, then charging
834 if (mBatteryLow && !mBatteryCharging) {
835 Hardware.setLedState(BATTERY_LOW_ARGB, BATTERY_LOW_ON, BATTERY_LOW_OFF);
836 } else if (mBatteryCharging) {
837 if (mBatteryLow) {
838 Hardware.setLedState(CHARGING_LOW_ARGB, CHARGING_LOW_ON, CHARGING_LOW_OFF);
839 } else if (mBatteryFull) {
840 Hardware.setLedState(CHARGING_FULL_ARGB, CHARGING_FULL_ON, CHARGING_FULL_OFF);
841 } else {
842 Hardware.setLedState(CHARGING_ARGB, CHARGING_ON, CHARGING_OFF);
843 }
844 } else {
845 // handle notification lights
846 if (mLedNotification == null) {
847 // get next notification, if any
848 int n = mLights.size();
849 if (n > 0) {
850 mLedNotification = mLights.get(n-1);
851 }
852 }
853
854 if (mLedNotification == null) {
855 Hardware.setLedState(0, 0, 0);
856 } else {
857 Hardware.setLedState(mLedNotification.notification.ledARGB,
858 mLedNotification.notification.ledOnMS,
859 mLedNotification.notification.ledOffMS);
860 }
861 }
862 }
863
864 // lock on mNotificationList
865 private int indexOfNotificationLocked(String pkg, int id)
866 {
867 ArrayList<NotificationRecord> list = mNotificationList;
868 final int len = list.size();
869 for (int i=0; i<len; i++) {
870 NotificationRecord r = list.get(i);
871 if (r.id == id && r.pkg.equals(pkg)) {
872 return i;
873 }
874 }
875 return -1;
876 }
877
878 // ======================================================================
879 @Override
880 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
881 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
882 != PackageManager.PERMISSION_GRANTED) {
883 pw.println("Permission Denial: can't dump NotificationManager from from pid="
884 + Binder.getCallingPid()
885 + ", uid=" + Binder.getCallingUid());
886 return;
887 }
888
889 pw.println("Current Notification Manager state:");
890
891 int N;
892
893 synchronized (mToastQueue) {
894 N = mToastQueue.size();
895 if (N > 0) {
896 pw.println(" Toast Queue:");
897 for (int i=0; i<N; i++) {
898 mToastQueue.get(i).dump(pw, " ");
899 }
900 pw.println(" ");
901 }
902
903 }
904
905 synchronized (mNotificationList) {
906 N = mNotificationList.size();
907 if (N > 0) {
908 pw.println(" Notification List:");
909 for (int i=0; i<N; i++) {
910 mNotificationList.get(i).dump(pw, " ", mContext);
911 }
912 pw.println(" ");
913 }
914
915 N = mLights.size();
916 if (N > 0) {
917 pw.println(" Lights List:");
918 for (int i=0; i<N; i++) {
919 mLights.get(i).dump(pw, " ", mContext);
920 }
921 pw.println(" ");
922 }
923
924 pw.println(" mSoundNotification=" + mSoundNotification);
925 pw.println(" mSound=" + mSound);
926 pw.println(" mVibrateNotification=" + mVibrateNotification);
927 }
928 }
929}