Android 4.2 SafeVolume机制

来源:互联网 发布:ubuntu安装unity3d 编辑:程序博客网 时间:2024/05/22 17:07

最近一个项目过认证, 在声压测试时failed。整改方案为:在用户将耳机音量提高至安全音量以上时,阻止此操作并弹出警告框,待用户确认后才提升音量。一开始并不知道android4.2中默认自带了这套机制,于是自己瞎琢磨添加了一套机制:在Audioervice中监听插入耳机时的音量变化,发送广播到SystemUI,再在SystemUI中注册Receiver去判断是否需要弹出警告框。历经千辛万苦终于把流程可以跑通,功能也可以实现,但有一个小BUG,就是用户当前提升的音量超过了安全音量,弹出警告框的同时音量也已经完成调节动作,不符合整改方案的要求。后来,无意中看到AudioService中有SafeVolume之类的变量,才恍然大悟,原来android默认就支持这个功能,于是将之前瞎琢磨的那套怒而删之,仔细研究如何开启android默认的机制, 以下是小小的心得,特此记录。

----------------------------------------------------------------------------------------------------

代码位置:frameworks/base/media/java/android/media/AudioService.java

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public AudioService(Context context) {  
  2.         ..........  //省略  
  3.         mSafeMediaVolumeState = new Integer(Settings.Global.getInt(mContentResolver,Settings.Global.AUDIO_SAFE_VOLUME_STATE,SAFE_MEDIA_VOLUME_NOT_CONFIGURED));   
  4.         mSafeMediaVolumeIndex = mContext.getResources().getInteger(com.android.internal.R.integer.config_safe_media_volume_index) * 10;  
  5.          .......... //省略  
  6. }  

在AudioService的构造函数中,有关于安全音量的初始化设置,从配置文件中读取是否配置安全媒体音量以及安全音量的大小。

是否enable的配置文件在frameworks/base/core/res/res/values-Mcc码 文件夹中:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <!-- Whether safe headphone volume is enabled or not (country specific). -->  
  2. <bool name="config_safe_media_volume_enabled">true</bool>  

goole了一下,中国的mcc码值为460, mnc为00或者01(对应移动或者联通), 我的这套android代码中没有这个文件夹, 我自己创建了一个并赋值。
配置安全媒体音量大小的文件在:frameworks/base/core/res/res/values/Config.xml中:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <!-- Safe headphone volume index. When music stream volume is below this index the SPL on headphone output is compliant to EN 60950 requirements for portable music players. -->  
  2. <integer name="config_safe_media_volume_index">5</integer>  

这个index的大小可以根据实际情况进行调整。
再次编译烧录,插入耳机播放音乐,调整音量至第五个梯度时,弹出警告框。
接下来研究这套机制的具体实现流程:
还是刚才的frameworks/base/media/java/android/media/AudioService.java构造方法:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public AudioService(Context context) {  
  2.       .................  //省略  
  3.       IntentFilter intentFilter = .....;  
  4.       intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);  
  5.       .................. //省略  
  6.       context.registerReceiver(mReceiver, intentFilter);  
  7. }  

AudioService的构造方法中监听BOOT_COMPLETED事件,当机器BOOT完成后,做如下操作:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private class AudioServiceBroadcastReceiver extends BroadcastReceiver {  
  2.         @Override  
  3.          public void onReceive(Context context, Intent intent) {  
  4.               ..................  
  5.               } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {  
  6.                         ................  
  7.                         sendMsg(mAudioHandler, MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED, SENDMSG_REPLACE, 00,SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);  
  8.                         ................  
  9.               }  
  10.               ..................  
  11. }  
  12. @Override  
  13. public void handleMessage(Message msg) {  
  14.      ..........................  
  15.      case MSG_CHECK_MUSIC_ACTIVE:  
  16.               onCheckMusicActive();  
  17.      break;  
  18.      case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:  
  19.      case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:  
  20.               onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED));   
  21.      break;  
  22.      case MSG_PERSIST_SAFE_VOLUME_STATE:  
  23.               onPersistSafeVolumeState(msg.arg1);  
  24.      break;  
  25.      ........................  
  26. }  

