您的位置:首页 > 移动开发 > Android开发

android 4.0.3 usb插拔提示音播放问题分析

2012-10-10 17:22 375 查看
前言:

最近客户看见别的android4.0.3机器插拔usb有播放提示音,而我们的机器没有。客户就开始抱怨了。

公司没有做应用的人,没办法,让我这个对java半桶水的人搞,哎,只有硬着头皮弄。

刚接到手,根本不知道从哪里开始,也不知道需要设置什么属性(后来看到源码里有读属性才知道),悲剧的很。

按理说,这种通知提示google应该是早就形成机制做好的,只要配置好,应该就可以了,不过事情并不是这么简单。...

无奈,logcat一下插拔usb时候的log,发现这个关键TAG:StorageNotification,就从这里开始。

1. StorageNotification.java

frameworks/base/packages/SystemUI/src/com/android/systemui/usb/

其实主要是看到了这一句log:

Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st));

所以就从这里开始分析:

private void onUsbMassStorageConnectionChangedAsync(boolean connected) {

mUmsAvailable = connected;

/*

* Even though we may have a UMS host connected, we the SD card

* may not be in a state for export.

*/

String st = Environment.getExternalStorageState();

Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st));

if (connected && (st.equals(

Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {

/*

* No card or card being checked = don't display

*/

connected = false;

}

updateUsbMassStorageNotification(connected);

}

参数connected表示usb是否已插入。

/* private Notification mUsbStorageNotification;

关于Notification和NotificationManager使用参考网址文章:
http://yuanyao.iteye.com/blog/472332 http://blog.csdn.net/feng88724/article/details/6259071 http://www.cnblogs.com/stay/articles/1898963.html
*/

void updateUsbMassStorageNotification(boolean available) {

if (available) {

Intent intent = new Intent();

intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);

setUsbStorageNotification(

com.android.internal.R.string.usb_storage_notification_title,

com.android.internal.R.string.usb_storage_notification_message,

com.android.internal.R.drawable.stat_sys_data_usb,

false, true, pi);

} else {

setUsbStorageNotification(0, 0, 0, false, false, null);

}

}

通过函数setUsbStorageNotification()的原型可以看出:

private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,

boolean sound, boolean visible, PendingIntent pi);

第四个参数表示是否播放声音,看到这里我万分激动将这个false改成true,不过郁闷的时,插拔usb没有声音出来。

再来看setUsbStorageNotification()函数的实现,基本上都是按照上面几篇文章介绍的那样来使用,第四个参数为true就设置使用默认提示音。

private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,

boolean sound, boolean visible, PendingIntent pi) {

if (!visible && mUsbStorageNotification == null) {

return;

}// 通知是否正常的逻辑判断

NotificationManager notificationManager = (NotificationManager) mContext

.getSystemService(Context.NOTIFICATION_SERVICE);// 获取notificationManager实例,用来和notificationManagerService通信。

if (notificationManager == null) {

return;

}

if (visible) { // 从上面传下来的参数和log看,只有在插入usb的时候这个visible才为true,拔出为false。

Resources r = Resources.getSystem();

CharSequence title = r.getText(titleId);// 获取显示在状态栏上的标题

CharSequence message = r.getText(messageId);// 获取显示在标题下的说明文字

if (mUsbStorageNotification == null) {

mUsbStorageNotification = new Notification();// 创建一个通知的实例

mUsbStorageNotification.icon = icon;// 获取显示在状态栏上的图标

mUsbStorageNotification.when = 0;

}

if (sound) { // 这里就是声音的设置了,要么使用默认,要么自己指定。

mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;

} else {

mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;

}

// mUsbStorageNotification.sound = Uri.parse("file:///sdcard/notification/ringer.mp3");

// mUsbStorageNotification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");

mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; // 把它放在“正在运行”组中.

mUsbStorageNotification.tickerText = title;

if (pi == null) {

Intent intent = new Intent();

pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);

}

mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);// 设置点击该通知之后执行跳转的界面

final boolean adbOn = 1 == Settings.Secure.getInt(

mContext.getContentResolver(),

Settings.Secure.ADB_ENABLED,

0);// adb 是否使能

if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {

// Pop up a full-screen alert to coach the user through enabling UMS. The average

// user has attached the device to USB either to charge the phone (in which case

// this is harmless) or transfer files, and in the latter case this alert saves

// several steps (as well as subtly indicates that you shouldn't mix UMS with other

// activities on the device).

//

// If ADB is enabled, however, we suppress this dialog (under the assumption that a

// developer (a) knows how to enable UMS, and (b) is probably using USB to install

// builds or use adb commands.

mUsbStorageNotification.fullScreenIntent = pi;// 如果adb调试模式没使能,直接弹出设置的全屏界面

}

}

