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