blob: c4b058709bc5a9d03b0385d5314458ab01f883d3 [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);
591 }
592 finally {
593 Binder.restoreCallingIdentity(identity);
594 }
595 }
596 } else {
597 if (old != null && old.statusBarKey != null) {
598 long identity = Binder.clearCallingIdentity();
599 try {
600 mStatusBarService.removeIcon(old.statusBarKey);
601 }
602 finally {
603 Binder.restoreCallingIdentity(identity);
604 }
605 }
606 }
607
608 // If we're not supposed to beep, vibrate, etc. then don't.
609 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
610 && (!(old != null
611 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))) {
612 // sound
613 final boolean useDefaultSound =
614 (notification.defaults & Notification.DEFAULT_SOUND) != 0;
615 if (useDefaultSound || notification.sound != null) {
616 Uri uri;
617 if (useDefaultSound) {
618 uri = Settings.System.DEFAULT_NOTIFICATION_URI;
619 } else {
620 uri = notification.sound;
621 }
622 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
623 int audioStreamType;
624 if (notification.audioStreamType >= 0) {
625 audioStreamType = notification.audioStreamType;
626 } else {
627 audioStreamType = DEFAULT_STREAM_TYPE;
628 }
629 mSoundNotification = r;
630 long identity = Binder.clearCallingIdentity();
631 try {
632 mSound.play(mContext, uri, looping, audioStreamType);
633 }
634 finally {
635 Binder.restoreCallingIdentity(identity);
636 }
637 }
638
639 // vibrate
640 final AudioManager audioManager = (AudioManager) mContext
641 .getSystemService(Context.AUDIO_SERVICE);
642 final boolean useDefaultVibrate =
643 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
644 if ((useDefaultVibrate || notification.vibrate != null)
645 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
646 mVibrateNotification = r;
647
648 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
649 : notification.vibrate,
650 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
651 }
652 }
653
654 // this option doesn't shut off the lights
655
656 // light
657 // the most recent thing gets the light
658 mLights.remove(old);
659 if (mLedNotification == old) {
660 mLedNotification = null;
661 }
662 //Log.i(TAG, "notification.lights="
663 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
664 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
665 mLights.add(r);
666 updateLightsLocked();
667 } else {
668 if (old != null
669 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
670 updateLightsLocked();
671 }
672 }
673 }
674
675 idOut[0] = id;
676 }
677
678 private void cancelNotificationLocked(NotificationRecord r) {
679 // status bar
680 if (r.notification.icon != 0) {
681 long identity = Binder.clearCallingIdentity();
682 try {
683 mStatusBarService.removeIcon(r.statusBarKey);
684 }
685 finally {
686 Binder.restoreCallingIdentity(identity);
687 }
688 r.statusBarKey = null;
689 }
690
691 // sound
692 if (mSoundNotification == r) {
693 mSoundNotification = null;
694 long identity = Binder.clearCallingIdentity();
695 try {
696 mSound.stop();
697 }
698 finally {
699 Binder.restoreCallingIdentity(identity);
700 }
701 }
702
703 // vibrate
704 if (mVibrateNotification == r) {
705 mVibrateNotification = null;
706 long identity = Binder.clearCallingIdentity();
707 try {
708 mVibrator.cancel();
709 }
710 finally {
711 Binder.restoreCallingIdentity(identity);
712 }
713 }
714
715 // light
716 mLights.remove(r);
717 if (mLedNotification == r) {
718 mLedNotification = null;
719 }
720 }
721
722 /**
723 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}.
724 */
725 private void cancelNotification(String pkg, int id, int mustHaveFlags) {
726 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
727
728 synchronized (mNotificationList) {
729 NotificationRecord r = null;
730
731 int index = indexOfNotificationLocked(pkg, id);
732 if (index >= 0) {
733 r = mNotificationList.get(index);
734
735 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
736 return;
737 }
738
739 mNotificationList.remove(index);
740
741 cancelNotificationLocked(r);
742 updateLightsLocked();
743 }
744 }
745 }
746
747 /**
748 * Cancels all notifications from a given package that have all of the
749 * {@code mustHaveFlags}.
750 */
751 private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) {
752 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
753
754 synchronized (mNotificationList) {
755 final int N = mNotificationList.size();
756 boolean canceledSomething = false;
757 for (int i = N-1; i >= 0; --i) {
758 NotificationRecord r = mNotificationList.get(i);
759 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
760 continue;
761 }
762 if (!r.pkg.equals(pkg)) {
763 continue;
764 }
765 mNotificationList.remove(i);
766 cancelNotificationLocked(r);
767 canceledSomething = true;
768 }
769 if (canceledSomething) {
770 updateLightsLocked();
771 }
772 }
773 }
774
775
776 public void cancelNotification(String pkg, int id)
777 {
778 cancelNotification(pkg, id, 0);
779 }
780
781 public void cancelAllNotifications(String pkg)
782 {
783 cancelAllNotificationsInt(pkg, 0);
784 }
785
786 public void cancelAll() {
787 synchronized (mNotificationList) {
788 final int N = mNotificationList.size();
789 for (int i=N-1; i>=0; i--) {
790 NotificationRecord r = mNotificationList.get(i);
791
792 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
793 | Notification.FLAG_NO_CLEAR)) == 0) {
794 if (r.notification.deleteIntent != null) {
795 try {
796 r.notification.deleteIntent.send();
797 } catch (PendingIntent.CanceledException ex) {
798 // do nothing - there's no relevant way to recover, and
799 // no reason to let this propagate
800 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
801 }
802 }
803 mNotificationList.remove(i);
804 cancelNotificationLocked(r);
805 }
806 }
807
808 updateLightsLocked();
809 }
810 }
811
812 private void updateLights() {
813 synchronized (mNotificationList) {
814 updateLightsLocked();
815 }
816 }
817
818 // lock on mNotificationList
819 private void updateLightsLocked()
820 {
The Android Open Source Project10592532009-03-18 17:39:46 -0700821 // Battery low always shows, other states only show if charging.
822 if (mBatteryLow) {
823 mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, BATTERY_LOW_ARGB,
824 HardwareService.LIGHT_FLASH_TIMED, BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 } else if (mBatteryCharging) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700826 if (mBatteryFull) {
827 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
828 BATTERY_FULL_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700830 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
831 BATTERY_MEDIUM_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 }
833 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700834 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY);
835 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800836
The Android Open Source Project10592532009-03-18 17:39:46 -0700837 // handle notification lights
838 if (mLedNotification == null) {
839 // get next notification, if any
840 int n = mLights.size();
841 if (n > 0) {
842 mLedNotification = mLights.get(n-1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800843 }
844 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700845 if (mLedNotification == null) {
846 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS);
847 } else {
848 mHardware.setLightFlashing_UNCHECKED(
849 HardwareService.LIGHT_ID_NOTIFICATIONS,
850 mLedNotification.notification.ledARGB,
851 HardwareService.LIGHT_FLASH_TIMED,
852 mLedNotification.notification.ledOnMS,
853 mLedNotification.notification.ledOffMS);
854 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800855 }
856
857 // lock on mNotificationList
858 private int indexOfNotificationLocked(String pkg, int id)
859 {
860 ArrayList<NotificationRecord> list = mNotificationList;
861 final int len = list.size();
862 for (int i=0; i<len; i++) {
863 NotificationRecord r = list.get(i);
864 if (r.id == id && r.pkg.equals(pkg)) {
865 return i;
866 }
867 }
868 return -1;
869 }
870
871 // ======================================================================
872 @Override
873 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
874 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
875 != PackageManager.PERMISSION_GRANTED) {
876 pw.println("Permission Denial: can't dump NotificationManager from from pid="
877 + Binder.getCallingPid()
878 + ", uid=" + Binder.getCallingUid());
879 return;
880 }
881
882 pw.println("Current Notification Manager state:");
883
884 int N;
885
886 synchronized (mToastQueue) {
887 N = mToastQueue.size();
888 if (N > 0) {
889 pw.println(" Toast Queue:");
890 for (int i=0; i<N; i++) {
891 mToastQueue.get(i).dump(pw, " ");
892 }
893 pw.println(" ");
894 }
895
896 }
897
898 synchronized (mNotificationList) {
899 N = mNotificationList.size();
900 if (N > 0) {
901 pw.println(" Notification List:");
902 for (int i=0; i<N; i++) {
903 mNotificationList.get(i).dump(pw, " ", mContext);
904 }
905 pw.println(" ");
906 }
907
908 N = mLights.size();
909 if (N > 0) {
910 pw.println(" Lights List:");
911 for (int i=0; i<N; i++) {
912 mLights.get(i).dump(pw, " ", mContext);
913 }
914 pw.println(" ");
915 }
916
917 pw.println(" mSoundNotification=" + mSoundNotification);
918 pw.println(" mSound=" + mSound);
919 pw.println(" mVibrateNotification=" + mVibrateNotification);
920 }
921 }
922}