开机完成后,AudioService接受到BOOT_COMPLETED, 告诉Receiver处理,Receviver通过Handler处理,配置SafeVolume。
现在看看onConfigureSafeVolume中做了哪些操作:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private void onConfigureSafeVolume(boolean force) {  
  2.         synchronized (mSafeMediaVolumeState) {  
  3.             int mcc = mContext.getResources().getConfiguration().mcc;  
  4.             if ((mMcc != mcc) || ((mMcc == 0) && force) {  
  5.                 mSafeMediaVolumeIndex = mContext.getResources().getInteger(  
  6.                         com.android.internal.R.integer.config_safe_media_volume_index) * 10;  
  7.                 boolean safeMediaVolumeEnabled = mContext.getResources().getBoolean(  
  8.                         com.android.internal.R.bool.config_safe_media_volume_enabled);  
  9.   
  10.                 // The persisted state is either "disabled" or "active": this is the state applied  
  11.                 // next time we boot and cannot be "inactive"  
  12.                 int persistedState;  
  13.                 if (safeMediaVolumeEnabled) {  
  14.                     persistedState = SAFE_MEDIA_VOLUME_ACTIVE;  
  15.                     // The state can already be "inactive" here if the user has forced it before  
  16.                     // the 30 seconds timeout for forced configuration. In this case we don't reset  
  17.                     // it to "active".  
  18.                     if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {  
  19.                         mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;  
  20.                         enforceSafeMediaVolume();  
  21.                     }  
  22.                 } else {  
  23.                     persistedState = SAFE_MEDIA_VOLUME_DISABLED;  
  24.                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;  
  25.                 }  
  26.                 mMcc = mcc;  
  27.                 sendMsg(mAudioHandler,  
  28.                         MSG_PERSIST_SAFE_VOLUME_STATE,  
  29.                         SENDMSG_QUEUE,  
  30.                         persistedState,  
  31.                         0,  
  32.                         null,  
  33.                         0);  
  34.             }  
  35.         }  
  36.     }  

流程很清晰,判断当前能否读取到mcc设置,我的理解是当前是否有SIM卡插入,不知道理解的对不对。根据mcc的值重新读取safeMediaVolumeIndex 和 safeMediaVolumeEnabled 的设置。如果启用了safeMediaVolume, 将mSafeMediaVolumeState 设置为激活状态, 并调用enforceSafeMediaVolume().
再看enforceSafeMediaVolume()中做了些什么:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private void enforceSafeMediaVolume() {  
  2.         /* add by quinn start 2014-05-10 */  
  3.         //VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];  
  4.         VolumeStreamState streamState;  
  5.         if (AudioSystem.isStreamActive(AudioSystem.STREAM_ATV, 0)) { /* now  ATV is active */  
  6.             streamState = mStreamStates[AudioSystem.STREAM_ATV];  
  7.         } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_FM, 0)) { /* now FM is active */  
  8.             streamState = mStreamStates[AudioSystem.STREAM_FM];  
  9.         } else { /* default music is active */  
  10.             streamState = mStreamStates[AudioSystem.STREAM_MUSIC];  
  11.         }  
  12.         /* add by quinn end 2014-05-10 */  
  13.         boolean lastAudible = (streamState.muteCount() != 0);  
  14.         int devices = mSafeMediaVolumeDevices;  
  15.         int i = 0;  
  16.   
  17.         while (devices != 0) {  
  18.             int device = 1 << i++;  
  19.             if ((device & devices) == 0) {  
  20.                 continue;  
  21.             }  
  22.             int index = streamState.getIndex(device, lastAudible);  
  23.             if (index > mSafeMediaVolumeIndex) {  
  24.                 if (lastAudible) {  
  25.                     streamState.setLastAudibleIndex(mSafeMediaVolumeIndex, device);  
  26.                     sendMsg(mAudioHandler,  
  27.                             MSG_PERSIST_VOLUME,  
  28.                             SENDMSG_QUEUE,  
  29.                             PERSIST_LAST_AUDIBLE,  
  30.                             device,  
  31.                             streamState,  
  32.                             PERSIST_DELAY);  
  33.                 } else {  
  34.                     streamState.setIndex(mSafeMediaVolumeIndex, device, true);  
  35.                     sendMsg(mAudioHandler,  
  36.                             MSG_SET_DEVICE_VOLUME,  
  37.                             SENDMSG_QUEUE,  
  38.                             device,  
  39.                             0,  
  40.                             streamState,  
  41.                             0);  
  42.                 }  
  43.             }  
  44.             devices &= ~device;  
  45.         }  
  46.     }  

