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