final int notificationId = mUsbStorageNotification.icon;

if (visible) {

notificationManager.notify(notificationId, mUsbStorageNotification);// 调用NotificationManager的notify函数发出这个通知

} else {

notificationManager.cancel(notificationId);// 如果是拔出usb,取消掉这个通知,当然关闭显示出来的UsbStorageActivity界面的话

// 是在同目录下的UsbStorageActivity.java中监听后直接finish掉的。

}

}

关于Notification更多的参数设置,还是直接看源码的好:frameworks/base/core/java/android/app/Notification.java

2. NotificationManager.java

frameworks/base/core/java/android/app/

这个文件是NotificationManagerService的客户端proxy实现。

public class NotificationManager

{

...

private static INotificationManager sService;

/** @hide */

static public INotificationManager getService()

{

if (sService != null) {

return sService;

}

IBinder b = ServiceManager.getService("notification");

sService = INotificationManager.Stub.asInterface(b);// 取得真正binder服务notification的BinderProxy实例。

return sService;

}

...

public void notify(int id, Notification notification)

{

notify(null, id, notification);

}

...

public void notify(String tag, int id, Notification notification)

{

int[] idOut = new int[1];

INotificationManager service = getService();

String pkg = mContext.getPackageName();

if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");

try {

service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut);// 调用远程服务的enqueueNotificationWithTag函数。

if (id != idOut[0]) {

Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);

}

} catch (RemoteException e) {

}

}

3. NotificationManagerService.java

frameworks/base/services/java/com/android/server/

public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,

int[] idOut)

{

enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),

tag, id, notification, idOut);

}

public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,

String tag, int id, Notification notification, int[] idOut)

{

enqueueNotificationInternal(pkg, callingUid, callingPid, tag, id,

((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0)

? StatusBarNotification.PRIORITY_ONGOING

: StatusBarNotification.PRIORITY_NORMAL,

notification, idOut);

}

public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,

String tag, int id, int priority, Notification notification, int[] idOut)

{

...

// 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;// 如果使用默认声音,取得系统默认声音的uri值。

} else {

uri = notification.sound;// 自己指定的Uri值。

}

boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; // 是否设置了重复播放的flag。

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);

}

}

...

}

虽然找到了是在这里播放的声音,但是默认声音的来源在哪里设置的呢?这个还需要继续查找。

4. Settings.System.DEFAULT_NOTIFICATION_URI

将希望寄托在DEFAULT_NOTIFICATION_URI上,搜索后在文件frameworks/base/core/java/android/provider/Settings.java中看到了它的定义:

public static final String NOTIFICATION_SOUND = "notification_sound";

public static final Uri DEFAULT_NOTIFICATION_URI = getUriFor(NOTIFICATION_SOUND);

搜索getUriFor()函数最后的实现,还是使用Uri.withAppendedPath()函数来得到Uri的。

getUriFor(String name) --> getUriFor(CONTENT_URI, name)

public static final Uri CONTENT_URI =

Uri.parse("content://" + AUTHORITY + "/system");

看似好像断了线索,不过还是用NOTIFICATION_SOUND搜索出了一些东西:

frameworks/base/media/java/android/media/MediaScanner.java + 896

if (notifications && mWasEmptyPriorToScan && !mDefaultNotificationSet) {

if (TextUtils.isEmpty(mDefaultNotificationFilename) ||

doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {

setSettingIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId); //设置Uri

mDefaultNotificationSet = true;

}

}

看看mDefaultNotificationFilename变量如何赋值:

private void setDefaultRingtoneFileNames() {

mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX

+ Settings.System.RINGTONE);

mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX

+ Settings.System.NOTIFICATION_SOUND);

mDefaultAlarmAlertFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX

+ Settings.System.ALARM_ALERT);

}

可以看到这里默认的ringtone、notification、alarm都是在这里通过系统属性获得的。

这个文件中定义了系统属性的前缀private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";

后缀就是诸如Settings.System.NOTIFICATION_SOUND = “notification_sound”的名字,定义于前面的文件:

frameworks/base/core/java/android/provider/Settings.java

实际上就是 ro.config.notification_sound

ro.config.ringtone

ro.config.alarm_alert

我就找了一下我们系统中根本就没有设置这些个属性,激动万分地添加上这个属性ro.config.notification_sound,不过还是没响,郁闷了。为什么了?

仔细检查了下,原来ogg的名字写错了,本来是:

PRODUCT_PROPERTY_OVERRIDES += \

ro.config.notification_sound=Drip.ogg

结果写成了

PRODUCT_PROPERTY_OVERRIDES += \

ro.config.notification_sound=Grip.ogg

改正之后,终于在插入usb之后听到了提示音,心里那个激动啊,不过凉水来了,拔出来的时候没有声音啊, 而且插入时候应该播放一遍啊,为什么连着播了3-4次,还混响呢,靠这是为什么啊?

