blob: aac7124b0c4f6ad211d809af48bfd8877efb5926 [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;
38import android.content.pm.PackageManager;
39import android.content.pm.PackageManager.NameNotFoundException;
40import android.content.res.Resources;
Dianne Hackborn1dac2772009-06-26 18:16:48 -070041import android.database.ContentObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.media.AsyncPlayer;
svetoslavganov75986cf2009-05-14 22:28:01 -070043import android.media.AudioManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.net.Uri;
45import android.os.BatteryManager;
46import android.os.Binder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import android.os.IBinder;
49import android.os.Message;
50import android.os.Power;
svetoslavganov75986cf2009-05-14 22:28:01 -070051import android.os.RemoteException;
Mike Lockwooded760372009-07-09 07:07:27 -040052import android.os.SystemProperties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import android.os.Vibrator;
54import android.provider.Settings;
svetoslavganov75986cf2009-05-14 22:28:01 -070055import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056import android.util.EventLog;
57import android.util.Log;
svetoslavganov75986cf2009-05-14 22:28:01 -070058import android.view.accessibility.AccessibilityEvent;
59import android.view.accessibility.AccessibilityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import android.widget.Toast;
61
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import java.io.FileDescriptor;
63import java.io.PrintWriter;
64import java.util.ArrayList;
65import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066
67class NotificationManagerService extends INotificationManager.Stub
68{
69 private static final String TAG = "NotificationService";
70 private static final boolean DBG = false;
71
72 // message codes
73 private static final int MESSAGE_TIMEOUT = 2;
74
75 private static final int LONG_DELAY = 3500; // 3.5 seconds
76 private static final int SHORT_DELAY = 2000; // 2 seconds
77
78 private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
79
80 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
81
82 final Context mContext;
83 final IActivityManager mAm;
84 final IBinder mForegroundToken = new Binder();
85
86 private WorkerHandler mHandler;
87 private StatusBarService mStatusBarService;
The Android Open Source Project10592532009-03-18 17:39:46 -070088 private HardwareService mHardware;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089
90 private NotificationRecord mSoundNotification;
91 private AsyncPlayer mSound;
Joe Onorato30275482009-07-08 17:09:14 -070092 private boolean mSystemReady;
Joe Onorato39f5b6a2009-07-23 12:29:19 -040093 private int mDisabledNotifications;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094
95 private NotificationRecord mVibrateNotification;
96 private Vibrator mVibrator = new Vibrator();
97
Dianne Hackborn1dac2772009-06-26 18:16:48 -070098 // adb
99 private int mBatteryPlugged;
100 private boolean mAdbEnabled = false;
101 private boolean mAdbNotificationShown = false;
102 private Notification mAdbNotification;
103
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 private ArrayList<NotificationRecord> mNotificationList;
105
106 private ArrayList<ToastRecord> mToastQueue;
107
108 private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
109
110 private boolean mBatteryCharging;
111 private boolean mBatteryLow;
112 private boolean mBatteryFull;
113 private NotificationRecord mLedNotification;
svetoslavganov75986cf2009-05-14 22:28:01 -0700114
The Android Open Source Project10592532009-03-18 17:39:46 -0700115 private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on
116 private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on
117 private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on
118 private static final int BATTERY_BLINK_ON = 125;
119 private static final int BATTERY_BLINK_OFF = 2875;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120
121 // Tag IDs for EventLog.
122 private static final int EVENT_LOG_ENQUEUE = 2750;
123 private static final int EVENT_LOG_CANCEL = 2751;
124 private static final int EVENT_LOG_CANCEL_ALL = 2752;
125
126 private static String idDebugString(Context baseContext, String packageName, int id) {
127 Context c = null;
128
129 if (packageName != null) {
130 try {
131 c = baseContext.createPackageContext(packageName, 0);
132 } catch (NameNotFoundException e) {
133 c = baseContext;
134 }
135 } else {
136 c = baseContext;
137 }
138
139 String pkg;
140 String type;
141 String name;
142
143 Resources r = c.getResources();
144 try {
145 return r.getResourceName(id);
146 } catch (Resources.NotFoundException e) {
147 return "<name unknown>";
148 }
149 }
150
151 private static final class NotificationRecord
152 {
153 String pkg;
154 int id;
155 ITransientNotification callback;
156 int duration;
157 Notification notification;
158 IBinder statusBarKey;
159
160 NotificationRecord(String pkg, int id, Notification notification)
161 {
162 this.pkg = pkg;
163 this.id = id;
164 this.notification = notification;
165 }
166
167 void dump(PrintWriter pw, String prefix, Context baseContext) {
168 pw.println(prefix + this);
169 pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon)
170 + " / " + idDebugString(baseContext, this.pkg, notification.icon));
171 pw.println(prefix + " contentIntent=" + notification.contentIntent);
172 pw.println(prefix + " deleteIntent=" + notification.deleteIntent);
173 pw.println(prefix + " tickerText=" + notification.tickerText);
174 pw.println(prefix + " contentView=" + notification.contentView);
175 pw.println(prefix + " defaults=0x" + Integer.toHexString(notification.defaults));
176 pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags));
177 pw.println(prefix + " sound=" + notification.sound);
178 pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
179 pw.println(prefix + " ledARGB=0x" + Integer.toHexString(notification.ledARGB)
180 + " ledOnMS=" + notification.ledOnMS
181 + " ledOffMS=" + notification.ledOffMS);
182 }
183
184 @Override
185 public final String toString()
186 {
187 return "NotificationRecord{"
188 + Integer.toHexString(System.identityHashCode(this))
189 + " pkg=" + pkg
190 + " id=" + Integer.toHexString(id) + "}";
191 }
192 }
193
194 private static final class ToastRecord
195 {
196 final int pid;
197 final String pkg;
198 final ITransientNotification callback;
199 int duration;
200
201 ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
202 {
203 this.pid = pid;
204 this.pkg = pkg;
205 this.callback = callback;
206 this.duration = duration;
207 }
208
209 void update(int duration) {
210 this.duration = duration;
211 }
212
213 void dump(PrintWriter pw, String prefix) {
214 pw.println(prefix + this);
215 }
216
217 @Override
218 public final String toString()
219 {
220 return "ToastRecord{"
221 + Integer.toHexString(System.identityHashCode(this))
222 + " pkg=" + pkg
223 + " callback=" + callback
224 + " duration=" + duration;
225 }
226 }
227
228 private StatusBarService.NotificationCallbacks mNotificationCallbacks
229 = new StatusBarService.NotificationCallbacks() {
230
231 public void onSetDisabled(int status) {
232 synchronized (mNotificationList) {
233 mDisabledNotifications = status;
234 if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
235 // cancel whatever's going on
236 long identity = Binder.clearCallingIdentity();
237 try {
238 mSound.stop();
239 }
240 finally {
241 Binder.restoreCallingIdentity(identity);
242 }
243
244 identity = Binder.clearCallingIdentity();
245 try {
246 mVibrator.cancel();
247 }
248 finally {
249 Binder.restoreCallingIdentity(identity);
250 }
251 }
252 }
253 }
254
255 public void onClearAll() {
256 cancelAll();
257 }
258
259 public void onNotificationClick(String pkg, int id) {
260 cancelNotification(pkg, id, Notification.FLAG_AUTO_CANCEL);
261 }
262
263 public void onPanelRevealed() {
264 synchronized (mNotificationList) {
265 // sound
266 mSoundNotification = null;
267 long identity = Binder.clearCallingIdentity();
268 try {
269 mSound.stop();
270 }
271 finally {
272 Binder.restoreCallingIdentity(identity);
273 }
274
275 // vibrate
276 mVibrateNotification = null;
277 identity = Binder.clearCallingIdentity();
278 try {
279 mVibrator.cancel();
280 }
281 finally {
282 Binder.restoreCallingIdentity(identity);
283 }
284
285 // light
286 mLights.clear();
287 mLedNotification = null;
288 updateLightsLocked();
289 }
290 }
291 };
292
293 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
294 @Override
295 public void onReceive(Context context, Intent intent) {
296 String action = intent.getAction();
297
298 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
299 boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0);
300 int level = intent.getIntExtra("level", -1);
301 boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD);
302 int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
303 boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90);
304
305 if (batteryCharging != mBatteryCharging ||
306 batteryLow != mBatteryLow ||
307 batteryFull != mBatteryFull) {
308 mBatteryCharging = batteryCharging;
309 mBatteryLow = batteryLow;
310 mBatteryFull = batteryFull;
311 updateLights();
312 }
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700313
314 mBatteryPlugged = intent.getIntExtra("plugged", 0);
315 updateAdbNotification();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316 } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
317 || action.equals(Intent.ACTION_PACKAGE_RESTARTED)) {
318 Uri uri = intent.getData();
319 if (uri == null) {
320 return;
321 }
322 String pkgName = uri.getSchemeSpecificPart();
323 if (pkgName == null) {
324 return;
325 }
326 cancelAllNotifications(pkgName);
327 }
328 }
329 };
330
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700331 class SettingsObserver extends ContentObserver {
332 SettingsObserver(Handler handler) {
333 super(handler);
334 }
335
336 void observe() {
337 ContentResolver resolver = mContext.getContentResolver();
338 resolver.registerContentObserver(Settings.Secure.getUriFor(
339 Settings.Secure.ADB_ENABLED), false, this);
340 update();
341 }
342
343 @Override public void onChange(boolean selfChange) {
344 update();
345 }
346
347 public void update() {
348 ContentResolver resolver = mContext.getContentResolver();
349 mAdbEnabled = Settings.Secure.getInt(resolver,
350 Settings.Secure.ADB_ENABLED, 0) != 0;
351 updateAdbNotification();
352 }
353 }
354 private final SettingsObserver mSettingsObserver;
355
The Android Open Source Project10592532009-03-18 17:39:46 -0700356 NotificationManagerService(Context context, StatusBarService statusBar,
357 HardwareService hardware)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 {
359 super();
360 mContext = context;
The Android Open Source Project10592532009-03-18 17:39:46 -0700361 mHardware = hardware;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 mAm = ActivityManagerNative.getDefault();
363 mSound = new AsyncPlayer(TAG);
364 mSound.setUsesWakeLock(context);
365 mToastQueue = new ArrayList<ToastRecord>();
366 mNotificationList = new ArrayList<NotificationRecord>();
367 mHandler = new WorkerHandler();
368 mStatusBarService = statusBar;
369 statusBar.setNotificationCallbacks(mNotificationCallbacks);
370
Joe Onorato39f5b6a2009-07-23 12:29:19 -0400371 // Don't start allowing notifications until the setup wizard has run once.
372 // After that, including subsequent boots, init with notifications turned on.
373 // This works on the first boot because the setup wizard will toggle this
374 // flag at least once and we'll go back to 0 after that.
375 if (0 == Settings.Secure.getInt(mContext.getContentResolver(),
376 Settings.Secure.DEVICE_PROVISIONED, 0)) {
377 mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
378 }
379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 // register for battery changed notifications
381 IntentFilter filter = new IntentFilter();
382 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
383 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
384 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
385 mContext.registerReceiver(mIntentReceiver, filter);
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700386
387 mSettingsObserver = new SettingsObserver(mHandler);
388 mSettingsObserver.observe();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 }
390
Joe Onorato30275482009-07-08 17:09:14 -0700391 void systemReady() {
392 // no beeping until we're basically done booting
393 mSystemReady = true;
394 }
395
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 // Toasts
397 // ============================================================================
398 public void enqueueToast(String pkg, ITransientNotification callback, int duration)
399 {
400 Log.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
401
402 if (pkg == null || callback == null) {
403 Log.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
404 return ;
405 }
406
407 synchronized (mToastQueue) {
408 int callingPid = Binder.getCallingPid();
409 long callingId = Binder.clearCallingIdentity();
410 try {
411 ToastRecord record;
412 int index = indexOfToastLocked(pkg, callback);
413 // If it's already in the queue, we update it in place, we don't
414 // move it to the end of the queue.
415 if (index >= 0) {
416 record = mToastQueue.get(index);
417 record.update(duration);
418 } else {
419 record = new ToastRecord(callingPid, pkg, callback, duration);
420 mToastQueue.add(record);
421 index = mToastQueue.size() - 1;
422 keepProcessAliveLocked(callingPid);
423 }
424 // If it's at index 0, it's the current toast. It doesn't matter if it's
425 // new or just been updated. Call back and tell it to show itself.
426 // If the callback fails, this will remove it from the list, so don't
427 // assume that it's valid after this.
428 if (index == 0) {
429 showNextToastLocked();
430 }
431 } finally {
432 Binder.restoreCallingIdentity(callingId);
433 }
434 }
435 }
436
437 public void cancelToast(String pkg, ITransientNotification callback) {
438 Log.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
439
440 if (pkg == null || callback == null) {
441 Log.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
442 return ;
443 }
444
445 synchronized (mToastQueue) {
446 long callingId = Binder.clearCallingIdentity();
447 try {
448 int index = indexOfToastLocked(pkg, callback);
449 if (index >= 0) {
450 cancelToastLocked(index);
451 } else {
452 Log.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
453 }
454 } finally {
455 Binder.restoreCallingIdentity(callingId);
456 }
457 }
458 }
459
460 private void showNextToastLocked() {
461 ToastRecord record = mToastQueue.get(0);
462 while (record != null) {
463 if (DBG) Log.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
464 try {
465 record.callback.show();
466 scheduleTimeoutLocked(record, false);
467 return;
468 } catch (RemoteException e) {
469 Log.w(TAG, "Object died trying to show notification " + record.callback
470 + " in package " + record.pkg);
471 // remove it from the list and let the process die
472 int index = mToastQueue.indexOf(record);
473 if (index >= 0) {
474 mToastQueue.remove(index);
475 }
476 keepProcessAliveLocked(record.pid);
477 if (mToastQueue.size() > 0) {
478 record = mToastQueue.get(0);
479 } else {
480 record = null;
481 }
482 }
483 }
484 }
485
486 private void cancelToastLocked(int index) {
487 ToastRecord record = mToastQueue.get(index);
488 try {
489 record.callback.hide();
490 } catch (RemoteException e) {
491 Log.w(TAG, "Object died trying to hide notification " + record.callback
492 + " in package " + record.pkg);
493 // don't worry about this, we're about to remove it from
494 // the list anyway
495 }
496 mToastQueue.remove(index);
497 keepProcessAliveLocked(record.pid);
498 if (mToastQueue.size() > 0) {
499 // Show the next one. If the callback fails, this will remove
500 // it from the list, so don't assume that the list hasn't changed
501 // after this point.
502 showNextToastLocked();
503 }
504 }
505
506 private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
507 {
508 Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
509 long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
510 mHandler.removeCallbacksAndMessages(r);
511 mHandler.sendMessageDelayed(m, delay);
512 }
513
514 private void handleTimeout(ToastRecord record)
515 {
516 if (DBG) Log.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
517 synchronized (mToastQueue) {
518 int index = indexOfToastLocked(record.pkg, record.callback);
519 if (index >= 0) {
520 cancelToastLocked(index);
521 }
522 }
523 }
524
525 // lock on mToastQueue
526 private int indexOfToastLocked(String pkg, ITransientNotification callback)
527 {
528 IBinder cbak = callback.asBinder();
529 ArrayList<ToastRecord> list = mToastQueue;
530 int len = list.size();
531 for (int i=0; i<len; i++) {
532 ToastRecord r = list.get(i);
533 if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
534 return i;
535 }
536 }
537 return -1;
538 }
539
540 // lock on mToastQueue
541 private void keepProcessAliveLocked(int pid)
542 {
543 int toastCount = 0; // toasts from this pid
544 ArrayList<ToastRecord> list = mToastQueue;
545 int N = list.size();
546 for (int i=0; i<N; i++) {
547 ToastRecord r = list.get(i);
548 if (r.pid == pid) {
549 toastCount++;
550 }
551 }
552 try {
553 mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
554 } catch (RemoteException e) {
555 // Shouldn't happen.
556 }
557 }
558
559 private final class WorkerHandler extends Handler
560 {
561 @Override
562 public void handleMessage(Message msg)
563 {
564 switch (msg.what)
565 {
566 case MESSAGE_TIMEOUT:
567 handleTimeout((ToastRecord)msg.obj);
568 break;
569 }
570 }
571 }
572
573
574 // Notifications
575 // ============================================================================
576 public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
577 {
578 // This conditional is a dirty hack to limit the logging done on
579 // behalf of the download manager without affecting other apps.
580 if (!pkg.equals("com.android.providers.downloads")
581 || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
582 EventLog.writeEvent(EVENT_LOG_ENQUEUE, pkg, id, notification.toString());
583 }
584
585 if (pkg == null || notification == null) {
586 throw new IllegalArgumentException("null not allowed: pkg=" + pkg
587 + " id=" + id + " notification=" + notification);
588 }
589 if (notification.icon != 0) {
590 if (notification.contentView == null) {
591 throw new IllegalArgumentException("contentView required: pkg=" + pkg
592 + " id=" + id + " notification=" + notification);
593 }
594 if (notification.contentIntent == null) {
595 throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
596 + " id=" + id + " notification=" + notification);
597 }
598 }
599
600 synchronized (mNotificationList) {
601 NotificationRecord r = new NotificationRecord(pkg, id, notification);
602 NotificationRecord old = null;
603
604 int index = indexOfNotificationLocked(pkg, id);
605 if (index < 0) {
606 mNotificationList.add(r);
607 } else {
608 old = mNotificationList.remove(index);
609 mNotificationList.add(index, r);
610 }
611 if (notification.icon != 0) {
612 IconData icon = IconData.makeIcon(null, pkg, notification.icon,
613 notification.iconLevel,
614 notification.number);
615 CharSequence truncatedTicker = notification.tickerText;
616
617 // TODO: make this restriction do something smarter like never fill
618 // more than two screens. "Why would anyone need more than 80 characters." :-/
619 final int maxTickerLen = 80;
620 if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
621 truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
622 }
623
624 NotificationData n = new NotificationData();
625 n.id = id;
626 n.pkg = pkg;
627 n.when = notification.when;
628 n.tickerText = truncatedTicker;
629 n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
630 if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
631 n.clearable = true;
632 }
633 n.contentView = notification.contentView;
634 n.contentIntent = notification.contentIntent;
635 n.deleteIntent = notification.deleteIntent;
636 if (old != null && old.statusBarKey != null) {
637 r.statusBarKey = old.statusBarKey;
638 long identity = Binder.clearCallingIdentity();
639 try {
640 mStatusBarService.updateIcon(r.statusBarKey, icon, n);
641 }
642 finally {
643 Binder.restoreCallingIdentity(identity);
644 }
645 } else {
646 long identity = Binder.clearCallingIdentity();
647 try {
648 r.statusBarKey = mStatusBarService.addIcon(icon, n);
Joe Onoratoc1e84462009-03-24 19:29:20 -0700649 mHardware.pulseBreathingLight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 }
651 finally {
652 Binder.restoreCallingIdentity(identity);
653 }
654 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700655
Joe Onorato30275482009-07-08 17:09:14 -0700656 sendAccessibilityEvent(notification, pkg);
svetoslavganov75986cf2009-05-14 22:28:01 -0700657
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 } else {
659 if (old != null && old.statusBarKey != null) {
660 long identity = Binder.clearCallingIdentity();
661 try {
662 mStatusBarService.removeIcon(old.statusBarKey);
663 }
664 finally {
665 Binder.restoreCallingIdentity(identity);
666 }
667 }
668 }
669
670 // If we're not supposed to beep, vibrate, etc. then don't.
671 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
672 && (!(old != null
Joe Onorato30275482009-07-08 17:09:14 -0700673 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
674 && mSystemReady) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 // sound
676 final boolean useDefaultSound =
677 (notification.defaults & Notification.DEFAULT_SOUND) != 0;
678 if (useDefaultSound || notification.sound != null) {
679 Uri uri;
680 if (useDefaultSound) {
681 uri = Settings.System.DEFAULT_NOTIFICATION_URI;
682 } else {
683 uri = notification.sound;
684 }
685 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
686 int audioStreamType;
687 if (notification.audioStreamType >= 0) {
688 audioStreamType = notification.audioStreamType;
689 } else {
690 audioStreamType = DEFAULT_STREAM_TYPE;
691 }
692 mSoundNotification = r;
693 long identity = Binder.clearCallingIdentity();
694 try {
695 mSound.play(mContext, uri, looping, audioStreamType);
696 }
697 finally {
698 Binder.restoreCallingIdentity(identity);
699 }
700 }
701
702 // vibrate
703 final AudioManager audioManager = (AudioManager) mContext
704 .getSystemService(Context.AUDIO_SERVICE);
705 final boolean useDefaultVibrate =
706 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
707 if ((useDefaultVibrate || notification.vibrate != null)
708 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
709 mVibrateNotification = r;
710
711 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
712 : notification.vibrate,
713 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
714 }
715 }
716
717 // this option doesn't shut off the lights
718
719 // light
720 // the most recent thing gets the light
721 mLights.remove(old);
722 if (mLedNotification == old) {
723 mLedNotification = null;
724 }
725 //Log.i(TAG, "notification.lights="
726 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
727 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
728 mLights.add(r);
729 updateLightsLocked();
730 } else {
731 if (old != null
732 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
733 updateLightsLocked();
734 }
735 }
736 }
737
738 idOut[0] = id;
739 }
740
Joe Onorato30275482009-07-08 17:09:14 -0700741 private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700742 AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
743 if (!manager.isEnabled()) {
744 return;
745 }
746
747 AccessibilityEvent event =
748 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
749 event.setPackageName(packageName);
750 event.setClassName(Notification.class.getName());
751 event.setParcelableData(notification);
752 CharSequence tickerText = notification.tickerText;
753 if (!TextUtils.isEmpty(tickerText)) {
754 event.getText().add(tickerText);
755 }
756
757 manager.sendAccessibilityEvent(event);
758 }
759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 private void cancelNotificationLocked(NotificationRecord r) {
761 // status bar
762 if (r.notification.icon != 0) {
763 long identity = Binder.clearCallingIdentity();
764 try {
765 mStatusBarService.removeIcon(r.statusBarKey);
766 }
767 finally {
768 Binder.restoreCallingIdentity(identity);
769 }
770 r.statusBarKey = null;
771 }
772
773 // sound
774 if (mSoundNotification == r) {
775 mSoundNotification = null;
776 long identity = Binder.clearCallingIdentity();
777 try {
778 mSound.stop();
779 }
780 finally {
781 Binder.restoreCallingIdentity(identity);
782 }
783 }
784
785 // vibrate
786 if (mVibrateNotification == r) {
787 mVibrateNotification = null;
788 long identity = Binder.clearCallingIdentity();
789 try {
790 mVibrator.cancel();
791 }
792 finally {
793 Binder.restoreCallingIdentity(identity);
794 }
795 }
796
797 // light
798 mLights.remove(r);
799 if (mLedNotification == r) {
800 mLedNotification = null;
801 }
802 }
803
804 /**
805 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}.
806 */
807 private void cancelNotification(String pkg, int id, int mustHaveFlags) {
808 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
809
810 synchronized (mNotificationList) {
811 NotificationRecord r = null;
812
813 int index = indexOfNotificationLocked(pkg, id);
814 if (index >= 0) {
815 r = mNotificationList.get(index);
816
817 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
818 return;
819 }
820
821 mNotificationList.remove(index);
822
823 cancelNotificationLocked(r);
824 updateLightsLocked();
825 }
826 }
827 }
828
829 /**
830 * Cancels all notifications from a given package that have all of the
831 * {@code mustHaveFlags}.
832 */
833 private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) {
834 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
835
836 synchronized (mNotificationList) {
837 final int N = mNotificationList.size();
838 boolean canceledSomething = false;
839 for (int i = N-1; i >= 0; --i) {
840 NotificationRecord r = mNotificationList.get(i);
841 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
842 continue;
843 }
844 if (!r.pkg.equals(pkg)) {
845 continue;
846 }
847 mNotificationList.remove(i);
848 cancelNotificationLocked(r);
849 canceledSomething = true;
850 }
851 if (canceledSomething) {
852 updateLightsLocked();
853 }
854 }
855 }
856
857
858 public void cancelNotification(String pkg, int id)
859 {
860 cancelNotification(pkg, id, 0);
861 }
862
863 public void cancelAllNotifications(String pkg)
864 {
865 cancelAllNotificationsInt(pkg, 0);
866 }
867
868 public void cancelAll() {
869 synchronized (mNotificationList) {
870 final int N = mNotificationList.size();
871 for (int i=N-1; i>=0; i--) {
872 NotificationRecord r = mNotificationList.get(i);
873
874 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
875 | Notification.FLAG_NO_CLEAR)) == 0) {
876 if (r.notification.deleteIntent != null) {
877 try {
878 r.notification.deleteIntent.send();
879 } catch (PendingIntent.CanceledException ex) {
880 // do nothing - there's no relevant way to recover, and
881 // no reason to let this propagate
882 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
883 }
884 }
885 mNotificationList.remove(i);
886 cancelNotificationLocked(r);
887 }
888 }
889
890 updateLightsLocked();
891 }
892 }
893
894 private void updateLights() {
895 synchronized (mNotificationList) {
896 updateLightsLocked();
897 }
898 }
899
900 // lock on mNotificationList
901 private void updateLightsLocked()
902 {
The Android Open Source Project10592532009-03-18 17:39:46 -0700903 // Battery low always shows, other states only show if charging.
904 if (mBatteryLow) {
905 mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, BATTERY_LOW_ARGB,
906 HardwareService.LIGHT_FLASH_TIMED, BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 } else if (mBatteryCharging) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700908 if (mBatteryFull) {
909 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
910 BATTERY_FULL_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700912 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
913 BATTERY_MEDIUM_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800914 }
915 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700916 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY);
917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918
The Android Open Source Project10592532009-03-18 17:39:46 -0700919 // handle notification lights
920 if (mLedNotification == null) {
921 // get next notification, if any
922 int n = mLights.size();
923 if (n > 0) {
924 mLedNotification = mLights.get(n-1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925 }
926 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700927 if (mLedNotification == null) {
928 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS);
929 } else {
930 mHardware.setLightFlashing_UNCHECKED(
931 HardwareService.LIGHT_ID_NOTIFICATIONS,
932 mLedNotification.notification.ledARGB,
933 HardwareService.LIGHT_FLASH_TIMED,
934 mLedNotification.notification.ledOnMS,
935 mLedNotification.notification.ledOffMS);
936 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937 }
938
939 // lock on mNotificationList
940 private int indexOfNotificationLocked(String pkg, int id)
941 {
942 ArrayList<NotificationRecord> list = mNotificationList;
943 final int len = list.size();
944 for (int i=0; i<len; i++) {
945 NotificationRecord r = list.get(i);
946 if (r.id == id && r.pkg.equals(pkg)) {
947 return i;
948 }
949 }
950 return -1;
951 }
952
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700953 // This is here instead of StatusBarPolicy because it is an important
954 // security feature that we don't want people customizing the platform
955 // to accidentally lose.
956 private void updateAdbNotification() {
957 if (mAdbEnabled && mBatteryPlugged == BatteryManager.BATTERY_PLUGGED_USB) {
Mike Lockwooded760372009-07-09 07:07:27 -0400958 if ("0".equals(SystemProperties.get("persist.adb.notify"))) {
959 return;
960 }
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700961 if (!mAdbNotificationShown) {
962 NotificationManager notificationManager = (NotificationManager) mContext
963 .getSystemService(Context.NOTIFICATION_SERVICE);
964 if (notificationManager != null) {
965 Resources r = mContext.getResources();
966 CharSequence title = r.getText(
967 com.android.internal.R.string.adb_active_notification_title);
968 CharSequence message = r.getText(
969 com.android.internal.R.string.adb_active_notification_message);
970
971 if (mAdbNotification == null) {
972 mAdbNotification = new Notification();
973 mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_warning;
974 mAdbNotification.when = 0;
975 mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT;
976 mAdbNotification.tickerText = title;
977 mAdbNotification.defaults |= Notification.DEFAULT_SOUND;
978 }
979
980 Intent intent = new Intent(
981 Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
982 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
983 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
984 // Note: we are hard-coding the component because this is
985 // an important security UI that we don't want anyone
986 // intercepting.
987 intent.setComponent(new ComponentName("com.android.settings",
988 "com.android.settings.DevelopmentSettings"));
989 PendingIntent pi = PendingIntent.getActivity(mContext, 0,
990 intent, 0);
991
992 mAdbNotification.setLatestEventInfo(mContext, title, message, pi);
993
994 mAdbNotificationShown = true;
995 notificationManager.notify(
996 com.android.internal.R.string.adb_active_notification_title,
997 mAdbNotification);
998 }
999 }
1000
1001 } else if (mAdbNotificationShown) {
1002 NotificationManager notificationManager = (NotificationManager) mContext
1003 .getSystemService(Context.NOTIFICATION_SERVICE);
1004 if (notificationManager != null) {
1005 mAdbNotificationShown = false;
1006 notificationManager.cancel(
1007 com.android.internal.R.string.adb_active_notification_title);
1008 }
1009 }
1010 }
1011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 // ======================================================================
1013 @Override
1014 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1015 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1016 != PackageManager.PERMISSION_GRANTED) {
1017 pw.println("Permission Denial: can't dump NotificationManager from from pid="
1018 + Binder.getCallingPid()
1019 + ", uid=" + Binder.getCallingUid());
1020 return;
1021 }
1022
1023 pw.println("Current Notification Manager state:");
1024
1025 int N;
1026
1027 synchronized (mToastQueue) {
1028 N = mToastQueue.size();
1029 if (N > 0) {
1030 pw.println(" Toast Queue:");
1031 for (int i=0; i<N; i++) {
1032 mToastQueue.get(i).dump(pw, " ");
1033 }
1034 pw.println(" ");
1035 }
1036
1037 }
1038
1039 synchronized (mNotificationList) {
1040 N = mNotificationList.size();
1041 if (N > 0) {
1042 pw.println(" Notification List:");
1043 for (int i=0; i<N; i++) {
1044 mNotificationList.get(i).dump(pw, " ", mContext);
1045 }
1046 pw.println(" ");
1047 }
1048
1049 N = mLights.size();
1050 if (N > 0) {
1051 pw.println(" Lights List:");
1052 for (int i=0; i<N; i++) {
1053 mLights.get(i).dump(pw, " ", mContext);
1054 }
1055 pw.println(" ");
1056 }
1057
1058 pw.println(" mSoundNotification=" + mSoundNotification);
1059 pw.println(" mSound=" + mSound);
1060 pw.println(" mVibrateNotification=" + mVibrateNotification);
Joe Onorato39f5b6a2009-07-23 12:29:19 -04001061 pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
1062 pw.println(" mSystemReady=" + mSystemReady);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 }
1064 }
1065}