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