android的notificaiton的声音sound也是申请的AudioManager机制来播放声音的。最近让我找恢复出厂设置后,手机刚启动,接受短信没有声音,如果恢复出厂设置后,等一会儿,过个2分钟再接受短信,就有铃声了。下面我把我分析代码的方法写下来,给自己和读者一些启发:
日历也是用的是Notification,但是恢复出厂设置后,立马设置日历后,日历可以出声音,我看日历的代码,结果发现日历只是用了Notification的闪屏,真正声音是日历自己实现了Mediaplayer来出声音的。所以我又不得不老老实实地研究Notification.sound到底把声音传递到什么地方去了?
转载请标明出处:http://blog.youkuaiyun.com/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来实现的:
- <span style="font-size:13px;"> 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();
- }</span>