5. 拔出无声音和插入播多次的问题

插入播多次的问题通过log看出,插入的时候调用了3-4次onUsbMassStorageConnectionChangedAsync()这个函数,为什么?我也不清楚,后来就直接在这个函数中使用变量屏蔽掉了后面的多次调用,只让第一次有效。呵呵,是不是有点 流氓了?!

private void onUsbMassStorageConnectionChangedAsync(boolean connected) {

mUmsAvailable = connected;

/*

* Even though we may have a UMS host connected, we the SD card

* may not be in a state for export.

*/

String st = Environment.getExternalStorageState();

Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st));

/******************/

if((mCount == 0) && connected){

mCount = 1;
// 插入后第一次调这个函数

}else if((mCount == 1) && !connected){

mCount = 0;
// 拔出后清0

}else{

return; // 插入后面的多次调用,直接返回。

}

/******************/

if (connected && (st.equals(

Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {

/*

* No card or card being checked = don't display

*/

connected = false;

}

updateUsbMassStorageNotification(connected);

}

对于拔出无声音这个问题,看看源码才知道,拔出的时候只是cancel了这个Notification,更别没有在发出通知:

所以后来索性多加了一个通知实例,专门用在拔出的时候发出去,而这个通知没有图标,标题,说明,更具不会显示在状态栏,只是播放默认提示音。

写的时候根本不知道能不能实现,谁知道居然可以实现。

private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,

boolean sound, boolean visible, PendingIntent pi) {

if (!visible && mUsbStorageNotification == null) {

return;

}

NotificationManager notificationManager = (NotificationManager) mContext

.getSystemService(Context.NOTIFICATION_SERVICE);

if (notificationManager == null) {

return;

}

if (visible) {

...

}else{ // 拔出的时候

if (mRemovedUsbStorageNotification == null) {

mRemovedUsbStorageNotification = new Notification();

mRemovedUsbStorageNotification.icon = icon; // icon传入的为0

mRemovedUsbStorageNotification.when = 0;

}

if (sound) { // 当然这里一定要是True了。

mRemovedUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;

} else {

mRemovedUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;

}

mRemovedUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;

}

final int notificationId = mUsbStorageNotification.icon;

// mRemovedUsbStorageNotificationID 定义成了0,应该是可以随便定义。

if (visible) {

if(!mIsFrist){

notificationManager.cancel(mRemovedUsbStorageNotificationID);

}// 第一次插入不需要cancel removed通知实例,以免出现混乱。

notificationManager.notify(notificationId, mUsbStorageNotification);

} else { // 拔出usb的时候先发出这个removed通知来播放提示音。

notificationManager.notify(mRemovedUsbStorageNotificationID, mRemovedUsbStorageNotification);

notificationManager.cancel(notificationId); // 再接着取消掉插入时候的通知实例

if(mIsFrist) mIsFrist = false;

// mIsFrist的设置是为了开机第一次插入,就不需要cancel removed这个通知实例,以后每次插之前先cancel removed的实例。

}

}

不要忘记了在调用函数setUsbStorageNotification()第四个关于sound的参数一定要设置成true。

这样已修改,插拔都有声音,而且也没有了多次播放的现象。不过另一个问题来了,关机的时候还播放了一次。看了下源码,另外一个地方也调用了函数

setUsbStorageNotification(),肯定是那里在作怪了。

6. 关机还播放一次

StorageNotification.java文件中onStorageStateChangedAsync()函数还调用了setUsbStorageNotification()函数。在不知道是什么情况下调用了它,也不知道走的哪条路径调用了这个函数,所以决定再次流氓下:

将其中所有的updateUsbMassStorageNotification(false);调用换成:

setUsbStorageNotification(0, 0, 0, false, false, null); // 第四参数为false,表示不响声音。

将其中所有的updateUsbMassStorageNotification(mUmsAvailable);调用换成:

if(mUmsAvailable)

updateUsbMassStorageNotification(mUmsAvailable);

else

setUsbStorageNotification(0, 0, 0, false, false, null);

也就是将mUmsAvailable为false的情况特殊化,因为关机log中得到的log:

Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st));

connected为false。

7. 遗留问题

第一次烧录开机,或者恢复出厂设置之后第一次启动,在锁屏界面刚刚出现就马上进行插拔usb测试,这个时候是没有声音播放出来的,

但是过一会,或者休眠一下,就可以正常听见提示音。

观察log也没有看到NotificationManagerService中的相关信息打印出来。

初步推断是,binder服务线程数量为15的限制,导致这种情况下,已经没有空闲线程来为usb的这个通知服务。

不知道是否真是这样,也没有进一步跟进,希望懂的朋友讲一讲这是为何!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: