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

2024-01-02 18:38

本文主要是介绍android 4.0.3 usb插拔提示音播放问题分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:
最近客户看见别的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的这个通知服务。

不知道是否真是这样,也没有进一步跟进,希望懂的朋友讲一讲这是为何!

这篇关于android 4.0.3 usb插拔提示音播放问题分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/563319

相关文章

linux生产者,消费者问题

pthread_cond_wait() :用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

2024.6.24 IDEA中文乱码问题(服务器 控制台 TOMcat)实测已解决

1.问题产生原因: 1.文件编码不一致:如果文件的编码方式与IDEA设置的编码方式不一致,就会产生乱码。确保文件和IDEA使用相同的编码,通常是UTF-8。2.IDEA设置问题:检查IDEA的全局编码设置和项目编码设置是否正确。3.终端或控制台编码问题:如果你在终端或控制台看到乱码,可能是终端的编码设置问题。确保终端使用的是支持你的文件的编码方式。 2.解决方案: 1.File -> S

vcpkg安装opencv中的特殊问题记录(无法找到opencv_corexd.dll)

我是按照网上的vcpkg安装opencv方法进行的(比如这篇:从0开始在visual studio上安装opencv(超详细,针对小白)),但是中间出现了一些别人没有遇到的问题,虽然原因没有找到,但是本人给出一些暂时的解决办法: 问题1: 我在安装库命令行使用的是 .\vcpkg.exe install opencv 我的电脑是x64,vcpkg在这条命令后默认下载的也是opencv2:x6

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

问题-windows-VPN不正确关闭导致网页打不开

为什么会发生这类事情呢? 主要原因是关机之前vpn没有关掉导致的。 至于为什么没关掉vpn会导致网页打不开,我猜测是因为vpn建立的链接没被更改。 正确关掉vpn的时候,会把ip链接断掉,如果你不正确关掉,ip链接没有断掉,此时你vpn又是没启动的,没有域名解析,所以就打不开网站。 你可以在打不开网页的时候,把vpn打开,你会发现网络又可以登录了。 方法一 注意:方法一虽然方便,但是可能会有

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

android 免费短信验证功能

没有太复杂的使用的话,功能实现比较简单粗暴。 在www.mob.com网站中可以申请使用免费短信验证功能。 步骤: 1.注册登录。 2.选择“短信验证码SDK” 3.下载对应的sdk包,我这是选studio的。 4.从头像那进入后台并创建短信验证应用,获取到key跟secret 5.根据技术文档操作(initSDK方法写在setContentView上面) 6.关键:在有用到的Mo

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

Android我的二维码扫描功能发展史(完整)

最近在研究下二维码扫描功能,跟据从网上查阅的资料到自己勉强已实现扫描功能来一一介绍我的二维码扫描功能实现的发展历程: 首页通过网络搜索发现做android二维码扫描功能看去都是基于google的ZXing项目开发。 2、搜索怎么使用ZXing实现自己的二维码扫描:从网上下载ZXing-2.2.zip以及core-2.2-source.jar文件,分别解压两个文件。然后把.jar解压出来的整个c