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