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