blob: e5de7f9fb5f610c8854a85901987603f14558099 [file] [log] [blame]
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001/*
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
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -080075 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070076
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 }
315 }
316 };
317
318 NotificationManagerService(Context context, StatusBarService statusBar)
319 {
320 super();
321 mContext = context;
322 mAm = ActivityManagerNative.getDefault();
323 mSound = new AsyncPlayer(TAG);
324 mSound.setUsesWakeLock(context);
325 mToastQueue = new ArrayList<ToastRecord>();
326 mNotificationList = new ArrayList<NotificationRecord>();
327 mHandler = new WorkerHandler();
328 mStatusBarService = statusBar;
329 statusBar.setNotificationCallbacks(mNotificationCallbacks);
330
331 // register for battery changed notifications
332 IntentFilter filter = new IntentFilter();
333 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
334 mContext.registerReceiver(mIntentReceiver, filter);
335 }
336
337 // Toasts
338 // ============================================================================
339 public void enqueueToast(String pkg, ITransientNotification callback, int duration)
340 {
341 Log.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
342
343 if (pkg == null || callback == null) {
344 Log.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
345 return ;
346 }
347
348 synchronized (mToastQueue) {
349 int callingPid = Binder.getCallingPid();
350 long callingId = Binder.clearCallingIdentity();
351 try {
352 ToastRecord record;
353 int index = indexOfToastLocked(pkg, callback);
354 // If it's already in the queue, we update it in place, we don't
355 // move it to the end of the queue.
356 if (index >= 0) {
357 record = mToastQueue.get(index);
358 record.update(duration);
359 } else {
360 record = new ToastRecord(callingPid, pkg, callback, duration);
361 mToastQueue.add(record);
362 index = mToastQueue.size() - 1;
363 keepProcessAliveLocked(callingPid);
364 }
365 // If it's at index 0, it's the current toast. It doesn't matter if it's
366 // new or just been updated. Call back and tell it to show itself.
367 // If the callback fails, this will remove it from the list, so don't
368 // assume that it's valid after this.
369 if (index == 0) {
370 showNextToastLocked();
371 }
372 } finally {
373 Binder.restoreCallingIdentity(callingId);
374 }
375 }
376 }
377
378 public void cancelToast(String pkg, ITransientNotification callback) {
379 Log.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
380
381 if (pkg == null || callback == null) {
382 Log.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
383 return ;
384 }
385
386 synchronized (mToastQueue) {
387 long callingId = Binder.clearCallingIdentity();
388 try {
389 int index = indexOfToastLocked(pkg, callback);
390 if (index >= 0) {
391 cancelToastLocked(index);
392 } else {
393 Log.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
394 }
395 } finally {
396 Binder.restoreCallingIdentity(callingId);
397 }
398 }
399 }
400
401 private void showNextToastLocked() {
402 ToastRecord record = mToastQueue.get(0);
403 while (record != null) {
404 if (DBG) Log.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
405 try {
406 record.callback.show();
407 scheduleTimeoutLocked(record, false);
408 return;
409 } catch (RemoteException e) {
410 Log.w(TAG, "Object died trying to show notification " + record.callback
411 + " in package " + record.pkg);
412 // remove it from the list and let the process die
413 int index = mToastQueue.indexOf(record);
414 if (index >= 0) {
415 mToastQueue.remove(index);
416 }
417 keepProcessAliveLocked(record.pid);
418 if (mToastQueue.size() > 0) {
419 record = mToastQueue.get(0);
420 } else {
421 record = null;
422 }
423 }
424 }
425 }
426
427 private void cancelToastLocked(int index) {
428 ToastRecord record = mToastQueue.get(index);
429 try {
430 record.callback.hide();
431 } catch (RemoteException e) {
432 Log.w(TAG, "Object died trying to hide notification " + record.callback
433 + " in package " + record.pkg);
434 // don't worry about this, we're about to remove it from
435 // the list anyway
436 }
437 mToastQueue.remove(index);
438 keepProcessAliveLocked(record.pid);
439 if (mToastQueue.size() > 0) {
440 // Show the next one. If the callback fails, this will remove
441 // it from the list, so don't assume that the list hasn't changed
442 // after this point.
443 showNextToastLocked();
444 }
445 }
446
447 private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
448 {
449 Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
450 long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
451 mHandler.removeCallbacksAndMessages(r);
452 mHandler.sendMessageDelayed(m, delay);
453 }
454
455 private void handleTimeout(ToastRecord record)
456 {
457 if (DBG) Log.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
458 synchronized (mToastQueue) {
459 int index = indexOfToastLocked(record.pkg, record.callback);
460 if (index >= 0) {
461 cancelToastLocked(index);
462 }
463 }
464 }
465
466 // lock on mToastQueue
467 private int indexOfToastLocked(String pkg, ITransientNotification callback)
468 {
469 IBinder cbak = callback.asBinder();
470 ArrayList<ToastRecord> list = mToastQueue;
471 int len = list.size();
472 for (int i=0; i<len; i++) {
473 ToastRecord r = list.get(i);
474 if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
475 return i;
476 }
477 }
478 return -1;
479 }
480
481 // lock on mToastQueue
482 private void keepProcessAliveLocked(int pid)
483 {
484 int toastCount = 0; // toasts from this pid
485 ArrayList<ToastRecord> list = mToastQueue;
486 int N = list.size();
487 for (int i=0; i<N; i++) {
488 ToastRecord r = list.get(i);
489 if (r.pid == pid) {
490 toastCount++;
491 }
492 }
493 try {
494 mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
495 } catch (RemoteException e) {
496 // Shouldn't happen.
497 }
498 }
499
500 private final class WorkerHandler extends Handler
501 {
502 @Override
503 public void handleMessage(Message msg)
504 {
505 switch (msg.what)
506 {
507 case MESSAGE_TIMEOUT:
508 handleTimeout((ToastRecord)msg.obj);
509 break;
510 }
511 }
512 }
513
514
515 // Notifications
516 // ============================================================================
517 public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
518 {
519 // This conditional is a dirty hack to limit the logging done on
520 // behalf of the download manager without affecting other apps.
521 if (!pkg.equals("com.android.providers.downloads")
522 || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
523 EventLog.writeEvent(EVENT_LOG_ENQUEUE, pkg, id, notification.toString());
524 }
525
526 if (pkg == null || notification == null) {
527 throw new IllegalArgumentException("null not allowed: pkg=" + pkg
528 + " id=" + id + " notification=" + notification);
529 }
530 if (notification.icon != 0) {
531 if (notification.contentView == null) {
532 throw new IllegalArgumentException("contentView required: pkg=" + pkg
533 + " id=" + id + " notification=" + notification);
534 }
535 if (notification.contentIntent == null) {
536 throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
537 + " id=" + id + " notification=" + notification);
538 }
539 }
540
541 synchronized (mNotificationList) {
542 NotificationRecord r = new NotificationRecord(pkg, id, notification);
543 NotificationRecord old = null;
544
545 int index = indexOfNotificationLocked(pkg, id);
546 if (index < 0) {
547 mNotificationList.add(r);
548 } else {
549 old = mNotificationList.remove(index);
550 mNotificationList.add(index, r);
551 }
552 if (notification.icon != 0) {
553 IconData icon = IconData.makeIcon(null, pkg, notification.icon,
554 notification.iconLevel,
555 notification.number);
556 CharSequence truncatedTicker = notification.tickerText;
557
558 // TODO: make this restriction do something smarter like never fill
559 // more than two screens. "Why would anyone need more than 80 characters." :-/
560 final int maxTickerLen = 80;
561 if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
562 truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
563 }
564
565 NotificationData n = new NotificationData();
566 n.id = id;
567 n.pkg = pkg;
568 n.when = notification.when;
569 n.tickerText = truncatedTicker;
570 n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
571 if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
572 n.clearable = true;
573 }
574 n.contentView = notification.contentView;
575 n.contentIntent = notification.contentIntent;
576 n.deleteIntent = notification.deleteIntent;
577 if (old != null && old.statusBarKey != null) {
578 r.statusBarKey = old.statusBarKey;
579 long identity = Binder.clearCallingIdentity();
580 try {
581 mStatusBarService.updateIcon(r.statusBarKey, icon, n);
582 }
583 finally {
584 Binder.restoreCallingIdentity(identity);
585 }
586 } else {
587 long identity = Binder.clearCallingIdentity();
588 try {
589 r.statusBarKey = mStatusBarService.addIcon(icon, n);
590 }
591 finally {
592 Binder.restoreCallingIdentity(identity);
593 }
594 }
595 } else {
596 if (old != null && old.statusBarKey != null) {
597 long identity = Binder.clearCallingIdentity();
598 try {
599 mStatusBarService.removeIcon(old.statusBarKey);
600 }
601 finally {
602 Binder.restoreCallingIdentity(identity);
603 }
604 }
605 }
606
607 // If we're not supposed to beep, vibrate, etc. then don't.
608 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
609 && (!(old != null
610 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))) {
611 // sound
612 final boolean useDefaultSound =
613 (notification.defaults & Notification.DEFAULT_SOUND) != 0;
614 if (useDefaultSound || notification.sound != null) {
615 Uri uri;
616 if (useDefaultSound) {
617 uri = Settings.System.DEFAULT_NOTIFICATION_URI;
618 } else {
619 uri = notification.sound;
620 }
621 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
622 int audioStreamType;
623 if (notification.audioStreamType >= 0) {
624 audioStreamType = notification.audioStreamType;
625 } else {
626 audioStreamType = DEFAULT_STREAM_TYPE;
627 }
628 mSoundNotification = r;
629 long identity = Binder.clearCallingIdentity();
630 try {
631 mSound.play(mContext, uri, looping, audioStreamType);
632 }
633 finally {
634 Binder.restoreCallingIdentity(identity);
635 }
636 }
637
638 // vibrate
639 final AudioManager audioManager = (AudioManager) mContext
640 .getSystemService(Context.AUDIO_SERVICE);
641 final boolean useDefaultVibrate =
642 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
643 if ((useDefaultVibrate || notification.vibrate != null)
644 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
645 mVibrateNotification = r;
646
647 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
648 : notification.vibrate,
649 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
650 }
651 }
652
653 // this option doesn't shut off the lights
654
655 // light
656 // the most recent thing gets the light
657 mLights.remove(old);
658 if (mLedNotification == old) {
659 mLedNotification = null;
660 }
661 //Log.i(TAG, "notification.lights="
662 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
663 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
664 mLights.add(r);
665 updateLightsLocked();
666 } else {
667 if (old != null
668 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
669 updateLightsLocked();
670 }
671 }
672 }
673
674 idOut[0] = id;
675 }
676
677 private void cancelNotificationLocked(NotificationRecord r) {
678 // status bar
679 if (r.notification.icon != 0) {
680 long identity = Binder.clearCallingIdentity();
681 try {
682 mStatusBarService.removeIcon(r.statusBarKey);
683 }
684 finally {
685 Binder.restoreCallingIdentity(identity);
686 }
687 r.statusBarKey = null;
688 }
689
690 // sound
691 if (mSoundNotification == r) {
692 mSoundNotification = null;
693 long identity = Binder.clearCallingIdentity();
694 try {
695 mSound.stop();
696 }
697 finally {
698 Binder.restoreCallingIdentity(identity);
699 }
700 }
701
702 // vibrate
703 if (mVibrateNotification == r) {
704 mVibrateNotification = null;
705 long identity = Binder.clearCallingIdentity();
706 try {
707 mVibrator.cancel();
708 }
709 finally {
710 Binder.restoreCallingIdentity(identity);
711 }
712 }
713
714 // light
715 mLights.remove(r);
716 if (mLedNotification == r) {
717 mLedNotification = null;
718 }
719 }
720
721 /**
722 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}.
723 */
724 private void cancelNotification(String pkg, int id, int mustHaveFlags) {
725 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
726
727 synchronized (mNotificationList) {
728 NotificationRecord r = null;
729
730 int index = indexOfNotificationLocked(pkg, id);
731 if (index >= 0) {
732 r = mNotificationList.get(index);
733
734 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
735 return;
736 }
737
738 mNotificationList.remove(index);
739
740 cancelNotificationLocked(r);
741 updateLightsLocked();
742 }
743 }
744 }
745
746 /**
747 * Cancels all notifications from a given package that have all of the
748 * {@code mustHaveFlags}.
749 */
750 private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) {
751 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
752
753 synchronized (mNotificationList) {
754 final int N = mNotificationList.size();
755 boolean canceledSomething = false;
756 for (int i = N-1; i >= 0; --i) {
757 NotificationRecord r = mNotificationList.get(i);
758 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
759 continue;
760 }
761 if (!r.pkg.equals(pkg)) {
762 continue;
763 }
764 mNotificationList.remove(i);
765 cancelNotificationLocked(r);
766 canceledSomething = true;
767 }
768 if (canceledSomething) {
769 updateLightsLocked();
770 }
771 }
772 }
773
774
775 public void cancelNotification(String pkg, int id)
776 {
777 cancelNotification(pkg, id, 0);
778 }
779
780 public void cancelAllNotifications(String pkg)
781 {
782 cancelAllNotificationsInt(pkg, 0);
783 }
784
785 public void cancelAll() {
786 synchronized (mNotificationList) {
787 final int N = mNotificationList.size();
788 for (int i=N-1; i>=0; i--) {
789 NotificationRecord r = mNotificationList.get(i);
790
791 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
792 | Notification.FLAG_NO_CLEAR)) == 0) {
793 if (r.notification.deleteIntent != null) {
794 try {
795 r.notification.deleteIntent.send();
796 } catch (PendingIntent.CanceledException ex) {
797 // do nothing - there's no relevant way to recover, and
798 // no reason to let this propagate
799 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
800 }
801 }
802 mNotificationList.remove(i);
803 cancelNotificationLocked(r);
804 }
805 }
806
807 updateLightsLocked();
808 }
809 }
810
811 private void updateLights() {
812 synchronized (mNotificationList) {
813 updateLightsLocked();
814 }
815 }
816
817 // lock on mNotificationList
818 private void updateLightsLocked()
819 {
820 // battery low has highest priority, then charging
821 if (mBatteryLow && !mBatteryCharging) {
822 Hardware.setLedState(BATTERY_LOW_ARGB, BATTERY_LOW_ON, BATTERY_LOW_OFF);
823 } else if (mBatteryCharging) {
824 if (mBatteryLow) {
825 Hardware.setLedState(CHARGING_LOW_ARGB, CHARGING_LOW_ON, CHARGING_LOW_OFF);
826 } else if (mBatteryFull) {
827 Hardware.setLedState(CHARGING_FULL_ARGB, CHARGING_FULL_ON, CHARGING_FULL_OFF);
828 } else {
829 Hardware.setLedState(CHARGING_ARGB, CHARGING_ON, CHARGING_OFF);
830 }
831 } else {
832 // handle notification lights
833 if (mLedNotification == null) {
834 // get next notification, if any
835 int n = mLights.size();
836 if (n > 0) {
837 mLedNotification = mLights.get(n-1);
838 }
839 }
840
841 if (mLedNotification == null) {
842 Hardware.setLedState(0, 0, 0);
843 } else {
844 Hardware.setLedState(mLedNotification.notification.ledARGB,
845 mLedNotification.notification.ledOnMS,
846 mLedNotification.notification.ledOffMS);
847 }
848 }
849 }
850
851 // lock on mNotificationList
852 private int indexOfNotificationLocked(String pkg, int id)
853 {
854 ArrayList<NotificationRecord> list = mNotificationList;
855 final int len = list.size();
856 for (int i=0; i<len; i++) {
857 NotificationRecord r = list.get(i);
858 if (r.id == id && r.pkg.equals(pkg)) {
859 return i;
860 }
861 }
862 return -1;
863 }
864
865 // ======================================================================
866 @Override
867 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800868 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700869 != PackageManager.PERMISSION_GRANTED) {
870 pw.println("Permission Denial: can't dump NotificationManager from from pid="
871 + Binder.getCallingPid()
872 + ", uid=" + Binder.getCallingUid());
873 return;
874 }
875
876 pw.println("Current Notification Manager state:");
877
878 int N;
879
880 synchronized (mToastQueue) {
881 N = mToastQueue.size();
882 if (N > 0) {
883 pw.println(" Toast Queue:");
884 for (int i=0; i<N; i++) {
885 mToastQueue.get(i).dump(pw, " ");
886 }
887 pw.println(" ");
888 }
889
890 }
891
892 synchronized (mNotificationList) {
893 N = mNotificationList.size();
894 if (N > 0) {
895 pw.println(" Notification List:");
896 for (int i=0; i<N; i++) {
897 mNotificationList.get(i).dump(pw, " ", mContext);
898 }
899 pw.println(" ");
900 }
901
902 N = mLights.size();
903 if (N > 0) {
904 pw.println(" Lights List:");
905 for (int i=0; i<N; i++) {
906 mLights.get(i).dump(pw, " ", mContext);
907 }
908 pw.println(" ");
909 }
910
911 pw.println(" mSoundNotification=" + mSoundNotification);
912 pw.println(" mSound=" + mSound);
913 pw.println(" mVibrateNotification=" + mVibrateNotification);
914 }
915 }
916}