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