blob: 38fb7c9cd9d85fe6d57d9233ebab4c97a3497dfb [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;
93 private int mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
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
371 // register for battery changed notifications
372 IntentFilter filter = new IntentFilter();
373 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
374 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
375 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
376 mContext.registerReceiver(mIntentReceiver, filter);
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700377
378 mSettingsObserver = new SettingsObserver(mHandler);
379 mSettingsObserver.observe();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 }
381
Joe Onorato30275482009-07-08 17:09:14 -0700382 void systemReady() {
383 // no beeping until we're basically done booting
384 mSystemReady = true;
385 }
386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 // Toasts
388 // ============================================================================
389 public void enqueueToast(String pkg, ITransientNotification callback, int duration)
390 {
391 Log.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
392
393 if (pkg == null || callback == null) {
394 Log.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
395 return ;
396 }
397
398 synchronized (mToastQueue) {
399 int callingPid = Binder.getCallingPid();
400 long callingId = Binder.clearCallingIdentity();
401 try {
402 ToastRecord record;
403 int index = indexOfToastLocked(pkg, callback);
404 // If it's already in the queue, we update it in place, we don't
405 // move it to the end of the queue.
406 if (index >= 0) {
407 record = mToastQueue.get(index);
408 record.update(duration);
409 } else {
410 record = new ToastRecord(callingPid, pkg, callback, duration);
411 mToastQueue.add(record);
412 index = mToastQueue.size() - 1;
413 keepProcessAliveLocked(callingPid);
414 }
415 // If it's at index 0, it's the current toast. It doesn't matter if it's
416 // new or just been updated. Call back and tell it to show itself.
417 // If the callback fails, this will remove it from the list, so don't
418 // assume that it's valid after this.
419 if (index == 0) {
420 showNextToastLocked();
421 }
422 } finally {
423 Binder.restoreCallingIdentity(callingId);
424 }
425 }
426 }
427
428 public void cancelToast(String pkg, ITransientNotification callback) {
429 Log.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
430
431 if (pkg == null || callback == null) {
432 Log.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
433 return ;
434 }
435
436 synchronized (mToastQueue) {
437 long callingId = Binder.clearCallingIdentity();
438 try {
439 int index = indexOfToastLocked(pkg, callback);
440 if (index >= 0) {
441 cancelToastLocked(index);
442 } else {
443 Log.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
444 }
445 } finally {
446 Binder.restoreCallingIdentity(callingId);
447 }
448 }
449 }
450
451 private void showNextToastLocked() {
452 ToastRecord record = mToastQueue.get(0);
453 while (record != null) {
454 if (DBG) Log.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
455 try {
456 record.callback.show();
457 scheduleTimeoutLocked(record, false);
458 return;
459 } catch (RemoteException e) {
460 Log.w(TAG, "Object died trying to show notification " + record.callback
461 + " in package " + record.pkg);
462 // remove it from the list and let the process die
463 int index = mToastQueue.indexOf(record);
464 if (index >= 0) {
465 mToastQueue.remove(index);
466 }
467 keepProcessAliveLocked(record.pid);
468 if (mToastQueue.size() > 0) {
469 record = mToastQueue.get(0);
470 } else {
471 record = null;
472 }
473 }
474 }
475 }
476
477 private void cancelToastLocked(int index) {
478 ToastRecord record = mToastQueue.get(index);
479 try {
480 record.callback.hide();
481 } catch (RemoteException e) {
482 Log.w(TAG, "Object died trying to hide notification " + record.callback
483 + " in package " + record.pkg);
484 // don't worry about this, we're about to remove it from
485 // the list anyway
486 }
487 mToastQueue.remove(index);
488 keepProcessAliveLocked(record.pid);
489 if (mToastQueue.size() > 0) {
490 // Show the next one. If the callback fails, this will remove
491 // it from the list, so don't assume that the list hasn't changed
492 // after this point.
493 showNextToastLocked();
494 }
495 }
496
497 private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
498 {
499 Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
500 long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
501 mHandler.removeCallbacksAndMessages(r);
502 mHandler.sendMessageDelayed(m, delay);
503 }
504
505 private void handleTimeout(ToastRecord record)
506 {
507 if (DBG) Log.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
508 synchronized (mToastQueue) {
509 int index = indexOfToastLocked(record.pkg, record.callback);
510 if (index >= 0) {
511 cancelToastLocked(index);
512 }
513 }
514 }
515
516 // lock on mToastQueue
517 private int indexOfToastLocked(String pkg, ITransientNotification callback)
518 {
519 IBinder cbak = callback.asBinder();
520 ArrayList<ToastRecord> list = mToastQueue;
521 int len = list.size();
522 for (int i=0; i<len; i++) {
523 ToastRecord r = list.get(i);
524 if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
525 return i;
526 }
527 }
528 return -1;
529 }
530
531 // lock on mToastQueue
532 private void keepProcessAliveLocked(int pid)
533 {
534 int toastCount = 0; // toasts from this pid
535 ArrayList<ToastRecord> list = mToastQueue;
536 int N = list.size();
537 for (int i=0; i<N; i++) {
538 ToastRecord r = list.get(i);
539 if (r.pid == pid) {
540 toastCount++;
541 }
542 }
543 try {
544 mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
545 } catch (RemoteException e) {
546 // Shouldn't happen.
547 }
548 }
549
550 private final class WorkerHandler extends Handler
551 {
552 @Override
553 public void handleMessage(Message msg)
554 {
555 switch (msg.what)
556 {
557 case MESSAGE_TIMEOUT:
558 handleTimeout((ToastRecord)msg.obj);
559 break;
560 }
561 }
562 }
563
564
565 // Notifications
566 // ============================================================================
567 public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
568 {
569 // This conditional is a dirty hack to limit the logging done on
570 // behalf of the download manager without affecting other apps.
571 if (!pkg.equals("com.android.providers.downloads")
572 || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
573 EventLog.writeEvent(EVENT_LOG_ENQUEUE, pkg, id, notification.toString());
574 }
575
576 if (pkg == null || notification == null) {
577 throw new IllegalArgumentException("null not allowed: pkg=" + pkg
578 + " id=" + id + " notification=" + notification);
579 }
580 if (notification.icon != 0) {
581 if (notification.contentView == null) {
582 throw new IllegalArgumentException("contentView required: pkg=" + pkg
583 + " id=" + id + " notification=" + notification);
584 }
585 if (notification.contentIntent == null) {
586 throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
587 + " id=" + id + " notification=" + notification);
588 }
589 }
590
591 synchronized (mNotificationList) {
592 NotificationRecord r = new NotificationRecord(pkg, id, notification);
593 NotificationRecord old = null;
594
595 int index = indexOfNotificationLocked(pkg, id);
596 if (index < 0) {
597 mNotificationList.add(r);
598 } else {
599 old = mNotificationList.remove(index);
600 mNotificationList.add(index, r);
601 }
602 if (notification.icon != 0) {
603 IconData icon = IconData.makeIcon(null, pkg, notification.icon,
604 notification.iconLevel,
605 notification.number);
606 CharSequence truncatedTicker = notification.tickerText;
607
608 // TODO: make this restriction do something smarter like never fill
609 // more than two screens. "Why would anyone need more than 80 characters." :-/
610 final int maxTickerLen = 80;
611 if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
612 truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
613 }
614
615 NotificationData n = new NotificationData();
616 n.id = id;
617 n.pkg = pkg;
618 n.when = notification.when;
619 n.tickerText = truncatedTicker;
620 n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
621 if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
622 n.clearable = true;
623 }
624 n.contentView = notification.contentView;
625 n.contentIntent = notification.contentIntent;
626 n.deleteIntent = notification.deleteIntent;
627 if (old != null && old.statusBarKey != null) {
628 r.statusBarKey = old.statusBarKey;
629 long identity = Binder.clearCallingIdentity();
630 try {
631 mStatusBarService.updateIcon(r.statusBarKey, icon, n);
632 }
633 finally {
634 Binder.restoreCallingIdentity(identity);
635 }
636 } else {
637 long identity = Binder.clearCallingIdentity();
638 try {
639 r.statusBarKey = mStatusBarService.addIcon(icon, n);
Joe Onoratoc1e84462009-03-24 19:29:20 -0700640 mHardware.pulseBreathingLight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 }
642 finally {
643 Binder.restoreCallingIdentity(identity);
644 }
645 }
svetoslavganov75986cf2009-05-14 22:28:01 -0700646
Joe Onorato30275482009-07-08 17:09:14 -0700647 sendAccessibilityEvent(notification, pkg);
svetoslavganov75986cf2009-05-14 22:28:01 -0700648
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649 } else {
650 if (old != null && old.statusBarKey != null) {
651 long identity = Binder.clearCallingIdentity();
652 try {
653 mStatusBarService.removeIcon(old.statusBarKey);
654 }
655 finally {
656 Binder.restoreCallingIdentity(identity);
657 }
658 }
659 }
660
661 // If we're not supposed to beep, vibrate, etc. then don't.
662 if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
663 && (!(old != null
Joe Onorato30275482009-07-08 17:09:14 -0700664 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
665 && mSystemReady) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 // sound
667 final boolean useDefaultSound =
668 (notification.defaults & Notification.DEFAULT_SOUND) != 0;
669 if (useDefaultSound || notification.sound != null) {
670 Uri uri;
671 if (useDefaultSound) {
672 uri = Settings.System.DEFAULT_NOTIFICATION_URI;
673 } else {
674 uri = notification.sound;
675 }
676 boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
677 int audioStreamType;
678 if (notification.audioStreamType >= 0) {
679 audioStreamType = notification.audioStreamType;
680 } else {
681 audioStreamType = DEFAULT_STREAM_TYPE;
682 }
683 mSoundNotification = r;
684 long identity = Binder.clearCallingIdentity();
685 try {
686 mSound.play(mContext, uri, looping, audioStreamType);
687 }
688 finally {
689 Binder.restoreCallingIdentity(identity);
690 }
691 }
692
693 // vibrate
694 final AudioManager audioManager = (AudioManager) mContext
695 .getSystemService(Context.AUDIO_SERVICE);
696 final boolean useDefaultVibrate =
697 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
698 if ((useDefaultVibrate || notification.vibrate != null)
699 && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
700 mVibrateNotification = r;
701
702 mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
703 : notification.vibrate,
704 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
705 }
706 }
707
708 // this option doesn't shut off the lights
709
710 // light
711 // the most recent thing gets the light
712 mLights.remove(old);
713 if (mLedNotification == old) {
714 mLedNotification = null;
715 }
716 //Log.i(TAG, "notification.lights="
717 // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
718 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
719 mLights.add(r);
720 updateLightsLocked();
721 } else {
722 if (old != null
723 && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
724 updateLightsLocked();
725 }
726 }
727 }
728
729 idOut[0] = id;
730 }
731
Joe Onorato30275482009-07-08 17:09:14 -0700732 private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
svetoslavganov75986cf2009-05-14 22:28:01 -0700733 AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
734 if (!manager.isEnabled()) {
735 return;
736 }
737
738 AccessibilityEvent event =
739 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
740 event.setPackageName(packageName);
741 event.setClassName(Notification.class.getName());
742 event.setParcelableData(notification);
743 CharSequence tickerText = notification.tickerText;
744 if (!TextUtils.isEmpty(tickerText)) {
745 event.getText().add(tickerText);
746 }
747
748 manager.sendAccessibilityEvent(event);
749 }
750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 private void cancelNotificationLocked(NotificationRecord r) {
752 // status bar
753 if (r.notification.icon != 0) {
754 long identity = Binder.clearCallingIdentity();
755 try {
756 mStatusBarService.removeIcon(r.statusBarKey);
757 }
758 finally {
759 Binder.restoreCallingIdentity(identity);
760 }
761 r.statusBarKey = null;
762 }
763
764 // sound
765 if (mSoundNotification == r) {
766 mSoundNotification = null;
767 long identity = Binder.clearCallingIdentity();
768 try {
769 mSound.stop();
770 }
771 finally {
772 Binder.restoreCallingIdentity(identity);
773 }
774 }
775
776 // vibrate
777 if (mVibrateNotification == r) {
778 mVibrateNotification = null;
779 long identity = Binder.clearCallingIdentity();
780 try {
781 mVibrator.cancel();
782 }
783 finally {
784 Binder.restoreCallingIdentity(identity);
785 }
786 }
787
788 // light
789 mLights.remove(r);
790 if (mLedNotification == r) {
791 mLedNotification = null;
792 }
793 }
794
795 /**
796 * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}.
797 */
798 private void cancelNotification(String pkg, int id, int mustHaveFlags) {
799 EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
800
801 synchronized (mNotificationList) {
802 NotificationRecord r = null;
803
804 int index = indexOfNotificationLocked(pkg, id);
805 if (index >= 0) {
806 r = mNotificationList.get(index);
807
808 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
809 return;
810 }
811
812 mNotificationList.remove(index);
813
814 cancelNotificationLocked(r);
815 updateLightsLocked();
816 }
817 }
818 }
819
820 /**
821 * Cancels all notifications from a given package that have all of the
822 * {@code mustHaveFlags}.
823 */
824 private void cancelAllNotificationsInt(String pkg, int mustHaveFlags) {
825 EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
826
827 synchronized (mNotificationList) {
828 final int N = mNotificationList.size();
829 boolean canceledSomething = false;
830 for (int i = N-1; i >= 0; --i) {
831 NotificationRecord r = mNotificationList.get(i);
832 if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
833 continue;
834 }
835 if (!r.pkg.equals(pkg)) {
836 continue;
837 }
838 mNotificationList.remove(i);
839 cancelNotificationLocked(r);
840 canceledSomething = true;
841 }
842 if (canceledSomething) {
843 updateLightsLocked();
844 }
845 }
846 }
847
848
849 public void cancelNotification(String pkg, int id)
850 {
851 cancelNotification(pkg, id, 0);
852 }
853
854 public void cancelAllNotifications(String pkg)
855 {
856 cancelAllNotificationsInt(pkg, 0);
857 }
858
859 public void cancelAll() {
860 synchronized (mNotificationList) {
861 final int N = mNotificationList.size();
862 for (int i=N-1; i>=0; i--) {
863 NotificationRecord r = mNotificationList.get(i);
864
865 if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
866 | Notification.FLAG_NO_CLEAR)) == 0) {
867 if (r.notification.deleteIntent != null) {
868 try {
869 r.notification.deleteIntent.send();
870 } catch (PendingIntent.CanceledException ex) {
871 // do nothing - there's no relevant way to recover, and
872 // no reason to let this propagate
873 Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
874 }
875 }
876 mNotificationList.remove(i);
877 cancelNotificationLocked(r);
878 }
879 }
880
881 updateLightsLocked();
882 }
883 }
884
885 private void updateLights() {
886 synchronized (mNotificationList) {
887 updateLightsLocked();
888 }
889 }
890
891 // lock on mNotificationList
892 private void updateLightsLocked()
893 {
The Android Open Source Project10592532009-03-18 17:39:46 -0700894 // Battery low always shows, other states only show if charging.
895 if (mBatteryLow) {
896 mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, BATTERY_LOW_ARGB,
897 HardwareService.LIGHT_FLASH_TIMED, BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 } else if (mBatteryCharging) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700899 if (mBatteryFull) {
900 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
901 BATTERY_FULL_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800902 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700903 mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
904 BATTERY_MEDIUM_ARGB);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800905 }
906 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700907 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY);
908 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909
The Android Open Source Project10592532009-03-18 17:39:46 -0700910 // handle notification lights
911 if (mLedNotification == null) {
912 // get next notification, if any
913 int n = mLights.size();
914 if (n > 0) {
915 mLedNotification = mLights.get(n-1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 }
917 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700918 if (mLedNotification == null) {
919 mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS);
920 } else {
921 mHardware.setLightFlashing_UNCHECKED(
922 HardwareService.LIGHT_ID_NOTIFICATIONS,
923 mLedNotification.notification.ledARGB,
924 HardwareService.LIGHT_FLASH_TIMED,
925 mLedNotification.notification.ledOnMS,
926 mLedNotification.notification.ledOffMS);
927 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 }
929
930 // lock on mNotificationList
931 private int indexOfNotificationLocked(String pkg, int id)
932 {
933 ArrayList<NotificationRecord> list = mNotificationList;
934 final int len = list.size();
935 for (int i=0; i<len; i++) {
936 NotificationRecord r = list.get(i);
937 if (r.id == id && r.pkg.equals(pkg)) {
938 return i;
939 }
940 }
941 return -1;
942 }
943
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700944 // This is here instead of StatusBarPolicy because it is an important
945 // security feature that we don't want people customizing the platform
946 // to accidentally lose.
947 private void updateAdbNotification() {
948 if (mAdbEnabled && mBatteryPlugged == BatteryManager.BATTERY_PLUGGED_USB) {
Mike Lockwooded760372009-07-09 07:07:27 -0400949 if ("0".equals(SystemProperties.get("persist.adb.notify"))) {
950 return;
951 }
Dianne Hackborn1dac2772009-06-26 18:16:48 -0700952 if (!mAdbNotificationShown) {
953 NotificationManager notificationManager = (NotificationManager) mContext
954 .getSystemService(Context.NOTIFICATION_SERVICE);
955 if (notificationManager != null) {
956 Resources r = mContext.getResources();
957 CharSequence title = r.getText(
958 com.android.internal.R.string.adb_active_notification_title);
959 CharSequence message = r.getText(
960 com.android.internal.R.string.adb_active_notification_message);
961
962 if (mAdbNotification == null) {
963 mAdbNotification = new Notification();
964 mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_warning;
965 mAdbNotification.when = 0;
966 mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT;
967 mAdbNotification.tickerText = title;
968 mAdbNotification.defaults |= Notification.DEFAULT_SOUND;
969 }
970
971 Intent intent = new Intent(
972 Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
973 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
974 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
975 // Note: we are hard-coding the component because this is
976 // an important security UI that we don't want anyone
977 // intercepting.
978 intent.setComponent(new ComponentName("com.android.settings",
979 "com.android.settings.DevelopmentSettings"));
980 PendingIntent pi = PendingIntent.getActivity(mContext, 0,
981 intent, 0);
982
983 mAdbNotification.setLatestEventInfo(mContext, title, message, pi);
984
985 mAdbNotificationShown = true;
986 notificationManager.notify(
987 com.android.internal.R.string.adb_active_notification_title,
988 mAdbNotification);
989 }
990 }
991
992 } else if (mAdbNotificationShown) {
993 NotificationManager notificationManager = (NotificationManager) mContext
994 .getSystemService(Context.NOTIFICATION_SERVICE);
995 if (notificationManager != null) {
996 mAdbNotificationShown = false;
997 notificationManager.cancel(
998 com.android.internal.R.string.adb_active_notification_title);
999 }
1000 }
1001 }
1002
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001003 // ======================================================================
1004 @Override
1005 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1006 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1007 != PackageManager.PERMISSION_GRANTED) {
1008 pw.println("Permission Denial: can't dump NotificationManager from from pid="
1009 + Binder.getCallingPid()
1010 + ", uid=" + Binder.getCallingUid());
1011 return;
1012 }
1013
1014 pw.println("Current Notification Manager state:");
1015
1016 int N;
1017
1018 synchronized (mToastQueue) {
1019 N = mToastQueue.size();
1020 if (N > 0) {
1021 pw.println(" Toast Queue:");
1022 for (int i=0; i<N; i++) {
1023 mToastQueue.get(i).dump(pw, " ");
1024 }
1025 pw.println(" ");
1026 }
1027
1028 }
1029
1030 synchronized (mNotificationList) {
1031 N = mNotificationList.size();
1032 if (N > 0) {
1033 pw.println(" Notification List:");
1034 for (int i=0; i<N; i++) {
1035 mNotificationList.get(i).dump(pw, " ", mContext);
1036 }
1037 pw.println(" ");
1038 }
1039
1040 N = mLights.size();
1041 if (N > 0) {
1042 pw.println(" Lights List:");
1043 for (int i=0; i<N; i++) {
1044 mLights.get(i).dump(pw, " ", mContext);
1045 }
1046 pw.println(" ");
1047 }
1048
1049 pw.println(" mSoundNotification=" + mSoundNotification);
1050 pw.println(" mSound=" + mSound);
1051 pw.println(" mVibrateNotification=" + mVibrateNotification);
1052 }
1053 }
1054}