blob: 4a2808b50c3d14382383d77efdfebbc8f5df7520 [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
svetoslavganov75986cf2009-05-14 22:28:01 -070019import com.android.server.status.IconData;
20import com.android.server.status.NotificationData;
21import com.android.server.status.StatusBarService;
22
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.app.ActivityManagerNative;
24import android.app.IActivityManager;
25import android.app.INotificationManager;
26import android.app.ITransientNotification;
27import android.app.Notification;
28import android.app.PendingIntent;
29import android.app.StatusBarManager;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
36import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.media.AsyncPlayer;
svetoslavganov75986cf2009-05-14 22:28:01 -070038import android.media.AudioManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.net.Uri;
40import android.os.BatteryManager;
41import android.os.Binder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.os.IBinder;
44import android.os.Message;
45import android.os.Power;
svetoslavganov75986cf2009-05-14 22:28:01 -070046import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.os.Vibrator;
48import android.provider.Settings;
svetoslavganov75986cf2009-05-14 22:28:01 -070049import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.util.EventLog;
51import android.util.Log;
svetoslavganov75986cf2009-05-14 22:28:01 -070052import android.view.accessibility.AccessibilityEvent;
53import android.view.accessibility.AccessibilityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054import android.widget.Toast;
55
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056import java.io.FileDescriptor;
57import java.io.PrintWriter;
58import java.util.ArrayList;
59import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060
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;
svetoslavganov75986cf2009-05-14 22:28:01 -0700101
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 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700597
598 sendAccessibilityEventTypeNotificationChangedDoCheck(notification, pkg);
599
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 } else {
601 if (old != null && old.statusBarKey != null) {
602 long identity = Binder.clearCallingIdentity();
603 try {
604 mStatusBarService.removeIcon(old.statusBarKey);
605 }
606 finally {
607 Binder.restoreCallingIdentity(identity);
608 }
609 }
610 }
611
612 // If we're not supposed to beep, vibrate, etc. then don't.
613 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
614 && (!(old != null
615 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))) {
616 // sound
617 final boolean useDefaultSound =
618 (notification.defaults & Notification.DEFAULT_SOUND) != 0;
619 if (useDefaultSound || notification.sound != null) {
620 Uri uri;
621 if (useDefaultSound) {
622 uri = Settings.System.DEFAULT_NOTIFICATION_URI;
623 } else {
624 uri = notification.sound;
625 }
626 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
627 int audioStreamType;
628 if (notification.audioStreamType >= 0) {
629 audioStreamType = notification.audioStreamType;
630 } else {
631 audioStreamType = DEFAULT_STREAM_TYPE;
632 }
633 mSoundNotification = r;
634 long identity = Binder.clearCallingIdentity();
635 try {
636 mSound.play(mContext, uri, looping, audioStreamType);
637 }
638 finally {
639 Binder.restoreCallingIdentity(identity);
640 }
641 }
642
643 // vibrate
644 final AudioManager audioManager = (AudioManager) mContext
645 .getSystemService(Context.AUDIO_SERVICE);
646 final boolean useDefaultVibrate =
647 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
648 if ((useDefaultVibrate || notification.vibrate != null)
649 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
650 mVibrateNotification = r;
651
652 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
653 : notification.vibrate,
654 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
655 }
656 }
657
658 // this option doesn't shut off the lights
659
660 // light
661 // the most recent thing gets the light
662 mLights.remove(old);
663 if (mLedNotification == old) {
664 mLedNotification = null;
665 }
666 //Log.i(TAG, "notification.lights="
667 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
668 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
669 mLights.add(r);
670 updateLightsLocked();
671 } else {
672 if (old != null
673 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
674 updateLightsLocked();
675 }
676 }
677 }
678
679 idOut[0] = id;
680 }
681
svetoslavganov75986cf2009-05-14 22:28:01 -0700682 private void sendAccessibilityEventTypeNotificationChangedDoCheck(Notification notification,
683 CharSequence packageName) {
684 AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
685 if (!manager.isEnabled()) {
686 return;
687 }
688
689 AccessibilityEvent event =
690 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
691 event.setPackageName(packageName);
692 event.setClassName(Notification.class.getName());
693 event.setParcelableData(notification);
694 CharSequence tickerText = notification.tickerText;
695 if (!TextUtils.isEmpty(tickerText)) {
696 event.getText().add(tickerText);
697 }
698
699 manager.sendAccessibilityEvent(event);
700 }
701
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702 private void cancelNotificationLocked(NotificationRecord r) {
703 // status bar
704 if (r.notification.icon != 0) {
705 long identity = Binder.clearCallingIdentity();
706 try {
707 mStatusBarService.removeIcon(r.statusBarKey);
708 }
709 finally {
710 Binder.restoreCallingIdentity(identity);
711 }
712 r.statusBarKey = null;
713 }
714
715 // sound
716 if (mSoundNotification == r) {
717 mSoundNotification = null;
718 long identity = Binder.clearCallingIdentity();
719 try {
720 mSound.stop();
721 }
722 finally {
723 Binder.restoreCallingIdentity(identity);
724 }
725 }
726
727 // vibrate
728 if (mVibrateNotification == r) {
729 mVibrateNotification = null;
730 long identity = Binder.clearCallingIdentity();
731 try {
732 mVibrator.cancel();
733 }
734 finally {
735 Binder.restoreCallingIdentity(identity);
736 }
737 }
738
739 // light
740 mLights.remove(r);
741 if (mLedNotification == r) {
742 mLedNotification = null;
743 }
744 }
745
746 /**
747 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}.
748 */
749 private void cancelNotification(String pkg, int id, int mustHaveFlags) {
750 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
751
752 synchronized (mNotificationList) {
753 NotificationRecord r = null;
754
755 int index = indexOfNotificationLocked(pkg, id);
756 if (index >= 0) {
757 r = mNotificationList.get(index);
758
759 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
760 return;
761 }
762
763 mNotificationList.remove(index);
764
765 cancelNotificationLocked(r);
766 updateLightsLocked();
767 }
768 }
769 }
770
771 /**
772 * Cancels all notifications from a given package that have all of the
773 * {@code mustHaveFlags}.
774 */
775 private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) {
776 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
777
778 synchronized (mNotificationList) {
779 final int N = mNotificationList.size();
780 boolean canceledSomething = false;
781 for (int i = N-1; i >= 0; --i) {
782 NotificationRecord r = mNotificationList.get(i);
783 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
784 continue;
785 }
786 if (!r.pkg.equals(pkg)) {
787 continue;
788 }
789 mNotificationList.remove(i);
790 cancelNotificationLocked(r);
791 canceledSomething = true;
792 }
793 if (canceledSomething) {
794 updateLightsLocked();
795 }
796 }
797 }
798
799
800 public void cancelNotification(String pkg, int id)
801 {
802 cancelNotification(pkg, id, 0);
803 }
804
805 public void cancelAllNotifications(String pkg)
806 {
807 cancelAllNotificationsInt(pkg, 0);
808 }
809
810 public void cancelAll() {
811 synchronized (mNotificationList) {
812 final int N = mNotificationList.size();
813 for (int i=N-1; i>=0; i--) {
814 NotificationRecord r = mNotificationList.get(i);
815
816 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
817 | Notification.FLAG_NO_CLEAR)) == 0) {
818 if (r.notification.deleteIntent != null) {
819 try {
820 r.notification.deleteIntent.send();
821 } catch (PendingIntent.CanceledException ex) {
822 // do nothing - there's no relevant way to recover, and
823 // no reason to let this propagate
824 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
825 }
826 }
827 mNotificationList.remove(i);
828 cancelNotificationLocked(r);
829 }
830 }
831
832 updateLightsLocked();
833 }
834 }
835
836 private void updateLights() {
837 synchronized (mNotificationList) {
838 updateLightsLocked();
839 }
840 }
841
842 // lock on mNotificationList
843 private void updateLightsLocked()
844 {
The Android Open Source Project10592532009-03-18 17:39:46 -0700845 // Battery low always shows, other states only show if charging.
846 if (mBatteryLow) {
847 mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, BATTERY_LOW_ARGB,
848 HardwareService.LIGHT_FLASH_TIMED, BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800849 } else if (mBatteryCharging) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700850 if (mBatteryFull) {
851 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
852 BATTERY_FULL_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700854 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
855 BATTERY_MEDIUM_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856 }
857 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700858 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY);
859 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800860
The Android Open Source Project10592532009-03-18 17:39:46 -0700861 // handle notification lights
862 if (mLedNotification == null) {
863 // get next notification, if any
864 int n = mLights.size();
865 if (n > 0) {
866 mLedNotification = mLights.get(n-1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 }
868 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700869 if (mLedNotification == null) {
870 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS);
871 } else {
872 mHardware.setLightFlashing_UNCHECKED(
873 HardwareService.LIGHT_ID_NOTIFICATIONS,
874 mLedNotification.notification.ledARGB,
875 HardwareService.LIGHT_FLASH_TIMED,
876 mLedNotification.notification.ledOnMS,
877 mLedNotification.notification.ledOffMS);
878 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879 }
880
881 // lock on mNotificationList
882 private int indexOfNotificationLocked(String pkg, int id)
883 {
884 ArrayList<NotificationRecord> list = mNotificationList;
885 final int len = list.size();
886 for (int i=0; i<len; i++) {
887 NotificationRecord r = list.get(i);
888 if (r.id == id && r.pkg.equals(pkg)) {
889 return i;
890 }
891 }
892 return -1;
893 }
894
895 // ======================================================================
896 @Override
897 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
898 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
899 != PackageManager.PERMISSION_GRANTED) {
900 pw.println("Permission Denial: can't dump NotificationManager from from pid="
901 + Binder.getCallingPid()
902 + ", uid=" + Binder.getCallingUid());
903 return;
904 }
905
906 pw.println("Current Notification Manager state:");
907
908 int N;
909
910 synchronized (mToastQueue) {
911 N = mToastQueue.size();
912 if (N > 0) {
913 pw.println(" Toast Queue:");
914 for (int i=0; i<N; i++) {
915 mToastQueue.get(i).dump(pw, " ");
916 }
917 pw.println(" ");
918 }
919
920 }
921
922 synchronized (mNotificationList) {
923 N = mNotificationList.size();
924 if (N > 0) {
925 pw.println(" Notification List:");
926 for (int i=0; i<N; i++) {
927 mNotificationList.get(i).dump(pw, " ", mContext);
928 }
929 pw.println(" ");
930 }
931
932 N = mLights.size();
933 if (N > 0) {
934 pw.println(" Lights List:");
935 for (int i=0; i<N; i++) {
936 mLights.get(i).dump(pw, " ", mContext);
937 }
938 pw.println(" ");
939 }
940
941 pw.println(" mSoundNotification=" + mSoundNotification);
942 pw.println(" mSound=" + mSound);
943 pw.println(" mVibrateNotification=" + mVibrateNotification);
944 }
945 }
946}