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