NotificationManagerService.java revision 75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec
1/* 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 com.android.server.status.IconData; 20import com.android.server.status.NotificationData; 21import com.android.server.status.StatusBarService; 22 23import 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; 37import android.media.AsyncPlayer; 38import android.media.AudioManager; 39import android.net.Uri; 40import android.os.BatteryManager; 41import android.os.Binder; 42import android.os.Handler; 43import android.os.IBinder; 44import android.os.Message; 45import android.os.Power; 46import android.os.RemoteException; 47import android.os.Vibrator; 48import android.provider.Settings; 49import android.text.TextUtils; 50import android.util.EventLog; 51import android.util.Log; 52import android.view.accessibility.AccessibilityEvent; 53import android.view.accessibility.AccessibilityManager; 54import android.widget.Toast; 55 56import java.io.FileDescriptor; 57import java.io.PrintWriter; 58import java.util.ArrayList; 59import java.util.Arrays; 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; 82 private HardwareService mHardware; 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 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; 107 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 315 NotificationManagerService(Context context, StatusBarService statusBar, 316 HardwareService hardware) 317 { 318 super(); 319 mContext = context; 320 mHardware = hardware; 321 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 mHardware.pulseBreathingLight(); 592 } 593 finally { 594 Binder.restoreCallingIdentity(identity); 595 } 596 } 597 598 sendAccessibilityEventTypeNotificationChangedDoCheck(notification, pkg); 599 600 } 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 682 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 702 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 { 845 // 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); 849 } else if (mBatteryCharging) { 850 if (mBatteryFull) { 851 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, 852 BATTERY_FULL_ARGB); 853 } else { 854 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, 855 BATTERY_MEDIUM_ARGB); 856 } 857 } else { 858 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY); 859 } 860 861 // 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); 867 } 868 } 869 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 } 879 } 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} 947