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