android 默认只会对AudioSystem.STREAM_MUSIC进行安全媒体音量的检查,而我们的项目中有FM 和 ATV 功能,所以添加了个判断。此方法中,根据当前激活的音频状态,判断音量大小是否超过了安全音量,如果是则强制恢复到安全音量。对应到实际使用场景中即为: 用户在插入耳机后将音量调整至安全音量以上,再次开机后会强制恢复音量。
现在关于开机部分的安全音量机制已经走了一遍,对于在插入耳机时,用户通过音量按键或者panel去控制音量调整时的流程又是怎么样的呢, 继续往下走:
阅读代码发现,调节音量时会调用AudioService的setStreamVolume() adjustStreamVolume()方法,而在这两个方法中都有一个!checkSafeMediaVolume()的判断,直接贴该方法的实现:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private boolean checkSafeMediaVolume(int streamType, int index, int device) {  
  2.        /* add by quinn start 2014-05-10 */  
  3.        boolean musicOrFmOrATVActive = (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC ||   
  4.                mStreamVolumeAlias[streamType] == AudioSystem.STREAM_FM ||   
  5.                mStreamVolumeAlias[streamType] == AudioSystem.STREAM_ATV);  
  6.       /* add by quinn end 2014-05-10 */   
  7.        synchronized (mSafeMediaVolumeState) {  
  8.            if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) &&  
  9.                    /* add by quinn start 2014-05-10 */  
  10.                    //(mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&  
  11.                    musicOrFmOrATVActive &&  
  12.                    /* add by quinn end 2014-05-10 */  
  13.                    ((device & mSafeMediaVolumeDevices) != 0) &&  
  14.                    (index > mSafeMediaVolumeIndex)) {  
  15.                mVolumePanel.postDisplaySafeVolumeWarning();  
  16.                return false;  
  17.            }  
  18.            return true;  
  19.        }  
  20.    }  

看代码可以发现,当要设置的音量超过安全音量时,会调用VolumePanel的postDisplaySafeVolumeWarning()方法,最终实现为onDisplaySafeVolumeWarrning(),即为需要用户确认的警告框,方法如下:

frameworks/base/core/java/android/view/VolumePanel.java

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. protected void onDisplaySafeVolumeWarning() {  
  2.         synchronized (sConfirmSafeVolumeLock) {  
  3.             if (sConfirmSafeVolumeDialog != null) {  
  4.                 return;  
  5.             }  
  6.             sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)  
  7.                     .setMessage(com.android.internal.R.string.safe_media_volume_warning)  
  8.                     .setPositiveButton(com.android.internal.R.string.yes,  
  9.                                         new DialogInterface.OnClickListener() {  
  10.                         public void onClick(DialogInterface dialog, int which) {  
  11.                             mAudioService.disableSafeMediaVolume();  
  12.                         }  
  13.                     })  
  14.                     .setNegativeButton(com.android.internal.R.string.no, null)  
  15.                     .setIconAttribute(android.R.attr.alertDialogIcon)  
  16.                     .create();  
  17.             final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,  
  18.                     sConfirmSafeVolumeDialog);  
  19.   
  20.             sConfirmSafeVolumeDialog.setOnDismissListener(warning);  
  21.             sConfirmSafeVolumeDialog.getWindow().setType(  
  22.                                                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);  
  23.             sConfirmSafeVolumeDialog.show();  
  24.         }  
  25.     }  

