NotificationManagerService.java revision ea8b7d568ad640d464725d1e21efa0c9fd1de953
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.NotificationManager; 29import android.app.PendingIntent; 30import android.app.StatusBarManager; 31import android.content.BroadcastReceiver; 32import android.content.ComponentName; 33import android.content.ContentQueryMap; 34import android.content.ContentResolver; 35import android.content.Context; 36import android.content.Intent; 37import android.content.IntentFilter; 38import android.content.pm.PackageManager; 39import android.content.pm.PackageManager.NameNotFoundException; 40import android.content.res.Resources; 41import android.database.ContentObserver; 42import android.media.AsyncPlayer; 43import android.media.AudioManager; 44import android.net.Uri; 45import android.os.BatteryManager; 46import android.os.Binder; 47import android.os.Handler; 48import android.os.IBinder; 49import android.os.Message; 50import android.os.Power; 51import android.os.RemoteException; 52import android.os.SystemProperties; 53import android.os.Vibrator; 54import android.provider.Settings; 55import android.text.TextUtils; 56import android.util.EventLog; 57import android.util.Log; 58import android.view.accessibility.AccessibilityEvent; 59import android.view.accessibility.AccessibilityManager; 60import android.widget.Toast; 61 62import java.io.FileDescriptor; 63import java.io.PrintWriter; 64import java.util.ArrayList; 65import java.util.Arrays; 66 67class NotificationManagerService extends INotificationManager.Stub 68{ 69 private static final String TAG = "NotificationService"; 70 private static final boolean DBG = false; 71 72 // message codes 73 private static final int MESSAGE_TIMEOUT = 2; 74 75 private static final int LONG_DELAY = 3500; // 3.5 seconds 76 private static final int SHORT_DELAY = 2000; // 2 seconds 77 78 private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; 79 80 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION; 81 82 final Context mContext; 83 final IActivityManager mAm; 84 final IBinder mForegroundToken = new Binder(); 85 86 private WorkerHandler mHandler; 87 private StatusBarService mStatusBarService; 88 private HardwareService mHardware; 89 90 private NotificationRecord mSoundNotification; 91 private AsyncPlayer mSound; 92 private boolean mSystemReady; 93 private int mDisabledNotifications; 94 95 private NotificationRecord mVibrateNotification; 96 private Vibrator mVibrator = new Vibrator(); 97 98 // adb 99 private boolean mUsbConnected; 100 private boolean mAdbEnabled = false; 101 private boolean mAdbNotificationShown = false; 102 private Notification mAdbNotification; 103 104 private ArrayList<NotificationRecord> mNotificationList; 105 106 private ArrayList<ToastRecord> mToastQueue; 107 108 private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); 109 110 private boolean mBatteryCharging; 111 private boolean mBatteryLow; 112 private boolean mBatteryFull; 113 private NotificationRecord mLedNotification; 114 115 private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on 116 private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on 117 private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on 118 private static final int BATTERY_BLINK_ON = 125; 119 private static final int BATTERY_BLINK_OFF = 2875; 120 121 // Tag IDs for EventLog. 122 private static final int EVENT_LOG_ENQUEUE = 2750; 123 private static final int EVENT_LOG_CANCEL = 2751; 124 private static final int EVENT_LOG_CANCEL_ALL = 2752; 125 126 private static String idDebugString(Context baseContext, String packageName, int id) { 127 Context c = null; 128 129 if (packageName != null) { 130 try { 131 c = baseContext.createPackageContext(packageName, 0); 132 } catch (NameNotFoundException e) { 133 c = baseContext; 134 } 135 } else { 136 c = baseContext; 137 } 138 139 String pkg; 140 String type; 141 String name; 142 143 Resources r = c.getResources(); 144 try { 145 return r.getResourceName(id); 146 } catch (Resources.NotFoundException e) { 147 return "<name unknown>"; 148 } 149 } 150 151 private static final class NotificationRecord 152 { 153 String pkg; 154 int id; 155 ITransientNotification callback; 156 int duration; 157 Notification notification; 158 IBinder statusBarKey; 159 160 NotificationRecord(String pkg, int id, Notification notification) 161 { 162 this.pkg = pkg; 163 this.id = id; 164 this.notification = notification; 165 } 166 167 void dump(PrintWriter pw, String prefix, Context baseContext) { 168 pw.println(prefix + this); 169 pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon) 170 + " / " + idDebugString(baseContext, this.pkg, notification.icon)); 171 pw.println(prefix + " contentIntent=" + notification.contentIntent); 172 pw.println(prefix + " deleteIntent=" + notification.deleteIntent); 173 pw.println(prefix + " tickerText=" + notification.tickerText); 174 pw.println(prefix + " contentView=" + notification.contentView); 175 pw.println(prefix + " defaults=0x" + Integer.toHexString(notification.defaults)); 176 pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags)); 177 pw.println(prefix + " sound=" + notification.sound); 178 pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate)); 179 pw.println(prefix + " ledARGB=0x" + Integer.toHexString(notification.ledARGB) 180 + " ledOnMS=" + notification.ledOnMS 181 + " ledOffMS=" + notification.ledOffMS); 182 } 183 184 @Override 185 public final String toString() 186 { 187 return "NotificationRecord{" 188 + Integer.toHexString(System.identityHashCode(this)) 189 + " pkg=" + pkg 190 + " id=" + Integer.toHexString(id) + "}"; 191 } 192 } 193 194 private static final class ToastRecord 195 { 196 final int pid; 197 final String pkg; 198 final ITransientNotification callback; 199 int duration; 200 201 ToastRecord(int pid, String pkg, ITransientNotification callback, int duration) 202 { 203 this.pid = pid; 204 this.pkg = pkg; 205 this.callback = callback; 206 this.duration = duration; 207 } 208 209 void update(int duration) { 210 this.duration = duration; 211 } 212 213 void dump(PrintWriter pw, String prefix) { 214 pw.println(prefix + this); 215 } 216 217 @Override 218 public final String toString() 219 { 220 return "ToastRecord{" 221 + Integer.toHexString(System.identityHashCode(this)) 222 + " pkg=" + pkg 223 + " callback=" + callback 224 + " duration=" + duration; 225 } 226 } 227 228 private StatusBarService.NotificationCallbacks mNotificationCallbacks 229 = new StatusBarService.NotificationCallbacks() { 230 231 public void onSetDisabled(int status) { 232 synchronized (mNotificationList) { 233 mDisabledNotifications = status; 234 if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { 235 // cancel whatever's going on 236 long identity = Binder.clearCallingIdentity(); 237 try { 238 mSound.stop(); 239 } 240 finally { 241 Binder.restoreCallingIdentity(identity); 242 } 243 244 identity = Binder.clearCallingIdentity(); 245 try { 246 mVibrator.cancel(); 247 } 248 finally { 249 Binder.restoreCallingIdentity(identity); 250 } 251 } 252 } 253 } 254 255 public void onClearAll() { 256 cancelAll(); 257 } 258 259 public void onNotificationClick(String pkg, int id) { 260 cancelNotification(pkg, id, Notification.FLAG_AUTO_CANCEL); 261 } 262 263 public void onPanelRevealed() { 264 synchronized (mNotificationList) { 265 // sound 266 mSoundNotification = null; 267 long identity = Binder.clearCallingIdentity(); 268 try { 269 mSound.stop(); 270 } 271 finally { 272 Binder.restoreCallingIdentity(identity); 273 } 274 275 // vibrate 276 mVibrateNotification = null; 277 identity = Binder.clearCallingIdentity(); 278 try { 279 mVibrator.cancel(); 280 } 281 finally { 282 Binder.restoreCallingIdentity(identity); 283 } 284 285 // light 286 mLights.clear(); 287 mLedNotification = null; 288 updateLightsLocked(); 289 } 290 } 291 }; 292 293 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 294 @Override 295 public void onReceive(Context context, Intent intent) { 296 String action = intent.getAction(); 297 298 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { 299 boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0); 300 int level = intent.getIntExtra("level", -1); 301 boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD); 302 int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN); 303 boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90); 304 305 if (batteryCharging != mBatteryCharging || 306 batteryLow != mBatteryLow || 307 batteryFull != mBatteryFull) { 308 mBatteryCharging = batteryCharging; 309 mBatteryLow = batteryLow; 310 mBatteryFull = batteryFull; 311 updateLights(); 312 } 313 } else if (action.equals(Intent.ACTION_UMS_CONNECTED)) { 314 mUsbConnected = true; 315 updateAdbNotification(); 316 } else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) { 317 mUsbConnected = false; 318 updateAdbNotification(); 319 } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) 320 || action.equals(Intent.ACTION_PACKAGE_RESTARTED)) { 321 Uri uri = intent.getData(); 322 if (uri == null) { 323 return; 324 } 325 String pkgName = uri.getSchemeSpecificPart(); 326 if (pkgName == null) { 327 return; 328 } 329 cancelAllNotifications(pkgName); 330 } 331 } 332 }; 333 334 class SettingsObserver extends ContentObserver { 335 SettingsObserver(Handler handler) { 336 super(handler); 337 } 338 339 void observe() { 340 ContentResolver resolver = mContext.getContentResolver(); 341 resolver.registerContentObserver(Settings.Secure.getUriFor( 342 Settings.Secure.ADB_ENABLED), false, this); 343 update(); 344 } 345 346 @Override public void onChange(boolean selfChange) { 347 update(); 348 } 349 350 public void update() { 351 ContentResolver resolver = mContext.getContentResolver(); 352 mAdbEnabled = Settings.Secure.getInt(resolver, 353 Settings.Secure.ADB_ENABLED, 0) != 0; 354 updateAdbNotification(); 355 } 356 } 357 private final SettingsObserver mSettingsObserver; 358 359 NotificationManagerService(Context context, StatusBarService statusBar, 360 HardwareService hardware) 361 { 362 super(); 363 mContext = context; 364 mHardware = hardware; 365 mAm = ActivityManagerNative.getDefault(); 366 mSound = new AsyncPlayer(TAG); 367 mSound.setUsesWakeLock(context); 368 mToastQueue = new ArrayList<ToastRecord>(); 369 mNotificationList = new ArrayList<NotificationRecord>(); 370 mHandler = new WorkerHandler(); 371 mStatusBarService = statusBar; 372 statusBar.setNotificationCallbacks(mNotificationCallbacks); 373 374 // Don't start allowing notifications until the setup wizard has run once. 375 // After that, including subsequent boots, init with notifications turned on. 376 // This works on the first boot because the setup wizard will toggle this 377 // flag at least once and we'll go back to 0 after that. 378 if (0 == Settings.Secure.getInt(mContext.getContentResolver(), 379 Settings.Secure.DEVICE_PROVISIONED, 0)) { 380 mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS; 381 } 382 383 // register for battery changed notifications 384 IntentFilter filter = new IntentFilter(); 385 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 386 filter.addAction(Intent.ACTION_UMS_CONNECTED); 387 filter.addAction(Intent.ACTION_UMS_DISCONNECTED); 388 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 389 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); 390 mContext.registerReceiver(mIntentReceiver, filter); 391 392 mSettingsObserver = new SettingsObserver(mHandler); 393 mSettingsObserver.observe(); 394 } 395 396 void systemReady() { 397 // no beeping until we're basically done booting 398 mSystemReady = true; 399 } 400 401 // Toasts 402 // ============================================================================ 403 public void enqueueToast(String pkg, ITransientNotification callback, int duration) 404 { 405 Log.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration); 406 407 if (pkg == null || callback == null) { 408 Log.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback); 409 return ; 410 } 411 412 synchronized (mToastQueue) { 413 int callingPid = Binder.getCallingPid(); 414 long callingId = Binder.clearCallingIdentity(); 415 try { 416 ToastRecord record; 417 int index = indexOfToastLocked(pkg, callback); 418 // If it's already in the queue, we update it in place, we don't 419 // move it to the end of the queue. 420 if (index >= 0) { 421 record = mToastQueue.get(index); 422 record.update(duration); 423 } else { 424 record = new ToastRecord(callingPid, pkg, callback, duration); 425 mToastQueue.add(record); 426 index = mToastQueue.size() - 1; 427 keepProcessAliveLocked(callingPid); 428 } 429 // If it's at index 0, it's the current toast. It doesn't matter if it's 430 // new or just been updated. Call back and tell it to show itself. 431 // If the callback fails, this will remove it from the list, so don't 432 // assume that it's valid after this. 433 if (index == 0) { 434 showNextToastLocked(); 435 } 436 } finally { 437 Binder.restoreCallingIdentity(callingId); 438 } 439 } 440 } 441 442 public void cancelToast(String pkg, ITransientNotification callback) { 443 Log.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback); 444 445 if (pkg == null || callback == null) { 446 Log.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback); 447 return ; 448 } 449 450 synchronized (mToastQueue) { 451 long callingId = Binder.clearCallingIdentity(); 452 try { 453 int index = indexOfToastLocked(pkg, callback); 454 if (index >= 0) { 455 cancelToastLocked(index); 456 } else { 457 Log.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback); 458 } 459 } finally { 460 Binder.restoreCallingIdentity(callingId); 461 } 462 } 463 } 464 465 private void showNextToastLocked() { 466 ToastRecord record = mToastQueue.get(0); 467 while (record != null) { 468 if (DBG) Log.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); 469 try { 470 record.callback.show(); 471 scheduleTimeoutLocked(record, false); 472 return; 473 } catch (RemoteException e) { 474 Log.w(TAG, "Object died trying to show notification " + record.callback 475 + " in package " + record.pkg); 476 // remove it from the list and let the process die 477 int index = mToastQueue.indexOf(record); 478 if (index >= 0) { 479 mToastQueue.remove(index); 480 } 481 keepProcessAliveLocked(record.pid); 482 if (mToastQueue.size() > 0) { 483 record = mToastQueue.get(0); 484 } else { 485 record = null; 486 } 487 } 488 } 489 } 490 491 private void cancelToastLocked(int index) { 492 ToastRecord record = mToastQueue.get(index); 493 try { 494 record.callback.hide(); 495 } catch (RemoteException e) { 496 Log.w(TAG, "Object died trying to hide notification " + record.callback 497 + " in package " + record.pkg); 498 // don't worry about this, we're about to remove it from 499 // the list anyway 500 } 501 mToastQueue.remove(index); 502 keepProcessAliveLocked(record.pid); 503 if (mToastQueue.size() > 0) { 504 // Show the next one. If the callback fails, this will remove 505 // it from the list, so don't assume that the list hasn't changed 506 // after this point. 507 showNextToastLocked(); 508 } 509 } 510 511 private void scheduleTimeoutLocked(ToastRecord r, boolean immediate) 512 { 513 Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); 514 long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY); 515 mHandler.removeCallbacksAndMessages(r); 516 mHandler.sendMessageDelayed(m, delay); 517 } 518 519 private void handleTimeout(ToastRecord record) 520 { 521 if (DBG) Log.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback); 522 synchronized (mToastQueue) { 523 int index = indexOfToastLocked(record.pkg, record.callback); 524 if (index >= 0) { 525 cancelToastLocked(index); 526 } 527 } 528 } 529 530 // lock on mToastQueue 531 private int indexOfToastLocked(String pkg, ITransientNotification callback) 532 { 533 IBinder cbak = callback.asBinder(); 534 ArrayList<ToastRecord> list = mToastQueue; 535 int len = list.size(); 536 for (int i=0; i<len; i++) { 537 ToastRecord r = list.get(i); 538 if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) { 539 return i; 540 } 541 } 542 return -1; 543 } 544 545 // lock on mToastQueue 546 private void keepProcessAliveLocked(int pid) 547 { 548 int toastCount = 0; // toasts from this pid 549 ArrayList<ToastRecord> list = mToastQueue; 550 int N = list.size(); 551 for (int i=0; i<N; i++) { 552 ToastRecord r = list.get(i); 553 if (r.pid == pid) { 554 toastCount++; 555 } 556 } 557 try { 558 mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0); 559 } catch (RemoteException e) { 560 // Shouldn't happen. 561 } 562 } 563 564 private final class WorkerHandler extends Handler 565 { 566 @Override 567 public void handleMessage(Message msg) 568 { 569 switch (msg.what) 570 { 571 case MESSAGE_TIMEOUT: 572 handleTimeout((ToastRecord)msg.obj); 573 break; 574 } 575 } 576 } 577 578 579 // Notifications 580 // ============================================================================ 581 public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut) 582 { 583 // This conditional is a dirty hack to limit the logging done on 584 // behalf of the download manager without affecting other apps. 585 if (!pkg.equals("com.android.providers.downloads") 586 || Log.isLoggable("DownloadManager", Log.VERBOSE)) { 587 EventLog.writeEvent(EVENT_LOG_ENQUEUE, pkg, id, notification.toString()); 588 } 589 590 if (pkg == null || notification == null) { 591 throw new IllegalArgumentException("null not allowed: pkg=" + pkg 592 + " id=" + id + " notification=" + notification); 593 } 594 if (notification.icon != 0) { 595 if (notification.contentView == null) { 596 throw new IllegalArgumentException("contentView required: pkg=" + pkg 597 + " id=" + id + " notification=" + notification); 598 } 599 if (notification.contentIntent == null) { 600 throw new IllegalArgumentException("contentIntent required: pkg=" + pkg 601 + " id=" + id + " notification=" + notification); 602 } 603 } 604 605 synchronized (mNotificationList) { 606 NotificationRecord r = new NotificationRecord(pkg, id, notification); 607 NotificationRecord old = null; 608 609 int index = indexOfNotificationLocked(pkg, id); 610 if (index < 0) { 611 mNotificationList.add(r); 612 } else { 613 old = mNotificationList.remove(index); 614 mNotificationList.add(index, r); 615 } 616 if (notification.icon != 0) { 617 IconData icon = IconData.makeIcon(null, pkg, notification.icon, 618 notification.iconLevel, 619 notification.number); 620 CharSequence truncatedTicker = notification.tickerText; 621 622 // TODO: make this restriction do something smarter like never fill 623 // more than two screens. "Why would anyone need more than 80 characters." :-/ 624 final int maxTickerLen = 80; 625 if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) { 626 truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen); 627 } 628 629 NotificationData n = new NotificationData(); 630 n.id = id; 631 n.pkg = pkg; 632 n.when = notification.when; 633 n.tickerText = truncatedTicker; 634 n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; 635 if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) { 636 n.clearable = true; 637 } 638 n.contentView = notification.contentView; 639 n.contentIntent = notification.contentIntent; 640 n.deleteIntent = notification.deleteIntent; 641 if (old != null && old.statusBarKey != null) { 642 r.statusBarKey = old.statusBarKey; 643 long identity = Binder.clearCallingIdentity(); 644 try { 645 mStatusBarService.updateIcon(r.statusBarKey, icon, n); 646 } 647 finally { 648 Binder.restoreCallingIdentity(identity); 649 } 650 } else { 651 long identity = Binder.clearCallingIdentity(); 652 try { 653 r.statusBarKey = mStatusBarService.addIcon(icon, n); 654 mHardware.pulseBreathingLight(); 655 } 656 finally { 657 Binder.restoreCallingIdentity(identity); 658 } 659 } 660 661 sendAccessibilityEvent(notification, pkg); 662 663 } else { 664 if (old != null && old.statusBarKey != null) { 665 long identity = Binder.clearCallingIdentity(); 666 try { 667 mStatusBarService.removeIcon(old.statusBarKey); 668 } 669 finally { 670 Binder.restoreCallingIdentity(identity); 671 } 672 } 673 } 674 675 // If we're not supposed to beep, vibrate, etc. then don't. 676 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) 677 && (!(old != null 678 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) 679 && mSystemReady) { 680 // sound 681 final boolean useDefaultSound = 682 (notification.defaults & Notification.DEFAULT_SOUND) != 0; 683 if (useDefaultSound || notification.sound != null) { 684 Uri uri; 685 if (useDefaultSound) { 686 uri = Settings.System.DEFAULT_NOTIFICATION_URI; 687 } else { 688 uri = notification.sound; 689 } 690 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; 691 int audioStreamType; 692 if (notification.audioStreamType >= 0) { 693 audioStreamType = notification.audioStreamType; 694 } else { 695 audioStreamType = DEFAULT_STREAM_TYPE; 696 } 697 mSoundNotification = r; 698 long identity = Binder.clearCallingIdentity(); 699 try { 700 mSound.play(mContext, uri, looping, audioStreamType); 701 } 702 finally { 703 Binder.restoreCallingIdentity(identity); 704 } 705 } 706 707 // vibrate 708 final AudioManager audioManager = (AudioManager) mContext 709 .getSystemService(Context.AUDIO_SERVICE); 710 final boolean useDefaultVibrate = 711 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; 712 if ((useDefaultVibrate || notification.vibrate != null) 713 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) { 714 mVibrateNotification = r; 715 716 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN 717 : notification.vibrate, 718 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1); 719 } 720 } 721 722 // this option doesn't shut off the lights 723 724 // light 725 // the most recent thing gets the light 726 mLights.remove(old); 727 if (mLedNotification == old) { 728 mLedNotification = null; 729 } 730 //Log.i(TAG, "notification.lights=" 731 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0)); 732 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) { 733 mLights.add(r); 734 updateLightsLocked(); 735 } else { 736 if (old != null 737 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) { 738 updateLightsLocked(); 739 } 740 } 741 } 742 743 idOut[0] = id; 744 } 745 746 private void sendAccessibilityEvent(Notification notification, CharSequence packageName) { 747 AccessibilityManager manager = AccessibilityManager.getInstance(mContext); 748 if (!manager.isEnabled()) { 749 return; 750 } 751 752 AccessibilityEvent event = 753 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); 754 event.setPackageName(packageName); 755 event.setClassName(Notification.class.getName()); 756 event.setParcelableData(notification); 757 CharSequence tickerText = notification.tickerText; 758 if (!TextUtils.isEmpty(tickerText)) { 759 event.getText().add(tickerText); 760 } 761 762 manager.sendAccessibilityEvent(event); 763 } 764 765 private void cancelNotificationLocked(NotificationRecord r) { 766 // status bar 767 if (r.notification.icon != 0) { 768 long identity = Binder.clearCallingIdentity(); 769 try { 770 mStatusBarService.removeIcon(r.statusBarKey); 771 } 772 finally { 773 Binder.restoreCallingIdentity(identity); 774 } 775 r.statusBarKey = null; 776 } 777 778 // sound 779 if (mSoundNotification == r) { 780 mSoundNotification = null; 781 long identity = Binder.clearCallingIdentity(); 782 try { 783 mSound.stop(); 784 } 785 finally { 786 Binder.restoreCallingIdentity(identity); 787 } 788 } 789 790 // vibrate 791 if (mVibrateNotification == r) { 792 mVibrateNotification = null; 793 long identity = Binder.clearCallingIdentity(); 794 try { 795 mVibrator.cancel(); 796 } 797 finally { 798 Binder.restoreCallingIdentity(identity); 799 } 800 } 801 802 // light 803 mLights.remove(r); 804 if (mLedNotification == r) { 805 mLedNotification = null; 806 } 807 } 808 809 /** 810 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}. 811 */ 812 private void cancelNotification(String pkg, int id, int mustHaveFlags) { 813 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags); 814 815 synchronized (mNotificationList) { 816 NotificationRecord r = null; 817 818 int index = indexOfNotificationLocked(pkg, id); 819 if (index >= 0) { 820 r = mNotificationList.get(index); 821 822 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) { 823 return; 824 } 825 826 mNotificationList.remove(index); 827 828 cancelNotificationLocked(r); 829 updateLightsLocked(); 830 } 831 } 832 } 833 834 /** 835 * Cancels all notifications from a given package that have all of the 836 * {@code mustHaveFlags}. 837 */ 838 private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) { 839 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags); 840 841 synchronized (mNotificationList) { 842 final int N = mNotificationList.size(); 843 boolean canceledSomething = false; 844 for (int i = N-1; i >= 0; --i) { 845 NotificationRecord r = mNotificationList.get(i); 846 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) { 847 continue; 848 } 849 if (!r.pkg.equals(pkg)) { 850 continue; 851 } 852 mNotificationList.remove(i); 853 cancelNotificationLocked(r); 854 canceledSomething = true; 855 } 856 if (canceledSomething) { 857 updateLightsLocked(); 858 } 859 } 860 } 861 862 863 public void cancelNotification(String pkg, int id) 864 { 865 cancelNotification(pkg, id, 0); 866 } 867 868 public void cancelAllNotifications(String pkg) 869 { 870 cancelAllNotificationsInt(pkg, 0); 871 } 872 873 public void cancelAll() { 874 synchronized (mNotificationList) { 875 final int N = mNotificationList.size(); 876 for (int i=N-1; i>=0; i--) { 877 NotificationRecord r = mNotificationList.get(i); 878 879 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT 880 | Notification.FLAG_NO_CLEAR)) == 0) { 881 if (r.notification.deleteIntent != null) { 882 try { 883 r.notification.deleteIntent.send(); 884 } catch (PendingIntent.CanceledException ex) { 885 // do nothing - there's no relevant way to recover, and 886 // no reason to let this propagate 887 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex); 888 } 889 } 890 mNotificationList.remove(i); 891 cancelNotificationLocked(r); 892 } 893 } 894 895 updateLightsLocked(); 896 } 897 } 898 899 private void updateLights() { 900 synchronized (mNotificationList) { 901 updateLightsLocked(); 902 } 903 } 904 905 // lock on mNotificationList 906 private void updateLightsLocked() 907 { 908 // Battery low always shows, other states only show if charging. 909 if (mBatteryLow) { 910 mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, BATTERY_LOW_ARGB, 911 HardwareService.LIGHT_FLASH_TIMED, BATTERY_BLINK_ON, BATTERY_BLINK_OFF); 912 } else if (mBatteryCharging) { 913 if (mBatteryFull) { 914 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, 915 BATTERY_FULL_ARGB); 916 } else { 917 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, 918 BATTERY_MEDIUM_ARGB); 919 } 920 } else { 921 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY); 922 } 923 924 // handle notification lights 925 if (mLedNotification == null) { 926 // get next notification, if any 927 int n = mLights.size(); 928 if (n > 0) { 929 mLedNotification = mLights.get(n-1); 930 } 931 } 932 if (mLedNotification == null) { 933 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS); 934 } else { 935 mHardware.setLightFlashing_UNCHECKED( 936 HardwareService.LIGHT_ID_NOTIFICATIONS, 937 mLedNotification.notification.ledARGB, 938 HardwareService.LIGHT_FLASH_TIMED, 939 mLedNotification.notification.ledOnMS, 940 mLedNotification.notification.ledOffMS); 941 } 942 } 943 944 // lock on mNotificationList 945 private int indexOfNotificationLocked(String pkg, int id) 946 { 947 ArrayList<NotificationRecord> list = mNotificationList; 948 final int len = list.size(); 949 for (int i=0; i<len; i++) { 950 NotificationRecord r = list.get(i); 951 if (r.id == id && r.pkg.equals(pkg)) { 952 return i; 953 } 954 } 955 return -1; 956 } 957 958 // This is here instead of StatusBarPolicy because it is an important 959 // security feature that we don't want people customizing the platform 960 // to accidentally lose. 961 private void updateAdbNotification() { 962 if (mAdbEnabled && mUsbConnected) { 963 if ("0".equals(SystemProperties.get("persist.adb.notify"))) { 964 return; 965 } 966 if (!mAdbNotificationShown) { 967 NotificationManager notificationManager = (NotificationManager) mContext 968 .getSystemService(Context.NOTIFICATION_SERVICE); 969 if (notificationManager != null) { 970 Resources r = mContext.getResources(); 971 CharSequence title = r.getText( 972 com.android.internal.R.string.adb_active_notification_title); 973 CharSequence message = r.getText( 974 com.android.internal.R.string.adb_active_notification_message); 975 976 if (mAdbNotification == null) { 977 mAdbNotification = new Notification(); 978 mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_warning; 979 mAdbNotification.when = 0; 980 mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT; 981 mAdbNotification.tickerText = title; 982 mAdbNotification.defaults |= Notification.DEFAULT_SOUND; 983 } 984 985 Intent intent = new Intent( 986 Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); 987 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 988 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 989 // Note: we are hard-coding the component because this is 990 // an important security UI that we don't want anyone 991 // intercepting. 992 intent.setComponent(new ComponentName("com.android.settings", 993 "com.android.settings.DevelopmentSettings")); 994 PendingIntent pi = PendingIntent.getActivity(mContext, 0, 995 intent, 0); 996 997 mAdbNotification.setLatestEventInfo(mContext, title, message, pi); 998 999 mAdbNotificationShown = true; 1000 notificationManager.notify( 1001 com.android.internal.R.string.adb_active_notification_title, 1002 mAdbNotification); 1003 } 1004 } 1005 1006 } else if (mAdbNotificationShown) { 1007 NotificationManager notificationManager = (NotificationManager) mContext 1008 .getSystemService(Context.NOTIFICATION_SERVICE); 1009 if (notificationManager != null) { 1010 mAdbNotificationShown = false; 1011 notificationManager.cancel( 1012 com.android.internal.R.string.adb_active_notification_title); 1013 } 1014 } 1015 } 1016 1017 // ====================================================================== 1018 @Override 1019 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1020 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 1021 != PackageManager.PERMISSION_GRANTED) { 1022 pw.println("Permission Denial: can't dump NotificationManager from from pid=" 1023 + Binder.getCallingPid() 1024 + ", uid=" + Binder.getCallingUid()); 1025 return; 1026 } 1027 1028 pw.println("Current Notification Manager state:"); 1029 1030 int N; 1031 1032 synchronized (mToastQueue) { 1033 N = mToastQueue.size(); 1034 if (N > 0) { 1035 pw.println(" Toast Queue:"); 1036 for (int i=0; i<N; i++) { 1037 mToastQueue.get(i).dump(pw, " "); 1038 } 1039 pw.println(" "); 1040 } 1041 1042 } 1043 1044 synchronized (mNotificationList) { 1045 N = mNotificationList.size(); 1046 if (N > 0) { 1047 pw.println(" Notification List:"); 1048 for (int i=0; i<N; i++) { 1049 mNotificationList.get(i).dump(pw, " ", mContext); 1050 } 1051 pw.println(" "); 1052 } 1053 1054 N = mLights.size(); 1055 if (N > 0) { 1056 pw.println(" Lights List:"); 1057 for (int i=0; i<N; i++) { 1058 mLights.get(i).dump(pw, " ", mContext); 1059 } 1060 pw.println(" "); 1061 } 1062 1063 pw.println(" mSoundNotification=" + mSoundNotification); 1064 pw.println(" mSound=" + mSound); 1065 pw.println(" mVibrateNotification=" + mVibrateNotification); 1066 pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications)); 1067 pw.println(" mSystemReady=" + mSystemReady); 1068 } 1069 } 1070} 1071