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