blob: 696ef317b1e9119b938be5cbfc92f3cd438a61ea [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
svetoslavganov75986cf2009-05-14 22:28:01 -070019import com.android.server.status.IconData;
20import com.android.server.status.NotificationData;
21import com.android.server.status.StatusBarService;
22
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.app.ActivityManagerNative;
24import android.app.IActivityManager;
25import android.app.INotificationManager;
26import android.app.ITransientNotification;
27import android.app.Notification;
Dianne Hackborn1dac2772009-06-26 18:16:48 -070028import android.app.NotificationManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.app.PendingIntent;
30import android.app.StatusBarManager;
31import android.content.BroadcastReceiver;
Dianne Hackborn1dac2772009-06-26 18:16:48 -070032import android.content.ComponentName;
33import android.content.ContentQueryMap;
34import android.content.ContentResolver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.content.Context;
36import android.content.Intent;
37import android.content.IntentFilter;
Dianne Hackbornd8a43f62009-08-17 23:33:56 -070038import android.content.pm.ApplicationInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.content.pm.PackageManager;
40import android.content.pm.PackageManager.NameNotFoundException;
41import android.content.res.Resources;
Dianne Hackborn1dac2772009-06-26 18:16:48 -070042import android.database.ContentObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.media.AsyncPlayer;
svetoslavganov75986cf2009-05-14 22:28:01 -070044import android.media.AudioManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.net.Uri;
46import android.os.BatteryManager;
47import android.os.Binder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.os.IBinder;
50import android.os.Message;
51import android.os.Power;
Dianne Hackbornd8a43f62009-08-17 23:33:56 -070052import android.os.Process;
svetoslavganov75986cf2009-05-14 22:28:01 -070053import android.os.RemoteException;
Mike Lockwooded760372009-07-09 07:07:27 -040054import android.os.SystemProperties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055import android.os.Vibrator;
56import android.provider.Settings;
svetoslavganov75986cf2009-05-14 22:28:01 -070057import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058import android.util.EventLog;
59import android.util.Log;
svetoslavganov75986cf2009-05-14 22:28:01 -070060import android.view.accessibility.AccessibilityEvent;
61import android.view.accessibility.AccessibilityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.widget.Toast;
63
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064import java.io.FileDescriptor;
65import java.io.PrintWriter;
66import java.util.ArrayList;
67import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068
69class NotificationManagerService extends INotificationManager.Stub
70{
71 private static final String TAG = "NotificationService";
72 private static final boolean DBG = false;
73
74 // message codes
75 private static final int MESSAGE_TIMEOUT = 2;
76
77 private static final int LONG_DELAY = 3500; // 3.5 seconds
78 private static final int SHORT_DELAY = 2000; // 2 seconds
79
80 private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
81
82 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
83
84 final Context mContext;
85 final IActivityManager mAm;
86 final IBinder mForegroundToken = new Binder();
87
88 private WorkerHandler mHandler;
89 private StatusBarService mStatusBarService;
The Android Open Source Project10592532009-03-18 17:39:46 -070090 private HardwareService mHardware;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091
92 private NotificationRecord mSoundNotification;
93 private AsyncPlayer mSound;
Joe Onorato30275482009-07-08 17:09:14 -070094 private boolean mSystemReady;
Joe Onorato39f5b6a2009-07-23 12:29:19 -040095 private int mDisabledNotifications;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096
97 private NotificationRecord mVibrateNotification;
98 private Vibrator mVibrator = new Vibrator();
99
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700100 // adb
Mike Lockwoodea8b7d52009-08-04 17:03:15 -0400101 private boolean mUsbConnected;
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700102 private boolean mAdbEnabled = false;
103 private boolean mAdbNotificationShown = false;
104 private Notification mAdbNotification;
105
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 private ArrayList<NotificationRecord> mNotificationList;
107
108 private ArrayList<ToastRecord> mToastQueue;
109
110 private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
111
112 private boolean mBatteryCharging;
113 private boolean mBatteryLow;
114 private boolean mBatteryFull;
115 private NotificationRecord mLedNotification;
svetoslavganov75986cf2009-05-14 22:28:01 -0700116
The Android Open Source Project10592532009-03-18 17:39:46 -0700117 private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on
118 private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on
119 private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on
120 private static final int BATTERY_BLINK_ON = 125;
121 private static final int BATTERY_BLINK_OFF = 2875;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122
123 // Tag IDs for EventLog.
124 private static final int EVENT_LOG_ENQUEUE = 2750;
125 private static final int EVENT_LOG_CANCEL = 2751;
126 private static final int EVENT_LOG_CANCEL_ALL = 2752;
127
128 private static String idDebugString(Context baseContext, String packageName, int id) {
129 Context c = null;
130
131 if (packageName != null) {
132 try {
133 c = baseContext.createPackageContext(packageName, 0);
134 } catch (NameNotFoundException e) {
135 c = baseContext;
136 }
137 } else {
138 c = baseContext;
139 }
140
141 String pkg;
142 String type;
143 String name;
144
145 Resources r = c.getResources();
146 try {
147 return r.getResourceName(id);
148 } catch (Resources.NotFoundException e) {
149 return "<name unknown>";
150 }
151 }
152
153 private static final class NotificationRecord
154 {
155 String pkg;
156 int id;
157 ITransientNotification callback;
158 int duration;
159 Notification notification;
160 IBinder statusBarKey;
161
162 NotificationRecord(String pkg, int id, Notification notification)
163 {
164 this.pkg = pkg;
165 this.id = id;
166 this.notification = notification;
167 }
168
169 void dump(PrintWriter pw, String prefix, Context baseContext) {
170 pw.println(prefix + this);
171 pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon)
172 + " / " + idDebugString(baseContext, this.pkg, notification.icon));
173 pw.println(prefix + " contentIntent=" + notification.contentIntent);
174 pw.println(prefix + " deleteIntent=" + notification.deleteIntent);
175 pw.println(prefix + " tickerText=" + notification.tickerText);
176 pw.println(prefix + " contentView=" + notification.contentView);
177 pw.println(prefix + " defaults=0x" + Integer.toHexString(notification.defaults));
178 pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags));
179 pw.println(prefix + " sound=" + notification.sound);
180 pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
181 pw.println(prefix + " ledARGB=0x" + Integer.toHexString(notification.ledARGB)
182 + " ledOnMS=" + notification.ledOnMS
183 + " ledOffMS=" + notification.ledOffMS);
184 }
185
186 @Override
187 public final String toString()
188 {
189 return "NotificationRecord{"
190 + Integer.toHexString(System.identityHashCode(this))
191 + " pkg=" + pkg
192 + " id=" + Integer.toHexString(id) + "}";
193 }
194 }
195
196 private static final class ToastRecord
197 {
198 final int pid;
199 final String pkg;
200 final ITransientNotification callback;
201 int duration;
202
203 ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
204 {
205 this.pid = pid;
206 this.pkg = pkg;
207 this.callback = callback;
208 this.duration = duration;
209 }
210
211 void update(int duration) {
212 this.duration = duration;
213 }
214
215 void dump(PrintWriter pw, String prefix) {
216 pw.println(prefix + this);
217 }
218
219 @Override
220 public final String toString()
221 {
222 return "ToastRecord{"
223 + Integer.toHexString(System.identityHashCode(this))
224 + " pkg=" + pkg
225 + " callback=" + callback
226 + " duration=" + duration;
227 }
228 }
229
230 private StatusBarService.NotificationCallbacks mNotificationCallbacks
231 = new StatusBarService.NotificationCallbacks() {
232
233 public void onSetDisabled(int status) {
234 synchronized (mNotificationList) {
235 mDisabledNotifications = status;
236 if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
237 // cancel whatever's going on
238 long identity = Binder.clearCallingIdentity();
239 try {
240 mSound.stop();
241 }
242 finally {
243 Binder.restoreCallingIdentity(identity);
244 }
245
246 identity = Binder.clearCallingIdentity();
247 try {
248 mVibrator.cancel();
249 }
250 finally {
251 Binder.restoreCallingIdentity(identity);
252 }
253 }
254 }
255 }
256
257 public void onClearAll() {
258 cancelAll();
259 }
260
261 public void onNotificationClick(String pkg, int id) {
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700262 cancelNotification(pkg, id, Notification.FLAG_AUTO_CANCEL,
263 Notification.FLAG_FOREGROUND_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 }
265
266 public void onPanelRevealed() {
267 synchronized (mNotificationList) {
268 // sound
269 mSoundNotification = null;
270 long identity = Binder.clearCallingIdentity();
271 try {
272 mSound.stop();
273 }
274 finally {
275 Binder.restoreCallingIdentity(identity);
276 }
277
278 // vibrate
279 mVibrateNotification = null;
280 identity = Binder.clearCallingIdentity();
281 try {
282 mVibrator.cancel();
283 }
284 finally {
285 Binder.restoreCallingIdentity(identity);
286 }
287
288 // light
289 mLights.clear();
290 mLedNotification = null;
291 updateLightsLocked();
292 }
293 }
294 };
295
296 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
297 @Override
298 public void onReceive(Context context, Intent intent) {
299 String action = intent.getAction();
300
301 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
302 boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0);
303 int level = intent.getIntExtra("level", -1);
304 boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD);
305 int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
306 boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90);
307
308 if (batteryCharging != mBatteryCharging ||
309 batteryLow != mBatteryLow ||
310 batteryFull != mBatteryFull) {
311 mBatteryCharging = batteryCharging;
312 mBatteryLow = batteryLow;
313 mBatteryFull = batteryFull;
314 updateLights();
315 }
Mike Lockwoodea8b7d52009-08-04 17:03:15 -0400316 } else if (action.equals(Intent.ACTION_UMS_CONNECTED)) {
317 mUsbConnected = true;
318 updateAdbNotification();
319 } else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) {
320 mUsbConnected = false;
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700321 updateAdbNotification();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
323 || action.equals(Intent.ACTION_PACKAGE_RESTARTED)) {
324 Uri uri = intent.getData();
325 if (uri == null) {
326 return;
327 }
328 String pkgName = uri.getSchemeSpecificPart();
329 if (pkgName == null) {
330 return;
331 }
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700332 cancelAllNotificationsInt(pkgName, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 }
334 }
335 };
336
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700337 class SettingsObserver extends ContentObserver {
338 SettingsObserver(Handler handler) {
339 super(handler);
340 }
341
342 void observe() {
343 ContentResolver resolver = mContext.getContentResolver();
344 resolver.registerContentObserver(Settings.Secure.getUriFor(
345 Settings.Secure.ADB_ENABLED), false, this);
346 update();
347 }
348
349 @Override public void onChange(boolean selfChange) {
350 update();
351 }
352
353 public void update() {
354 ContentResolver resolver = mContext.getContentResolver();
355 mAdbEnabled = Settings.Secure.getInt(resolver,
356 Settings.Secure.ADB_ENABLED, 0) != 0;
357 updateAdbNotification();
358 }
359 }
360 private final SettingsObserver mSettingsObserver;
361
The Android Open Source Project10592532009-03-18 17:39:46 -0700362 NotificationManagerService(Context context, StatusBarService statusBar,
363 HardwareService hardware)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 {
365 super();
366 mContext = context;
The Android Open Source Project10592532009-03-18 17:39:46 -0700367 mHardware = hardware;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 mAm = ActivityManagerNative.getDefault();
369 mSound = new AsyncPlayer(TAG);
370 mSound.setUsesWakeLock(context);
371 mToastQueue = new ArrayList<ToastRecord>();
372 mNotificationList = new ArrayList<NotificationRecord>();
373 mHandler = new WorkerHandler();
374 mStatusBarService = statusBar;
375 statusBar.setNotificationCallbacks(mNotificationCallbacks);
376
Joe Onorato39f5b6a2009-07-23 12:29:19 -0400377 // Don't start allowing notifications until the setup wizard has run once.
378 // After that, including subsequent boots, init with notifications turned on.
379 // This works on the first boot because the setup wizard will toggle this
380 // flag at least once and we'll go back to 0 after that.
381 if (0 == Settings.Secure.getInt(mContext.getContentResolver(),
382 Settings.Secure.DEVICE_PROVISIONED, 0)) {
383 mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
384 }
385
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 // register for battery changed notifications
387 IntentFilter filter = new IntentFilter();
388 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
Mike Lockwoodea8b7d52009-08-04 17:03:15 -0400389 filter.addAction(Intent.ACTION_UMS_CONNECTED);
390 filter.addAction(Intent.ACTION_UMS_DISCONNECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
392 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
393 mContext.registerReceiver(mIntentReceiver, filter);
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700394
395 mSettingsObserver = new SettingsObserver(mHandler);
396 mSettingsObserver.observe();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 }
398
Joe Onorato30275482009-07-08 17:09:14 -0700399 void systemReady() {
400 // no beeping until we're basically done booting
401 mSystemReady = true;
402 }
403
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 // Toasts
405 // ============================================================================
406 public void enqueueToast(String pkg, ITransientNotification callback, int duration)
407 {
408 Log.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
409
410 if (pkg == null || callback == null) {
411 Log.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
412 return ;
413 }
414
415 synchronized (mToastQueue) {
416 int callingPid = Binder.getCallingPid();
417 long callingId = Binder.clearCallingIdentity();
418 try {
419 ToastRecord record;
420 int index = indexOfToastLocked(pkg, callback);
421 // If it's already in the queue, we update it in place, we don't
422 // move it to the end of the queue.
423 if (index >= 0) {
424 record = mToastQueue.get(index);
425 record.update(duration);
426 } else {
427 record = new ToastRecord(callingPid, pkg, callback, duration);
428 mToastQueue.add(record);
429 index = mToastQueue.size() - 1;
430 keepProcessAliveLocked(callingPid);
431 }
432 // If it's at index 0, it's the current toast. It doesn't matter if it's
433 // new or just been updated. Call back and tell it to show itself.
434 // If the callback fails, this will remove it from the list, so don't
435 // assume that it's valid after this.
436 if (index == 0) {
437 showNextToastLocked();
438 }
439 } finally {
440 Binder.restoreCallingIdentity(callingId);
441 }
442 }
443 }
444
445 public void cancelToast(String pkg, ITransientNotification callback) {
446 Log.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
447
448 if (pkg == null || callback == null) {
449 Log.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
450 return ;
451 }
452
453 synchronized (mToastQueue) {
454 long callingId = Binder.clearCallingIdentity();
455 try {
456 int index = indexOfToastLocked(pkg, callback);
457 if (index >= 0) {
458 cancelToastLocked(index);
459 } else {
460 Log.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
461 }
462 } finally {
463 Binder.restoreCallingIdentity(callingId);
464 }
465 }
466 }
467
468 private void showNextToastLocked() {
469 ToastRecord record = mToastQueue.get(0);
470 while (record != null) {
471 if (DBG) Log.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
472 try {
473 record.callback.show();
474 scheduleTimeoutLocked(record, false);
475 return;
476 } catch (RemoteException e) {
477 Log.w(TAG, "Object died trying to show notification " + record.callback
478 + " in package " + record.pkg);
479 // remove it from the list and let the process die
480 int index = mToastQueue.indexOf(record);
481 if (index >= 0) {
482 mToastQueue.remove(index);
483 }
484 keepProcessAliveLocked(record.pid);
485 if (mToastQueue.size() > 0) {
486 record = mToastQueue.get(0);
487 } else {
488 record = null;
489 }
490 }
491 }
492 }
493
494 private void cancelToastLocked(int index) {
495 ToastRecord record = mToastQueue.get(index);
496 try {
497 record.callback.hide();
498 } catch (RemoteException e) {
499 Log.w(TAG, "Object died trying to hide notification " + record.callback
500 + " in package " + record.pkg);
501 // don't worry about this, we're about to remove it from
502 // the list anyway
503 }
504 mToastQueue.remove(index);
505 keepProcessAliveLocked(record.pid);
506 if (mToastQueue.size() > 0) {
507 // Show the next one. If the callback fails, this will remove
508 // it from the list, so don't assume that the list hasn't changed
509 // after this point.
510 showNextToastLocked();
511 }
512 }
513
514 private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
515 {
516 Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
517 long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
518 mHandler.removeCallbacksAndMessages(r);
519 mHandler.sendMessageDelayed(m, delay);
520 }
521
522 private void handleTimeout(ToastRecord record)
523 {
524 if (DBG) Log.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
525 synchronized (mToastQueue) {
526 int index = indexOfToastLocked(record.pkg, record.callback);
527 if (index >= 0) {
528 cancelToastLocked(index);
529 }
530 }
531 }
532
533 // lock on mToastQueue
534 private int indexOfToastLocked(String pkg, ITransientNotification callback)
535 {
536 IBinder cbak = callback.asBinder();
537 ArrayList<ToastRecord> list = mToastQueue;
538 int len = list.size();
539 for (int i=0; i<len; i++) {
540 ToastRecord r = list.get(i);
541 if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
542 return i;
543 }
544 }
545 return -1;
546 }
547
548 // lock on mToastQueue
549 private void keepProcessAliveLocked(int pid)
550 {
551 int toastCount = 0; // toasts from this pid
552 ArrayList<ToastRecord> list = mToastQueue;
553 int N = list.size();
554 for (int i=0; i<N; i++) {
555 ToastRecord r = list.get(i);
556 if (r.pid == pid) {
557 toastCount++;
558 }
559 }
560 try {
561 mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
562 } catch (RemoteException e) {
563 // Shouldn't happen.
564 }
565 }
566
567 private final class WorkerHandler extends Handler
568 {
569 @Override
570 public void handleMessage(Message msg)
571 {
572 switch (msg.what)
573 {
574 case MESSAGE_TIMEOUT:
575 handleTimeout((ToastRecord)msg.obj);
576 break;
577 }
578 }
579 }
580
581
582 // Notifications
583 // ============================================================================
584 public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
585 {
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700586 checkIncomingCall(pkg);
587
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 // This conditional is a dirty hack to limit the logging done on
589 // behalf of the download manager without affecting other apps.
590 if (!pkg.equals("com.android.providers.downloads")
591 || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
592 EventLog.writeEvent(EVENT_LOG_ENQUEUE, pkg, id, notification.toString());
593 }
594
595 if (pkg == null || notification == null) {
596 throw new IllegalArgumentException("null not allowed: pkg=" + pkg
597 + " id=" + id + " notification=" + notification);
598 }
599 if (notification.icon != 0) {
600 if (notification.contentView == null) {
601 throw new IllegalArgumentException("contentView required: pkg=" + pkg
602 + " id=" + id + " notification=" + notification);
603 }
604 if (notification.contentIntent == null) {
605 throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
606 + " id=" + id + " notification=" + notification);
607 }
608 }
609
610 synchronized (mNotificationList) {
611 NotificationRecord r = new NotificationRecord(pkg, id, notification);
612 NotificationRecord old = null;
613
614 int index = indexOfNotificationLocked(pkg, id);
615 if (index < 0) {
616 mNotificationList.add(r);
617 } else {
618 old = mNotificationList.remove(index);
619 mNotificationList.add(index, r);
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700620 // Make sure we don't lose the foreground service state.
621 if (old != null) {
622 notification.flags |=
623 old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
624 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 }
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700626
627 // Ensure if this is a foreground service that the proper additional
628 // flags are set.
629 if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
630 notification.flags |= Notification.FLAG_ONGOING_EVENT
631 | Notification.FLAG_NO_CLEAR;
632 }
633
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634 if (notification.icon != 0) {
635 IconData icon = IconData.makeIcon(null, pkg, notification.icon,
636 notification.iconLevel,
637 notification.number);
638 CharSequence truncatedTicker = notification.tickerText;
639
640 // TODO: make this restriction do something smarter like never fill
641 // more than two screens. "Why would anyone need more than 80 characters." :-/
642 final int maxTickerLen = 80;
643 if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
644 truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
645 }
646
647 NotificationData n = new NotificationData();
648 n.id = id;
649 n.pkg = pkg;
650 n.when = notification.when;
651 n.tickerText = truncatedTicker;
652 n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
653 if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
654 n.clearable = true;
655 }
656 n.contentView = notification.contentView;
657 n.contentIntent = notification.contentIntent;
658 n.deleteIntent = notification.deleteIntent;
659 if (old != null && old.statusBarKey != null) {
660 r.statusBarKey = old.statusBarKey;
661 long identity = Binder.clearCallingIdentity();
662 try {
663 mStatusBarService.updateIcon(r.statusBarKey, icon, n);
664 }
665 finally {
666 Binder.restoreCallingIdentity(identity);
667 }
668 } else {
669 long identity = Binder.clearCallingIdentity();
670 try {
671 r.statusBarKey = mStatusBarService.addIcon(icon, n);
Joe Onoratoc1e84462009-03-24 19:29:20 -0700672 mHardware.pulseBreathingLight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 }
674 finally {
675 Binder.restoreCallingIdentity(identity);
676 }
677 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700678
Joe Onorato30275482009-07-08 17:09:14 -0700679 sendAccessibilityEvent(notification, pkg);
svetoslavganov75986cf2009-05-14 22:28:01 -0700680
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681 } else {
682 if (old != null && old.statusBarKey != null) {
683 long identity = Binder.clearCallingIdentity();
684 try {
685 mStatusBarService.removeIcon(old.statusBarKey);
686 }
687 finally {
688 Binder.restoreCallingIdentity(identity);
689 }
690 }
691 }
692
693 // If we're not supposed to beep, vibrate, etc. then don't.
694 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
695 && (!(old != null
Joe Onorato30275482009-07-08 17:09:14 -0700696 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
697 && mSystemReady) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 // sound
699 final boolean useDefaultSound =
700 (notification.defaults & Notification.DEFAULT_SOUND) != 0;
701 if (useDefaultSound || notification.sound != null) {
702 Uri uri;
703 if (useDefaultSound) {
704 uri = Settings.System.DEFAULT_NOTIFICATION_URI;
705 } else {
706 uri = notification.sound;
707 }
708 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
709 int audioStreamType;
710 if (notification.audioStreamType >= 0) {
711 audioStreamType = notification.audioStreamType;
712 } else {
713 audioStreamType = DEFAULT_STREAM_TYPE;
714 }
715 mSoundNotification = r;
716 long identity = Binder.clearCallingIdentity();
717 try {
718 mSound.play(mContext, uri, looping, audioStreamType);
719 }
720 finally {
721 Binder.restoreCallingIdentity(identity);
722 }
723 }
724
725 // vibrate
726 final AudioManager audioManager = (AudioManager) mContext
727 .getSystemService(Context.AUDIO_SERVICE);
728 final boolean useDefaultVibrate =
729 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
730 if ((useDefaultVibrate || notification.vibrate != null)
731 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
732 mVibrateNotification = r;
733
734 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
735 : notification.vibrate,
736 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
737 }
738 }
739
740 // this option doesn't shut off the lights
741
742 // light
743 // the most recent thing gets the light
744 mLights.remove(old);
745 if (mLedNotification == old) {
746 mLedNotification = null;
747 }
748 //Log.i(TAG, "notification.lights="
749 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
750 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
751 mLights.add(r);
752 updateLightsLocked();
753 } else {
754 if (old != null
755 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
756 updateLightsLocked();
757 }
758 }
759 }
760
761 idOut[0] = id;
762 }
763
Joe Onorato30275482009-07-08 17:09:14 -0700764 private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700765 AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
766 if (!manager.isEnabled()) {
767 return;
768 }
769
770 AccessibilityEvent event =
771 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
772 event.setPackageName(packageName);
773 event.setClassName(Notification.class.getName());
774 event.setParcelableData(notification);
775 CharSequence tickerText = notification.tickerText;
776 if (!TextUtils.isEmpty(tickerText)) {
777 event.getText().add(tickerText);
778 }
779
780 manager.sendAccessibilityEvent(event);
781 }
782
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 private void cancelNotificationLocked(NotificationRecord r) {
784 // status bar
785 if (r.notification.icon != 0) {
786 long identity = Binder.clearCallingIdentity();
787 try {
788 mStatusBarService.removeIcon(r.statusBarKey);
789 }
790 finally {
791 Binder.restoreCallingIdentity(identity);
792 }
793 r.statusBarKey = null;
794 }
795
796 // sound
797 if (mSoundNotification == r) {
798 mSoundNotification = null;
799 long identity = Binder.clearCallingIdentity();
800 try {
801 mSound.stop();
802 }
803 finally {
804 Binder.restoreCallingIdentity(identity);
805 }
806 }
807
808 // vibrate
809 if (mVibrateNotification == r) {
810 mVibrateNotification = null;
811 long identity = Binder.clearCallingIdentity();
812 try {
813 mVibrator.cancel();
814 }
815 finally {
816 Binder.restoreCallingIdentity(identity);
817 }
818 }
819
820 // light
821 mLights.remove(r);
822 if (mLedNotification == r) {
823 mLedNotification = null;
824 }
825 }
826
827 /**
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700828 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
829 * and none of the {@code mustNotHaveFlags}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 */
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700831 private void cancelNotification(String pkg, int id, int mustHaveFlags,
832 int mustNotHaveFlags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
834
835 synchronized (mNotificationList) {
836 NotificationRecord r = null;
837
838 int index = indexOfNotificationLocked(pkg, id);
839 if (index >= 0) {
840 r = mNotificationList.get(index);
841
842 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
843 return;
844 }
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700845 if ((r.notification.flags & mustNotHaveFlags) != 0) {
846 return;
847 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848
849 mNotificationList.remove(index);
850
851 cancelNotificationLocked(r);
852 updateLightsLocked();
853 }
854 }
855 }
856
857 /**
858 * Cancels all notifications from a given package that have all of the
859 * {@code mustHaveFlags}.
860 */
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700861 void cancelAllNotificationsInt(String pkg, int mustHaveFlags,
862 int mustNotHaveFlags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
864
865 synchronized (mNotificationList) {
866 final int N = mNotificationList.size();
867 boolean canceledSomething = false;
868 for (int i = N-1; i >= 0; --i) {
869 NotificationRecord r = mNotificationList.get(i);
870 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
871 continue;
872 }
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700873 if ((r.notification.flags & mustNotHaveFlags) != 0) {
874 continue;
875 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876 if (!r.pkg.equals(pkg)) {
877 continue;
878 }
879 mNotificationList.remove(i);
880 cancelNotificationLocked(r);
881 canceledSomething = true;
882 }
883 if (canceledSomething) {
884 updateLightsLocked();
885 }
886 }
887 }
888
889
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700890 public void cancelNotification(String pkg, int id) {
891 checkIncomingCall(pkg);
892 // Don't allow client applications to cancel foreground service notis.
893 cancelNotification(pkg, id, 0,
894 Binder.getCallingUid() == Process.SYSTEM_UID
895 ? 0 : Notification.FLAG_FOREGROUND_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800896 }
897
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700898 public void cancelAllNotifications(String pkg) {
899 checkIncomingCall(pkg);
900
901 // Calling from user space, don't allow the canceling of actively
902 // running foreground services.
903 cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800904 }
905
Dianne Hackbornd8a43f62009-08-17 23:33:56 -0700906 void checkIncomingCall(String pkg) {
907 int uid = Binder.getCallingUid();
908 if (uid == Process.SYSTEM_UID || uid == 0) {
909 return;
910 }
911 try {
912 ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(
913 pkg, 0);
914 if (ai.uid != uid) {
915 throw new SecurityException("Calling uid " + uid + " gave package"
916 + pkg + " which is owned by uid " + ai.uid);
917 }
918 } catch (PackageManager.NameNotFoundException e) {
919 throw new SecurityException("Unknown package " + pkg);
920 }
921 }
922
923 void cancelAll() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 synchronized (mNotificationList) {
925 final int N = mNotificationList.size();
926 for (int i=N-1; i>=0; i--) {
927 NotificationRecord r = mNotificationList.get(i);
928
929 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
930 | Notification.FLAG_NO_CLEAR)) == 0) {
931 if (r.notification.deleteIntent != null) {
932 try {
933 r.notification.deleteIntent.send();
934 } catch (PendingIntent.CanceledException ex) {
935 // do nothing - there's no relevant way to recover, and
936 // no reason to let this propagate
937 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
938 }
939 }
940 mNotificationList.remove(i);
941 cancelNotificationLocked(r);
942 }
943 }
944
945 updateLightsLocked();
946 }
947 }
948
949 private void updateLights() {
950 synchronized (mNotificationList) {
951 updateLightsLocked();
952 }
953 }
954
955 // lock on mNotificationList
956 private void updateLightsLocked()
957 {
The Android Open Source Project10592532009-03-18 17:39:46 -0700958 // Battery low always shows, other states only show if charging.
959 if (mBatteryLow) {
Mike Lockwood445f4302009-09-04 11:06:46 -0400960 if (mBatteryCharging) {
961 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
962 BATTERY_LOW_ARGB);
963 } else {
964 // Flash when battery is low and not charging
965 mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
966 BATTERY_LOW_ARGB, HardwareService.LIGHT_FLASH_TIMED,
967 BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
968 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 } else if (mBatteryCharging) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700970 if (mBatteryFull) {
971 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
972 BATTERY_FULL_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700974 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
975 BATTERY_MEDIUM_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800976 }
977 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700978 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY);
979 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800980
The Android Open Source Project10592532009-03-18 17:39:46 -0700981 // handle notification lights
982 if (mLedNotification == null) {
983 // get next notification, if any
984 int n = mLights.size();
985 if (n > 0) {
986 mLedNotification = mLights.get(n-1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 }
988 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700989 if (mLedNotification == null) {
990 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS);
991 } else {
992 mHardware.setLightFlashing_UNCHECKED(
993 HardwareService.LIGHT_ID_NOTIFICATIONS,
994 mLedNotification.notification.ledARGB,
995 HardwareService.LIGHT_FLASH_TIMED,
996 mLedNotification.notification.ledOnMS,
997 mLedNotification.notification.ledOffMS);
998 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800999 }
1000
1001 // lock on mNotificationList
1002 private int indexOfNotificationLocked(String pkg, int id)
1003 {
1004 ArrayList<NotificationRecord> list = mNotificationList;
1005 final int len = list.size();
1006 for (int i=0; i<len; i++) {
1007 NotificationRecord r = list.get(i);
1008 if (r.id == id && r.pkg.equals(pkg)) {
1009 return i;
1010 }
1011 }
1012 return -1;
1013 }
1014
Dianne Hackborn1dac2772009-06-26 18:16:48 -07001015 // This is here instead of StatusBarPolicy because it is an important
1016 // security feature that we don't want people customizing the platform
1017 // to accidentally lose.
1018 private void updateAdbNotification() {
Mike Lockwoodea8b7d52009-08-04 17:03:15 -04001019 if (mAdbEnabled && mUsbConnected) {
Mike Lockwooded760372009-07-09 07:07:27 -04001020 if ("0".equals(SystemProperties.get("persist.adb.notify"))) {
1021 return;
1022 }
Dianne Hackborn1dac2772009-06-26 18:16:48 -07001023 if (!mAdbNotificationShown) {
1024 NotificationManager notificationManager = (NotificationManager) mContext
1025 .getSystemService(Context.NOTIFICATION_SERVICE);
1026 if (notificationManager != null) {
1027 Resources r = mContext.getResources();
1028 CharSequence title = r.getText(
1029 com.android.internal.R.string.adb_active_notification_title);
1030 CharSequence message = r.getText(
1031 com.android.internal.R.string.adb_active_notification_message);
1032
1033 if (mAdbNotification == null) {
1034 mAdbNotification = new Notification();
1035 mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_warning;
1036 mAdbNotification.when = 0;
1037 mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT;
1038 mAdbNotification.tickerText = title;
1039 mAdbNotification.defaults |= Notification.DEFAULT_SOUND;
1040 }
1041
1042 Intent intent = new Intent(
1043 Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
1044 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1045 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1046 // Note: we are hard-coding the component because this is
1047 // an important security UI that we don't want anyone
1048 // intercepting.
1049 intent.setComponent(new ComponentName("com.android.settings",
1050 "com.android.settings.DevelopmentSettings"));
1051 PendingIntent pi = PendingIntent.getActivity(mContext, 0,
1052 intent, 0);
1053
1054 mAdbNotification.setLatestEventInfo(mContext, title, message, pi);
1055
1056 mAdbNotificationShown = true;
1057 notificationManager.notify(
1058 com.android.internal.R.string.adb_active_notification_title,
1059 mAdbNotification);
1060 }
1061 }
1062
1063 } else if (mAdbNotificationShown) {
1064 NotificationManager notificationManager = (NotificationManager) mContext
1065 .getSystemService(Context.NOTIFICATION_SERVICE);
1066 if (notificationManager != null) {
1067 mAdbNotificationShown = false;
1068 notificationManager.cancel(
1069 com.android.internal.R.string.adb_active_notification_title);
1070 }
1071 }
1072 }
1073
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001074 // ======================================================================
1075 @Override
1076 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1077 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1078 != PackageManager.PERMISSION_GRANTED) {
1079 pw.println("Permission Denial: can't dump NotificationManager from from pid="
1080 + Binder.getCallingPid()
1081 + ", uid=" + Binder.getCallingUid());
1082 return;
1083 }
1084
1085 pw.println("Current Notification Manager state:");
1086
1087 int N;
1088
1089 synchronized (mToastQueue) {
1090 N = mToastQueue.size();
1091 if (N > 0) {
1092 pw.println(" Toast Queue:");
1093 for (int i=0; i<N; i++) {
1094 mToastQueue.get(i).dump(pw, " ");
1095 }
1096 pw.println(" ");
1097 }
1098
1099 }
1100
1101 synchronized (mNotificationList) {
1102 N = mNotificationList.size();
1103 if (N > 0) {
1104 pw.println(" Notification List:");
1105 for (int i=0; i<N; i++) {
1106 mNotificationList.get(i).dump(pw, " ", mContext);
1107 }
1108 pw.println(" ");
1109 }
1110
1111 N = mLights.size();
1112 if (N > 0) {
1113 pw.println(" Lights List:");
1114 for (int i=0; i<N; i++) {
1115 mLights.get(i).dump(pw, " ", mContext);
1116 }
1117 pw.println(" ");
1118 }
1119
1120 pw.println(" mSoundNotification=" + mSoundNotification);
1121 pw.println(" mSound=" + mSound);
1122 pw.println(" mVibrateNotification=" + mVibrateNotification);
Joe Onorato39f5b6a2009-07-23 12:29:19 -04001123 pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
1124 pw.println(" mSystemReady=" + mSystemReady);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001125 }
1126 }
1127}