android的notificaiton的声音sound也是申请的AudioManager机制来播放声音的。最近让我找恢复出厂设置后,手机刚启动,接受短信没有声音,如果恢复出厂设置后,等一会儿,过个2分钟再接受短信,就有铃声了。下面我把我分析代码的方法写下来,给自己和读者一些启发:
日历也是用的是Notification,但是恢复出厂设置后,立马设置日历后,日历可以出声音,我看日历的代码,结果发现日历只是用了Notification的闪屏,真正声音是日历自己实现了Mediaplayer来出声音的。所以我又不得不老老实实地研究Notification.sound到底把声音传递到什么地方去了?
转载请标明出处:http://blog.csdn.net/wdaming1986/article/details/7081787
首先,我在短信的com.android.mms.transaction包中的MessagingNotification的573行的java代码:notification.sound = TextUtils.isEmpty(ringtoneStr) ? null : Uri.parse(ringtoneStr);打log查看,发现这个uri确实是存在的,我推测:就是说这个uri传给了framework一层,但是framework一层有没有执行完的动作,所以不响。为了验证是不是短信的错误,我自己单独写了个notification的例子,一个按钮,点击就发出声音。这个例子在正常情况下能正常发声。我就恢复出厂设置后,运行这个例子,结果发现没有声音,这就充分验证了我的猜测。我就去framework去着notificaion.sound = 的声音传递给framework做什么事情了??
接着,在Source Insight软件中导入framework整个工程,然后搜索,notificaiton.sounds,结果搜到了,在framework/base/core/java/android/app/Notification.java类。
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import java.util.Date;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.widget.RemoteViews;
/**
* A class that represents how a persistent notification is to be presented to
* the user using the {@link android.app.NotificationManager}.
*
* <p>For a guide to creating notifications, see the
* <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Creating Status
* Bar Notifications</a> document in the Dev Guide.</p>
*/
public class Notification implements Parcelable
{
/**
* Use all default values (where applicable).
*/
public static final int DEFAULT_ALL = ~0;
/**
* Use the default notification sound. This will ignore any given
* {@link #sound}.
*
* @see #defaults
*/
public static final int DEFAULT_SOUND = 1;
/**
* Use the default notification vibrate. This will ignore any given
* {@link #vibrate}. Using phone vibration requires the
* {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
*
* @see #defaults
*/
public static final int DEFAULT_VIBRATE = 2;
/**
* Use the default notification lights. This will ignore the
* {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or
* {@link #ledOnMS}.
*
* @see #defaults
*/
public static final int DEFAULT_LIGHTS = 4;
/**
* The timestamp for the notification. The icons and expanded views
* are sorted by this key.
*/
public long when;
/**
* The resource id of a drawable to use as the icon in the status bar.
*/
public int icon;
/**
* The number of events that this notification represents. For example, in a new mail
* notification, this could be the number of unread messages. This number is superimposed over
* the icon in the status bar. If the number is 0 or negative, it is not shown in the status
* bar.
*/
public int number;
/**
* The intent to execute when the expanded status entry is clicked. If
* this is an activity, it must include the
* {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
* that you take care of task management as described in the <em>Activities and Tasks</em>
* section of the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application
* Fundamentals</a> document.
*/
public PendingIntent contentIntent;
/**
* The intent to execute when the status entry is deleted by the user
* with the "Clear All Notifications" button. This probably shouldn't
* be launching an activity since several of those will be sent at the
* same time.
*/
public PendingIntent deleteIntent;
/**
* An intent to launch instead of posting the notification to the status bar.
* Only for use with extremely high-priority notifications demanding the user's
* <strong>immediate</strong> attention, such as an incoming phone call or
* alarm clock that the user has explicitly set to a particular time.
* If this facility is used for something else, please give the user an option
* to turn it off and use a normal notification, as this can be extremely
* disruptive.
*/
public PendingIntent fullScreenIntent;
/**
* Text to scroll across the screen when this item is added to
* the status bar.
*/
public CharSequence tickerText;
/**
* The view that will represent this notification in the expanded status bar.
*/
public RemoteViews contentView;
/**
* If the icon in the status bar is to have more than one level, you can set this. Otherwise,
* leave it at its default value of 0.
*
* @see android.widget.ImageView#setImageLevel
* @see android.graphics.drawable#setLevel
*/
public int iconLevel;
/**
* The sound to play.
*
* <p>
* To play the default notification sound, see {@link #defaults}.
* </p>
*/
public Uri sound;
/**
* Use this constant as the value for audioStreamType to request that
* the default stream type for notifications be used. Currently the
* default stream type is STREAM_RING.
*/
public static final int STREAM_DEFAULT = -1;
/**
* The audio stream type to use when playing the sound.
* Should be one of the STREAM_ constants from
* {@link android.media.AudioManager}.
*/
public int audioStreamType = STREAM_DEFAULT;
/**
* The pattern with which to vibrate.
*
* <p>
* To vibrate the default pattern, see {@link #defaults}.
* </p>
*
* @see android.os.Vibrator#vibrate(long[],int)
*/
public long[] vibrate;
/**
* The color of the led. The hardware will do its best approximation.
*
* @see #FLAG_SHOW_LIGHTS
* @see #flags
*/
public int ledARGB;
/**
* The number of milliseconds for the LED to be on while it's flashing.
* The hardware will do its best approximation.
*
* @see #FLAG_SHOW_LIGHTS
* @see #flags
*/
public int ledOnMS;
/**
* The number of milliseconds for the LED to be off while it's flashing.
* The hardware will do its best approximation.
*
* @see #FLAG_SHOW_LIGHTS
* @see #flags
*/
public int ledOffMS;
/**
* Specifies which values should be taken from the defaults.
* <p>
* To set, OR the desired from {@link #DEFAULT_SOUND},
* {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default
* values, use {@link #DEFAULT_ALL}.
* </p>
*/
public int defaults;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if you want the LED on for this notification.
* <ul>
* <li>To turn the LED off, pass 0 in the alpha channel for colorARGB
* or 0 for both ledOnMS and ledOffMS.</li>
* <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li>
* <li>To flash the LED, pass the number of milliseconds that it should
* be on and off to ledOnMS and ledOffMS.</li>
* </ul>
* <p>
* Since hardware varies, you are not guaranteed that any of the values
* you pass are honored exactly. Use the system defaults (TODO) if possible
* because they will be set to values that work on any given hardware.
* <p>
* The alpha channel must be set for forward compatibility.
*
*/
public static final int FLAG_SHOW_LIGHTS = 0x00000001;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if this notification is in reference to something that is ongoing,
* like a phone call. It should not be set if this notification is in
* reference to something that happened at a particular point in time,
* like a missed phone call.
*/
public static final int FLAG_ONGOING_EVENT = 0x00000002;
/**
* Bit to be bitwise-ored into the {@link #flags} field that if set,
* the audio will be repeated until the notification is
* cancelled or the notification window is opened.
*/
public static final int FLAG_INSISTENT = 0x00000004;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if you want the sound and/or vibration play each time the
* notification is sent, even if it has not been canceled before that.
*/
public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if the notification should be canceled when it is clicked by the
* user.
*/
public static final int FLAG_AUTO_CANCEL = 0x00000010;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if the notification should not be canceled when the user clicks
* the Clear all button.
*/
public static final int FLAG_NO_CLEAR = 0x00000020;
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if this notification represents a currently running service. This
* will normally be set for you by {@link Service#startForeground}.
*/
public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
public int flags;
/**
* Constructs a Notification object with everything set to 0.
*/
public Notification()
{
this.when = System.currentTimeMillis();
}
/**
* @deprecated use {@link #Notification(int,CharSequence,long)} and {@link #setLatestEventInfo}.
* @hide
*/
public Notification(Context context, int icon, CharSequence tickerText, long when,
CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
{
this.when = when;
this.icon = icon;
this.tickerText = tickerText;
setLatestEventInfo(context, contentTitle, contentText,
PendingIntent.getActivity(context, 0, contentIntent, 0));
}
/**
* Constructs a Notification object with the information needed to
* have a status bar icon without the standard expanded view.
*
* @param icon The resource id of the icon to put in the status bar.
* @param tickerText The text that flows by in the status bar when the notification first
* activates.
* @param when The time to show in the time field. In the System.currentTimeMillis
* timebase.
*/
public Notification(int icon, CharSequence tickerText, long when)
{
this.icon = icon;
this.tickerText = tickerText;
this.when = when;
}
/**
* Unflatten the notification from a parcel.
*/
public Notification(Parcel parcel)
{
int version = parcel.readInt();
when = parcel.readLong();
icon = parcel.readInt();
number = parcel.readInt();
if (parcel.readInt() != 0) {
contentIntent = PendingIntent.CREATOR.createFromParcel(parcel);
}
if (parcel.readInt() != 0) {
deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
}
if (parcel.readInt() != 0) {
tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
}
if (parcel.readInt() != 0) {
contentView = RemoteViews.CREATOR.createFromParcel(parcel);
}
defaults = parcel.readInt();
flags = parcel.readInt();
if (parcel.readInt() != 0) {
sound = Uri.CREATOR.createFromParcel(parcel);
}
audioStreamType = parcel.readInt();
vibrate = parcel.createLongArray();
ledARGB = parcel.readInt();
ledOnMS = parcel.readInt();
ledOffMS = parcel.readInt();
iconLevel = parcel.readInt();
if (parcel.readInt() != 0) {
fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
}
}
public Notification clone() {
Notification that = new Notification();
that.when = this.when;
that.icon = this.icon;
that.number = this.number;
// PendingIntents are global, so there's no reason (or way) to clone them.
that.contentIntent = this.contentIntent;
that.deleteIntent = this.deleteIntent;
that.fullScreenIntent = this.fullScreenIntent;
if (this.tickerText != null) {
that.tickerText = this.tickerText.toString();
}
if (this.contentView != null) {
that.contentView = this.contentView.clone();
}
that.iconLevel = that.iconLevel;
that.sound = this.sound; // android.net.Uri is immutable
that.audioStreamType = this.audioStreamType;
final long[] vibrate = this.vibrate;
if (vibrate != null) {
final int N = vibrate.length;
final long[] vib = that.vibrate = new long[N];
System.arraycopy(vibrate, 0, vib, 0, N);
}
that.ledARGB = this.ledARGB;
that.ledOnMS = this.ledOnMS;
that.ledOffMS = this.ledOffMS;
that.defaults = this.defaults;
that.flags = this.flags;
return that;
}
public int describeContents() {
return 0;
}
/**
* Flatten this notification from a parcel.
*/
public void writeToParcel(Parcel parcel, int flags)
{
parcel.writeInt(1);
parcel.writeLong(when);
parcel.writeInt(icon);
parcel.writeInt(number);
if (contentIntent != null) {
parcel.writeInt(1);
contentIntent.writeToParcel(parcel, 0);
} else {
parcel.writeInt(0);
}
if (deleteIntent != null) {
parcel.writeInt(1);
deleteIntent.writeToParcel(parcel, 0);
} else {
parcel.writeInt(0);
}
if (tickerText != null) {
parcel.writeInt(1);
TextUtils.writeToParcel(tickerText, parcel, flags);
} else {
parcel.writeInt(0);
}
if (contentView != null) {
parcel.writeInt(1);
contentView.writeToParcel(parcel, 0);
} else {
parcel.writeInt(0);
}
parcel.writeInt(defaults);
parcel.writeInt(this.flags);
if (sound != null) {
parcel.writeInt(1);
sound.writeToParcel(parcel, 0);
} else {
parcel.writeInt(0);
}
parcel.writeInt(audioStreamType);
parcel.writeLongArray(vibrate);
parcel.writeInt(ledARGB);
parcel.writeInt(ledOnMS);
parcel.writeInt(ledOffMS);
parcel.writeInt(iconLevel);
if (fullScreenIntent != null) {
parcel.writeInt(1);
fullScreenIntent.writeToParcel(parcel, 0);
} else {
parcel.writeInt(0);
}
}
/**
* Parcelable.Creator that instantiates Notification objects
*/
public static final Parcelable.Creator<Notification> CREATOR
= new Parcelable.Creator<Notification>()
{
public Notification createFromParcel(Parcel parcel)
{
return new Notification(parcel);
}
public Notification[] newArray(int size)
{
return new Notification[size];
}
};
/**
* Sets the {@link #contentView} field to be a view with the standard "Latest Event"
* layout.
*
* <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields
* in the view.</p>
* @param context The context for your application / activity.
* @param contentTitle The title that goes in the expanded entry.
* @param contentText The text that goes in the expanded entry.
* @param contentIntent The intent to launch when the user clicks the expanded notification.
* If this is an activity, it must include the
* {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
* that you take care of task management as described in
* <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">Application Fundamentals: Activities and Tasks</a>.
*/
public void setLatestEventInfo(Context context,
CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
RemoteViews contentView = new RemoteViews(context.getPackageName(),
com.android.internal.R.layout.status_bar_latest_event_content);
if (this.icon != 0) {
contentView.setImageViewResource(com.android.internal.R.id.icon, this.icon);
}
if (contentTitle != null) {
contentView.setTextViewText(com.android.internal.R.id.title, contentTitle);
}
if (contentText != null) {
contentView.setTextViewText(com.android.internal.R.id.text, contentText);
}
if (this.when != 0) {
contentView.setLong(com.android.internal.R.id.time, "setTime", when);
}
this.contentView = contentView;
this.contentIntent = contentIntent;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Notification(vibrate=");
if (this.vibrate != null) {
int N = this.vibrate.length-1;
sb.append("[");
for (int i=0; i<N; i++) {
sb.append(this.vibrate[i]);
sb.append(',');
}
if (N != -1) {
sb.append(this.vibrate[N]);
}
sb.append("]");
} else if ((this.defaults & DEFAULT_VIBRATE) != 0) {
sb.append("default");
} else {
sb.append("null");
}
sb.append(",sound=");
if (this.sound != null) {
sb.append(this.sound.toString());
} else if ((this.defaults & DEFAULT_SOUND) != 0) {
sb.append("default");
} else {
sb.append("null");
}
sb.append(",defaults=0x");
sb.append(Integer.toHexString(this.defaults));
sb.append(",flags=0x");
sb.append(Integer.toHexString(this.flags));
sb.append(")");
return sb.toString();
}
}
在344行, sound = Uri.CREATOR.createFromParcel(parcel);回来经过打log发现,问题的关键不再这个类中。
再次,改变方向,换条思路走,找notification的服务类,看有什么新的发现,在framework/base/services/java/com/android/server/NotificationManagerService.java类中,真的找到了我需要的,
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.server.StatusBarManagerService;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Power;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Vibrator;
import android.os.SystemClock;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
/** {@hide} */
public class NotificationManagerService extends INotificationManager.Stub
{
private static final String TAG = "NotificationService";
private static final boolean DBG = false;
private static final int MAX_PACKAGE_NOTIFICATIONS = 50;
private static final int NOTIFICATION_REQUEST_INTERVAL = 30000; // 30 seconds
private static final int MAX_PACKAGE_NOTIFICATION_REQUESTS = 500;
// message codes
private static final int MESSAGE_TIMEOUT = 2;
private static final int LONG_DELAY = 3500; // 3.5 seconds
private static final int SHORT_DELAY = 2000; // 2 seconds
private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
final Context mContext;
final IActivityManager mAm;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
private StatusBarManagerService mStatusBar;
private LightsService mLightsService;
private LightsService.Light mBatteryLight;
private LightsService.Light mNotificationLight;
private LightsService.Light mAttentionLight;
private int mDefaultNotificationColor;
private int mDefaultNotificationLedOn;
private int mDefaultNotificationLedOff;
private NotificationRecord mSoundNotification;
private NotificationPlayer mSound;
private boolean mSystemReady;
private int mDisabledNotifications;
private NotificationRecord mVibrateNotification;
private Vibrator mVibrator = new Vibrator();
// for enabling and disabling notification pulse behavior
private boolean mScreenOn = true;
private boolean mInCall = false;
private boolean mNotificationPulseEnabled;
// This is true if we have received a new notification while the screen is off
// (that is, if mLedNotification was set while the screen was off)
// This is reset to false when the screen is turned on.
private boolean mPendingPulseNotification;
// for adb connected notifications
private boolean mAdbNotificationShown = false;
private Notification mAdbNotification;
private final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
private class PackageRequestInfo {
int requestCount;
long lastPostTime;
}
private final HashMap<String, PackageRequestInfo> mPackageInfo =
new HashMap<String, PackageRequestInfo>();
private ArrayList<ToastRecord> mToastQueue;
private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
private boolean mBatteryCharging;
private boolean mBatteryLow;
private boolean mBatteryFull;
private NotificationRecord mLedNotification;
private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on
private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on
private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on
private static final int BATTERY_BLINK_ON = 125;
private static final int BATTERY_BLINK_OFF = 2875;
private static String idDebugString(Context baseContext, String packageName, int id) {
Context c = null;
if (packageName != null) {
try {
c = baseContext.createPackageContext(packageName, 0);
} catch (NameNotFoundException e) {
c = baseContext;
}
} else {
c = baseContext;
}
String pkg;
String type;
String name;
Resources r = c.getResources();
try {
return r.getResourceName(id);
} catch (Resources.NotFoundException e) {
return "<name unknown>";
}
}
private static final class NotificationRecord
{
final String pkg;
final String tag;
final int id;
final int uid;
final int initialPid;
ITransientNotification callback;
int duration;
final Notification notification;
IBinder statusBarKey;
NotificationRecord(String pkg, String tag, int id, int uid, int initialPid,
Notification notification)
{
this.pkg = pkg;
this.tag = tag;
this.id = id;
this.uid = uid;
this.initialPid = initialPid;
this.notification = notification;
}
void dump(PrintWriter pw, String prefix, Context baseContext) {
pw.println(prefix + this);
pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon)
+ " / " + idDebugString(baseContext, this.pkg, notification.icon));
pw.println(prefix + " contentIntent=" + notification.contentIntent);
pw.println(prefix + " deleteIntent=" + notification.deleteIntent);
pw.println(prefix + " tickerText=" + notification.tickerText);
pw.println(prefix + " contentView=" + notification.contentView);
pw.println(prefix + " defaults=0x" + Integer.toHexString(notification.defaults));
pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags));
pw.println(prefix + " sound=" + notification.sound);
pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
pw.println(prefix + " ledARGB=0x" + Integer.toHexString(notification.ledARGB)
+ " ledOnMS=" + notification.ledOnMS
+ " ledOffMS=" + notification.ledOffMS);
}
@Override
public final String toString()
{
return "NotificationRecord{"
+ Integer.toHexString(System.identityHashCode(this))
+ " pkg=" + pkg
+ " id=" + Integer.toHexString(id)
+ " tag=" + tag + "}";
}
}
private static final class ToastRecord
{
final int pid;
final String pkg;
final ITransientNotification callback;
int duration;
ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
{
this.pid = pid;
this.pkg = pkg;
this.callback = callback;
this.duration = duration;
}
void update(int duration) {
this.duration = duration;
}
void dump(PrintWriter pw, String prefix) {
pw.println(prefix + this);
}
@Override
public final String toString()
{
return "ToastRecord{"
+ Integer.toHexString(System.identityHashCode(this))
+ " pkg=" + pkg
+ " callback=" + callback
+ " duration=" + duration;
}
}
private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks
= new StatusBarManagerService.NotificationCallbacks() {
public void onSetDisabled(int status) {
synchronized (mNotificationList) {
mDisabledNotifications = status;
if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
// cancel whatever's going on
long identity = Binder.clearCallingIdentity();
try {
mSound.stop();
}
finally {
Binder.restoreCallingIdentity(identity);
}
identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}
public void onClearAll() {
cancelAll();
}
public void onNotificationClick(String pkg, String tag, int id) {
cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL,
Notification.FLAG_FOREGROUND_SERVICE);
}
public void onPanelRevealed() {
synchronized (mNotificationList) {
// sound
mSoundNotification = null;
long identity = Binder.clearCallingIdentity();
try {
mSound.stop();
}
finally {
Binder.restoreCallingIdentity(identity);
}
// vibrate
mVibrateNotification = null;
identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
}
finally {
Binder.restoreCallingIdentity(identity);
}
// light
mLights.clear();
mLedNotification = null;
updateLightsLocked();
}
}
public void onNotificationError(String pkg, String tag, int id,
int uid, int initialPid, String message) {
Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
+ "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
cancelNotification(pkg, tag, id, 0, 0);
long ident = Binder.clearCallingIdentity();
try {
ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
"Bad notification posted from package " + pkg
+ ": " + message);
} catch (RemoteException e) {
}
Binder.restoreCallingIdentity(ident);
}
};
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
boolean queryRestart = false;
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0);
int level = intent.getIntExtra("level", -1);
boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD);
int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90);
if (batteryCharging != mBatteryCharging ||
batteryLow != mBatteryLow ||
batteryFull != mBatteryFull) {
mBatteryCharging = batteryCharging;
mBatteryLow = batteryLow;
mBatteryFull = batteryFull;
updateLights();
}
} else if (action.equals(UsbManager.ACTION_USB_STATE)) {
Bundle extras = intent.getExtras();
boolean usbConnected = extras.getBoolean(UsbManager.USB_CONNECTED);
boolean adbEnabled = (UsbManager.USB_FUNCTION_ENABLED.equals(
extras.getString(UsbManager.USB_FUNCTION_ADB)));
updateAdbNotification(usbConnected && adbEnabled);
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
String pkgList[] = null;
if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else if (queryRestart) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
} else {
Uri uri = intent.getData();
if (uri == null) {
return;
}
String pkgName = uri.getSchemeSpecificPart();
if (pkgName == null) {
return;
}
pkgList = new String[]{pkgName};
}
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart);
}
}
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
mScreenOn = true;
updateNotificationPulse();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
mScreenOn = false;
updateNotificationPulse();
} else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_OFFHOOK));
updateNotificationPulse();
}
}
};
class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
}
void observe() {
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.NOTIFICATION_LIGHT_PULSE), false, this);
update();
}
@Override public void onChange(boolean selfChange) {
update();
}
public void update() {
ContentResolver resolver = mContext.getContentResolver();
boolean pulseEnabled = Settings.System.getInt(resolver,
Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
if (mNotificationPulseEnabled != pulseEnabled) {
mNotificationPulseEnabled = pulseEnabled;
updateNotificationPulse();
}
}
}
NotificationManagerService(Context context, StatusBarManagerService statusBar,
LightsService lights)
{
super();
mContext = context;
mLightsService = lights;
mAm = ActivityManagerNative.getDefault();
mSound = new NotificationPlayer(TAG);
mSound.setUsesWakeLock(context);
mToastQueue = new ArrayList<ToastRecord>();
mHandler = new WorkerHandler();
mStatusBar = statusBar;
statusBar.setNotificationCallbacks(mNotificationCallbacks);
mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY);
mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
Resources resources = mContext.getResources();
mDefaultNotificationColor = resources.getColor(
com.android.internal.R.color.config_defaultNotificationColor);
mDefaultNotificationLedOn = resources.getInteger(
com.android.internal.R.integer.config_defaultNotificationLedOn);
mDefaultNotificationLedOff = resources.getInteger(
com.android.internal.R.integer.config_defaultNotificationLedOff);
// Don't start allowing notifications until the setup wizard has run once.
// After that, including subsequent boots, init with notifications turned on.
// This works on the first boot because the setup wizard will toggle this
// flag at least once and we'll go back to 0 after that.
if (0 == Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.DEVICE_PROVISIONED, 0)) {
mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
}
// register for battery changed notifications
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(UsbManager.ACTION_USB_STATE);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
mContext.registerReceiver(mIntentReceiver, filter);
IntentFilter pkgFilter = new IntentFilter();
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
pkgFilter.addDataScheme("package");
mContext.registerReceiver(mIntentReceiver, pkgFilter);
IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mIntentReceiver, sdFilter);
SettingsObserver observer = new SettingsObserver(mHandler);
observer.observe();
}
void systemReady() {
// no beeping until we're basically done booting
mSystemReady = true;
}
// Toasts
// ============================================================================
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
if (pkg == null || callback == null) {
Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
return ;
}
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
// If it's already in the queue, we update it in place, we don't
// move it to the end of the queue.
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
// Limit the number of toasts that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (!"android".equals(pkg)) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
}
record = new ToastRecord(callingPid, pkg, callback, duration);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveLocked(callingPid);
}
// If it's at index 0, it's the current toast. It doesn't matter if it's
// new or just been updated. Call back and tell it to show itself.
// If the callback fails, this will remove it from the list, so don't
// assume that it's valid after this.
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
public void cancelToast(String pkg, ITransientNotification callback) {
Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
if (pkg == null || callback == null) {
Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
return ;
}
synchronized (mToastQueue) {
long callingId = Binder.clearCallingIdentity();
try {
int index = indexOfToastLocked(pkg, callback);
if (index >= 0) {
cancelToastLocked(index);
} else {
Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
private void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
record.callback.show();
scheduleTimeoutLocked(record, false);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " + record.callback
+ " in package " + record.pkg);
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
private void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
record.callback.hide();
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ " in package " + record.pkg);
// don't worry about this, we're about to remove it from
// the list anyway
}
mToastQueue.remove(index);
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
// Show the next one. If the callback fails, this will remove
// it from the list, so don't assume that the list hasn't changed
// after this point.
showNextToastLocked();
}
}
private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
{
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
mHandler.removeCallbacksAndMessages(r);
mHandler.sendMessageDelayed(m, delay);
}
private void handleTimeout(ToastRecord record)
{
if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
synchronized (mToastQueue) {
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index);
}
}
}
// lock on mToastQueue
private int indexOfToastLocked(String pkg, ITransientNotification callback)
{
IBinder cbak = callback.asBinder();
ArrayList<ToastRecord> list = mToastQueue;
int len = list.size();
for (int i=0; i<len; i++) {
ToastRecord r = list.get(i);
if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
return i;
}
}
return -1;
}
// lock on mToastQueue
private void keepProcessAliveLocked(int pid)
{
int toastCount = 0; // toasts from this pid
ArrayList<ToastRecord> list = mToastQueue;
int N = list.size();
for (int i=0; i<N; i++) {
ToastRecord r = list.get(i);
if (r.pid == pid) {
toastCount++;
}
}
try {
mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
} catch (RemoteException e) {
// Shouldn't happen.
}
}
private final class WorkerHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MESSAGE_TIMEOUT:
handleTimeout((ToastRecord)msg.obj);
break;
}
}
}
// Notifications
// ============================================================================
public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
{
enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut);
}
public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
int[] idOut)
{
enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
tag, id, notification, idOut);
}
// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
// uid/pid of another application)
public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idOut)
{
checkIncomingCall(pkg);
// Limit the number of notifications that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (!"android".equals(pkg)) {
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final NotificationRecord r = mNotificationList.get(i);
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " notifications. Not showing more. package=" + pkg);
return;
}
}
}
}
}
// Limit the number of notification requests, notify and cancel that
// a package can post. The MAX_PACKAGE_NOTIFICATIONS doesn't work for
// packages which notify and quickly cancel it and do this in an
// iteration
if (!"android".equals(pkg)) {
synchronized (mPackageInfo) {
if (!mPackageInfo.containsKey(pkg)) {
final PackageRequestInfo pInfo = new PackageRequestInfo();
pInfo.requestCount = 1;
pInfo.lastPostTime = SystemClock.elapsedRealtime();
mPackageInfo.put(pkg,pInfo);
}
else {
final PackageRequestInfo pInfo = mPackageInfo.get(pkg);
final long currentTime = SystemClock.elapsedRealtime();
if ((currentTime - pInfo.lastPostTime) <= NOTIFICATION_REQUEST_INTERVAL) {
// Keep track of requests posted within last 30 seconds
pInfo.requestCount++;
}
else {
pInfo.requestCount = 1;
pInfo.lastPostTime = SystemClock.elapsedRealtime();
}
if (pInfo.requestCount >= MAX_PACKAGE_NOTIFICATION_REQUESTS) {
// 500 requests within a span of 30 seconds is high
if (pInfo.requestCount%MAX_PACKAGE_NOTIFICATION_REQUESTS == 0) {
Slog.e(TAG, "Package has already posted too many notifications. "
+ "Not showing more. package=" + pkg);
}
return;
}
}
}
}
// This conditional is a dirty hack to limit the logging done on
// behalf of the download manager without affecting other apps.
if (!pkg.equals("com.android.providers.downloads")
|| Log.isLoggable("DownloadManager", Log.VERBOSE)) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, notification.toString());
}
if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
if (notification.icon != 0) {
if (notification.contentView == null) {
throw new IllegalArgumentException("contentView required: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
if (notification.contentIntent == null) {
throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
}
synchronized (mNotificationList) {
NotificationRecord r = new NotificationRecord(pkg, tag, id,
callingUid, callingPid, notification);
NotificationRecord old = null;
int index = indexOfNotificationLocked(pkg, tag, id);
if (index < 0) {
mNotificationList.add(r);
} else {
old = mNotificationList.remove(index);
mNotificationList.add(index, r);
// Make sure we don't lose the foreground service state.
if (old != null) {
notification.flags |=
old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
}
}
// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR;
}
if (notification.icon != 0) {
StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
r.uid, r.initialPid, notification);
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.updateNotification(r.statusBarKey, n);
}
finally {
Binder.restoreCallingIdentity(identity);
}
} else {
long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
mAttentionLight.pulse();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
sendAccessibilityEvent(notification, pkg);
} else {
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(old.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// If we're not supposed to beep, vibrate, etc. then don't.
if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& mSystemReady) {
final AudioManager audioManager = (AudioManager) mContext
.getSystemService(Context.AUDIO_SERVICE);
// sound
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != 0;
if (useDefaultSound || notification.sound != null) {
Uri uri;
if (useDefaultSound) {
uri = Settings.System.DEFAULT_NOTIFICATION_URI;
} else {
uri = notification.sound;
}
boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
int audioStreamType;
if (notification.audioStreamType >= 0) {
audioStreamType = notification.audioStreamType;
} else {
audioStreamType = DEFAULT_STREAM_TYPE;
}
mSoundNotification = r;
// do not play notifications if stream volume is 0
// (typically because ringer mode is silent).
if (audioManager.getStreamVolume(audioStreamType) != 0) {
long identity = Binder.clearCallingIdentity();
try {
mSound.play(mContext, uri, looping, audioStreamType);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// vibrate
final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
if ((useDefaultVibrate || notification.vibrate != null)
&& audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
mVibrateNotification = r;
mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
: notification.vibrate,
((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
}
}
// this option doesn't shut off the lights
// light
// the most recent thing gets the light
mLights.remove(old);
if (mLedNotification == old) {
mLedNotification = null;
}
//Slog.i(TAG, "notification.lights="
// + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
mLights.add(r);
updateLightsLocked();
} else {
if (old != null
&& ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
updateLightsLocked();
}
}
}
idOut[0] = id;
}
private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
if (!manager.isEnabled()) {
return;
}
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
event.setPackageName(packageName);
event.setClassName(Notification.class.getName());
event.setParcelableData(notification);
CharSequence tickerText = notification.tickerText;
if (!TextUtils.isEmpty(tickerText)) {
event.getText().add(tickerText);
}
manager.sendAccessibilityEvent(event);
}
private void cancelNotificationLocked(NotificationRecord r) {
// status bar
if (r.notification.icon != 0) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(r.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}
r.statusBarKey = null;
}
// sound
if (mSoundNotification == r) {
mSoundNotification = null;
long identity = Binder.clearCallingIdentity();
try {
mSound.stop();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
// vibrate
if (mVibrateNotification == r) {
mVibrateNotification = null;
long identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
// light
mLights.remove(r);
if (mLedNotification == r) {
mLedNotification = null;
}
}
/**
* Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
* and none of the {@code mustNotHaveFlags}.
*/
private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags,
int mustNotHaveFlags) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, mustHaveFlags);
synchronized (mNotificationList) {
int index = indexOfNotificationLocked(pkg, tag, id);
if (index >= 0) {
NotificationRecord r = mNotificationList.get(index);
if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
return;
}
if ((r.notification.flags & mustNotHaveFlags) != 0) {
return;
}
mNotificationList.remove(index);
cancelNotificationLocked(r);
updateLightsLocked();
}
}
}
/**
* Cancels all notifications from a given package that have all of the
* {@code mustHaveFlags}.
*/
boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags,
int mustNotHaveFlags, boolean doit) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, mustHaveFlags);
synchronized (mNotificationList) {
final int N = mNotificationList.size();
boolean canceledSomething = false;
for (int i = N-1; i >= 0; --i) {
NotificationRecord r = mNotificationList.get(i);
if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
continue;
}
if ((r.notification.flags & mustNotHaveFlags) != 0) {
continue;
}
if (!r.pkg.equals(pkg)) {
continue;
}
canceledSomething = true;
if (!doit) {
return true;
}
mNotificationList.remove(i);
cancelNotificationLocked(r);
}
if (canceledSomething) {
updateLightsLocked();
}
return canceledSomething;
}
}
public void cancelNotification(String pkg, int id) {
cancelNotificationWithTag(pkg, null /* tag */, id);
}
public void cancelNotificationWithTag(String pkg, String tag, int id) {
checkIncomingCall(pkg);
// Limit the number of notification requests, notify and cancel that
// a package can post. The MAX_PACKAGE_NOTIFICATIONS doesn't work for
// packages which notify and quickly cancel it and do this in an
// iteration
synchronized (mPackageInfo) {
if (!"android".equals(pkg) && mPackageInfo.containsKey(pkg)) {
final PackageRequestInfo pInfo = mPackageInfo.get(pkg);
final long currentTime = SystemClock.elapsedRealtime();
if ((currentTime - pInfo.lastPostTime) <= NOTIFICATION_REQUEST_INTERVAL) {
// Keep track of requests posted within last 30 seconds
pInfo.requestCount++;
}
else {
pInfo.requestCount = 1;
pInfo.lastPostTime = SystemClock.elapsedRealtime();
}
}
}
// Don't allow client applications to cancel foreground service notis.
cancelNotification(pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
? 0 : Notification.FLAG_FOREGROUND_SERVICE);
}
public void cancelAllNotifications(String pkg) {
checkIncomingCall(pkg);
// Calling from user space, don't allow the canceling of actively
// running foreground services.
cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true);
}
void checkIncomingCall(String pkg) {
int uid = Binder.getCallingUid();
if (uid == Process.SYSTEM_UID || uid == 0) {
return;
}
try {
ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(
pkg, 0);
if (ai.uid != uid) {
throw new SecurityException("Calling uid " + uid + " gave package"
+ pkg + " which is owned by uid " + ai.uid);
}
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException("Unknown package " + pkg);
}
}
void cancelAll() {
synchronized (mNotificationList) {
final int N = mNotificationList.size();
for (int i=N-1; i>=0; i--) {
NotificationRecord r = mNotificationList.get(i);
if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR)) == 0) {
if (r.notification.deleteIntent != null) {
try {
r.notification.deleteIntent.send();
} catch (PendingIntent.CanceledException ex) {
// do nothing - there's no relevant way to recover, and
// no reason to let this propagate
Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
}
}
mNotificationList.remove(i);
cancelNotificationLocked(r);
}
}
updateLightsLocked();
}
}
private void updateLights() {
synchronized (mNotificationList) {
updateLightsLocked();
}
}
// lock on mNotificationList
private void updateLightsLocked()
{
// Battery low always shows, other states only show if charging.
if (mBatteryLow) {
if (mBatteryCharging) {
mBatteryLight.setColor(BATTERY_LOW_ARGB);
} else {
// Flash when battery is low and not charging
mBatteryLight.setFlashing(BATTERY_LOW_ARGB, LightsService.LIGHT_FLASH_TIMED,
BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
}
} else if (mBatteryCharging) {
if (mBatteryFull) {
mBatteryLight.setColor(BATTERY_FULL_ARGB);
} else {
mBatteryLight.setColor(BATTERY_MEDIUM_ARGB);
}
} else {
mBatteryLight.turnOff();
}
// clear pending pulse notification if screen is on
if (mScreenOn || mLedNotification == null) {
mPendingPulseNotification = false;
}
// handle notification lights
if (mLedNotification == null) {
// get next notification, if any
int n = mLights.size();
if (n > 0) {
mLedNotification = mLights.get(n-1);
}
if (mLedNotification != null && !mScreenOn) {
mPendingPulseNotification = true;
}
}
// we only flash if screen is off and persistent pulsing is enabled
// and we are not currently in a call
if (!mPendingPulseNotification || mScreenOn || mInCall) {
mNotificationLight.turnOff();
} else {
int ledARGB = mLedNotification.notification.ledARGB;
int ledOnMS = mLedNotification.notification.ledOnMS;
int ledOffMS = mLedNotification.notification.ledOffMS;
if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
ledARGB = mDefaultNotificationColor;
ledOnMS = mDefaultNotificationLedOn;
ledOffMS = mDefaultNotificationLedOff;
}
if (mNotificationPulseEnabled) {
// pulse repeatedly
mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
ledOnMS, ledOffMS);
} else {
// pulse only once
mNotificationLight.pulse(ledARGB, ledOnMS);
}
}
}
// lock on mNotificationList
private int indexOfNotificationLocked(String pkg, String tag, int id)
{
ArrayList<NotificationRecord> list = mNotificationList;
final int len = list.size();
for (int i=0; i<len; i++) {
NotificationRecord r = list.get(i);
if (tag == null) {
if (r.tag != null) {
continue;
}
} else {
if (!tag.equals(r.tag)) {
continue;
}
}
if (r.id == id && r.pkg.equals(pkg)) {
return i;
}
}
return -1;
}
// This is here instead of StatusBarPolicy because it is an important
// security feature that we don't want people customizing the platform
// to accidentally lose.
private void updateAdbNotification(boolean adbEnabled) {
if (adbEnabled) {
if ("0".equals(SystemProperties.get("persist.adb.notify"))) {
return;
}
if (!mAdbNotificationShown) {
NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
Resources r = mContext.getResources();
CharSequence title = r.getText(
com.android.internal.R.string.adb_active_notification_title);
CharSequence message = r.getText(
com.android.internal.R.string.adb_active_notification_message);
if (mAdbNotification == null) {
mAdbNotification = new Notification();
mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_adb;
mAdbNotification.when = 0;
mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT;
mAdbNotification.tickerText = title;
mAdbNotification.defaults = 0; // please be quiet
mAdbNotification.sound = null;
mAdbNotification.vibrate = null;
}
Intent intent = new Intent(
Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
// Note: we are hard-coding the component because this is
// an important security UI that we don't want anyone
// intercepting.
intent.setComponent(new ComponentName("com.android.settings",
"com.android.settings.DevelopmentSettings"));
PendingIntent pi = PendingIntent.getActivity(mContext, 0,
intent, 0);
mAdbNotification.setLatestEventInfo(mContext, title, message, pi);
mAdbNotificationShown = true;
notificationManager.notify(
com.android.internal.R.string.adb_active_notification_title,
mAdbNotification);
}
}
} else if (mAdbNotificationShown) {
NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
mAdbNotificationShown = false;
notificationManager.cancel(
com.android.internal.R.string.adb_active_notification_title);
}
}
}
private void updateNotificationPulse() {
synchronized (mNotificationList) {
updateLightsLocked();
}
}
// ======================================================================
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump NotificationManager from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
}
pw.println("Current Notification Manager state:");
int N;
synchronized (mToastQueue) {
N = mToastQueue.size();
if (N > 0) {
pw.println(" Toast Queue:");
for (int i=0; i<N; i++) {
mToastQueue.get(i).dump(pw, " ");
}
pw.println(" ");
}
}
synchronized (mNotificationList) {
N = mNotificationList.size();
if (N > 0) {
pw.println(" Notification List:");
for (int i=0; i<N; i++) {
mNotificationList.get(i).dump(pw, " ", mContext);
}
pw.println(" ");
}
N = mLights.size();
if (N > 0) {
pw.println(" Lights List:");
for (int i=0; i<N; i++) {
mLights.get(i).dump(pw, " ", mContext);
}
pw.println(" ");
}
pw.println(" mSoundNotification=" + mSoundNotification);
pw.println(" mSound=" + mSound);
pw.println(" mVibrateNotification=" + mVibrateNotification);
pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
pw.println(" mSystemReady=" + mSystemReady);
}
}
}
在871行有这句代码:uri = notification.sound;在886行有这句代码:mSound.play(mContext, uri, looping, audioStreamType);当时我就有点兴奋了,感觉这就是问题的关键,然后打log,发现这就是问题的关键,当notification正常发声音的时候,这个886行的代码走进来了,不发声音的时候这个代码没有走进来,所以我离问题的根源又进了一步。最后发现是包着这段代码的if语句中的判断引起的,所以我把if语句中的代码都打印出来,发现是mDisabledNotifications这个变量引起的,我就追踪这个mDisabledNotifications变量值的变化的地方。发现在467行有这段代码:
// Don't start allowing notifications until the setup wizard has run once.
// After that, including subsequent boots, init with notifications turned on.
// This works on the first boot because the setup wizard will toggle this
// flag at least once and we'll go back to 0 after that.
if (0 == Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.DEVICE_PROVISIONED, 0)) {
mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
}
研究以上的注释,发现原来google故意这么设置的,至于google为什么要这么设置,我没有深究,暂时没有想明白,但是这个这个初始化的时候必须要tsetup wizard (设置向导)运行一次,所以导致了值不对,所以这个notification就不响了。
在264行有这段代码对mDisabledNotification进行改变的:
private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks
= new StatusBarManagerService.NotificationCallbacks() {
public void onSetDisabled(int status) {
synchronized (mNotificationList) {
mDisabledNotifications = status;
if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
// cancel whatever's going on
long identity = Binder.clearCallingIdentity();
try {
mSound.stop();
}
finally {
Binder.restoreCallingIdentity(identity);
}
identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}
找到问题的根源了,太兴奋了,这个问题断断续续困扰了我3周,终于经过我3天地认真分析,把问题的根源找到了,要想解决就很简单了,可以在初始化的时候直接赋值为0就ok了!
最后、886行mSound.play(mContext, uri, looping, audioStreamType);是NotificationPlayer类中的一个方法,在play()方法中有一个线程, mThread = new CmdThread(); mThread.start();线程中的run方法中有:case PLAY:startSound(cmd);在startSound()方法中又有一个线程: mCompletionThread = new CreationAndCompletionThread(cmd);
mCompletionThread = new CreationAndCompletionThread(cmd);
synchronized(mCompletionThread) {
mCompletionThread.start();
mCompletionThread.wait();
}
在这个线程类中的run方法中:在这个线程中进行播放音乐的 ,真正的发声音也是通过Mediapaly来实现的:
public void run() { Looper.prepare(); mLooper = Looper.myLooper(); synchronized(this) { AudioManager audioManager = (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE); try { MediaPlayer player = new MediaPlayer(); player.setAudioStreamType(mCmd.stream); player.setDataSource(mCmd.context, mCmd.uri); player.setLooping(mCmd.looping); player.prepare(); if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null) && (mCmd.uri.getEncodedPath().length() > 0)) { if (mCmd.looping) { audioManager.requestAudioFocus(null, mCmd.stream, AudioManager.AUDIOFOCUS_GAIN); } else { audioManager.requestAudioFocus(null, mCmd.stream, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); } } player.setOnCompletionListener(NotificationPlayer.this); player.start(); if (mPlayer != null) { mPlayer.release(); } mPlayer = player; } catch (Exception e) { Log.w(mTag, "error loading sound for " + mCmd.uri, e); } mAudioManager = audioManager; this.notify(); } Looper.loop(); }
希望给读者留下点启发,转载请标明出处:http://blog.csdn.net/wdaming1986/article/details/7081787