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