当用户点击确认按钮时,调用AudioService的disableSafeMediaVolume()方法:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void disableSafeMediaVolume() {  
  2.         synchronized (mSafeMediaVolumeState) {  
  3.             setSafeMediaVolumeEnabled(false);  
  4.         }  
  5.     }  
  6.   
  7. private void setSafeMediaVolumeEnabled(boolean on) {  
  8.         synchronized (mSafeMediaVolumeState) {  
  9.             if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&  
  10.                     (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {  
  11.                 if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {  
  12.                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;  
  13.                     enforceSafeMediaVolume();  
  14.                 } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {  
  15.                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;  
  16.                     mMusicActiveMs = 0;  
  17.                     sendMsg(mAudioHandler,  
  18.                             MSG_CHECK_MUSIC_ACTIVE,  
  19.                             SENDMSG_REPLACE,  
  20.                             0,  
  21.                             0,  
  22.                             null,  
  23.                             MUSIC_ACTIVE_POLL_PERIOD_MS);  
  24.                 }  
  25.             }  
  26.         }  
  27.     }  

最终调用setSafeMediaVolumeEnabled()方法,将mSafeMediaVolumeState 状态设置为SAFE_MEDIA_VOLUME_INACTIVE, 这样再次进入checkSafeMediaVolume()时,则会返回ture,这样音量就能成功设置。
anroid的这套机制还有另外一个功能,就是在安全音量以上累积播放20H会再次弹出警告,实现如下:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private void onSetWiredDeviceConnectionState(int device, int state, String name)  
  2.     {  
  3.         synchronized (mConnectedDevices) {  
  4.             ............  
  5.                 if ((device & mSafeMediaVolumeDevices) != 0) {  
  6.                     sendMsg(mAudioHandler,  
  7.                             MSG_CHECK_MUSIC_ACTIVE,  
  8.                             SENDMSG_REPLACE,  
  9.                             0,  
  10.                             0,  
  11.                             null,  
  12.                             MUSIC_ACTIVE_POLL_PERIOD_MS);  
  13.                 }  
  14.             }  
  15.             ...........  
  16.     }  
  17.   
  18. private void onCheckMusicActive() {  
  19.         synchronized (mSafeMediaVolumeState) {  
  20.             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {  
  21.                 /* add by quinn start 2014-05-10 */  
  22.                 int device, index;  
  23.                 boolean streamATVIsActive = false, streamFMIsActive = false, streamMusicIsActive = false, musicOrFMOrATVActive = false;  
  24.                 if (AudioSystem.isStreamActive(AudioSystem.STREAM_ATV, 0)) {  
  25.                     device = getDeviceForStream(AudioSystem.STREAM_ATV);  
  26.                     index = mStreamStates[AudioSystem.STREAM_ATV].getIndex(device, false);  
  27.                     streamATVIsActive = true;  
  28.                 } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_FM, 0)) {  
  29.                     device = getDeviceForStream(AudioSystem.STREAM_FM);  
  30.                     index = mStreamStates[AudioSystem.STREAM_FM].getIndex(device, false);  
  31.                     streamFMIsActive = true;  
  32.                 } else {  
  33.                     device = getDeviceForStream(AudioSystem.STREAM_MUSIC);  
  34.                     index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device, false);  
  35.                     streamMusicIsActive = true;  
  36.                 }  
  37.                 musicOrFMOrATVActive = (streamATVIsActive || streamFMIsActive || streamMusicIsActive);  
  38.                 /* add by quinn end 2014-05-10 */  
  39.                 if ((device & mSafeMediaVolumeDevices) != 0) {  
  40.                     sendMsg(mAudioHandler,  
  41.                             MSG_CHECK_MUSIC_ACTIVE,  
  42.                             SENDMSG_REPLACE,  
  43.                             0,  
  44.                             0,  
  45.                             null,  
  46.                             MUSIC_ACTIVE_POLL_PERIOD_MS);  
  47.                     /* add by quinn start 2014-05-10 */  
  48.                     // int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device, false /*lastAudible*/);  
  49.                     // if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&  
  50.                       if (musicOrFMOrATVActive &&  
  51.                     /* add by quinn end 2014-05-10 */  
  52.                             (index > mSafeMediaVolumeIndex)) {  
  53.                         // Approximate cumulative active music time  
  54.                         mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;  
  55.                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {  
  56.                             setSafeMediaVolumeEnabled(true);  
  57.                             mMusicActiveMs = 0;  
  58.                             mVolumePanel.postDisplaySafeVolumeWarning();  
  59.                         }  
  60.                     }  
  61.                 }  
  62.             }  
  63.         }  
  64.     }  

从代码中可以看到,onCheckMusicActive()会通过Handler 每隔MUSIC_ACTIVE_POLL_PERIOD_MS段时间后再次调用onCheckMusicActive(),从而实现播放时间的累计,如果大于预定的时间,则再次弹出警告。
----------------------------------------------------------------------
终于将整个流程都走了一遍,从一开始自己瞎琢磨去做,到仔细研究android的代码,给了我一个深刻的教训,就是遇到问题要先思考,不能拿到手就去敲代码,如果一开始就仔细阅读AudioService的代码,详细了解音量调节是如何实现的,肯定能发现android默认对SafeVolume的支持,就不会傻乎乎的再自己去写一套很有问题的东西。以此为鉴,要踏实稳重,不能太浮躁。
1 0