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