android?原生安全音量配置邏輯設計詳解
前言
接到一個開發(fā)需求,需要定制化開發(fā)一個安全音量功能;此前有了解過為了符合歐盟等有關國家和地區(qū)的規(guī)定,原生Android是有自帶一個安全音量功能的,想要定制則先要了解這個功能原先長什么樣子,下面我們就從一個系統(tǒng)工程師的角度出發(fā)去探尋一下,原生Android的安全音量功能是如何實現(xiàn)的。
安全音量配置
安全音量的相關配置都在framework的config.xml里面,可以直接修改或者overlay配置修改其默認值。
<!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">true</bool>
<!-- 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. --> <integer name="config_safe_media_volume_index">10</integer>
config_safe_media_volume_enabled是安全音量功能的總開關,config_safe_media_volume_index則是表明觸發(fā)安全音量彈框的音量大小值。
安全音量相關流程
安全音量的主要流程都在AudioService里面,其大致流程如下圖所示:
onSystemReady 初始化
系統(tǒng)啟動過程略去不表,在系統(tǒng)啟動完成后會調用onSystemReady;在onSystemReady中,service會發(fā)送一個MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED的msg,強制配置安全音量。
public void onSystemReady() { ... sendMsg(mAudioHandler, MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED, SENDMSG_REPLACE, 0, 0, TAG, SystemProperties.getBoolean("audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS); ... }
發(fā)送的MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED會調用onConfigureSafeVolume()來進行安全音量的配置
onConfigureSafeVolume() 安全音量配置
private void onConfigureSafeVolume(boolean force, String caller) { synchronized (mSafeMediaVolumeStateLock) { //Mobile contry code,國家代碼,主要用來區(qū)分不同國家,部分國家策略可能會不一致 int mcc = mContext.getResources().getConfiguration().mcc; if ((mMcc != mcc) || ((mMcc == 0) && force)) { //從config_safe_media_volume_index中獲取回來的安全音量觸發(fā)閾值 mSafeMediaVolumeIndex = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_index) * 10; mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); //根據(jù)audio.safemedia.force屬性值或者value配置的值來決定是否使能安全音量 boolean safeMediaVolumeEnabled = SystemProperties.getBoolean("audio.safemedia.force", false) || mContext.getResources().getBoolean( com.android.internal.R.bool.config_safe_media_volume_enabled); //確認是否需要bypass掉安全音量功能 boolean safeMediaVolumeBypass = SystemProperties.getBoolean("audio.safemedia.bypass", false); // The persisted state is either "disabled" or "active": this is the state applied // next time we boot and cannot be "inactive" int persistedState; if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) { persistedState = SAFE_MEDIA_VOLUME_ACTIVE; //這個值只能是disable或者active,不能是inactive,主要用于下次啟動。 // The state can already be "inactive" here if the user has forced it before // the 30 seconds timeout for forced configuration. In this case we don't reset // it to "active". if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) { if (mMusicActiveMs == 0) { //mMusicActiveMs主要用于計數(shù),當安全音量彈框彈出時,如果按了確定,這個值便開始遞增,當其達到UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX時,則重新使能安全音量 mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; enforceSafeMediaVolume(caller); } else { //跑到這里則表示已經(jīng)彈過安全音量警示了,并且按了確定,所以把值設置為inactive // We have existing playback time recorded, already confirmed. mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; } } } else { persistedState = SAFE_MEDIA_VOLUME_DISABLED; mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; } mMcc = mcc; //持久化當前安全音量的狀態(tài) sendMsg(mAudioHandler, MSG_PERSIST_SAFE_VOLUME_STATE, SENDMSG_QUEUE, persistedState, 0, null, 0); } } }
由上可知,onConfigureSafeVolume()主要用于配置和使能安全音量功能,并且通過發(fā)送MSG_PERSIST_SAFE_VOLUME_STATE來持久化安全音量配置的值,這個持久化的值只能是active或者disabled。
case MSG_PERSIST_SAFE_VOLUME_STATE: onPersistSafeVolumeState(msg.arg1); break; .... .... private void onPersistSafeVolumeState(int state) { Settings.Global.putInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, state); }
安全音量觸發(fā)
從實際操作可知,安全音量觸發(fā)條件是:音量增大到指定值。 從調節(jié)音量的代碼出發(fā),在調用mAudioManager.adjustStreamVolume和mAudioManager.setStreamVolume時,最終會調用到AudioService中的同名方法,在執(zhí)行該方法的內部:
protected void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage, String caller, int uid) { ... ... ... } else if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); mVolumeController.postDisplaySafeVolumeWarning(flags); .... ...
private void setStreamVolume(int streamType, int index, int flags, String callingPackage, String caller, int uid) { .... .... if (!checkSafeMediaVolume(streamTypeAlias, index, device)) { mVolumeController.postDisplaySafeVolumeWarning(flags); mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); } else { onSetStreamVolume(streamType, index, flags, device, caller); index = mStreamStates[streamType].getIndex(device); } .... ....
由以上代碼可以看出,其安全音量彈框警告的觸發(fā)地方就在checkSafeMediaVolume方法附近處,并且都是通過mVolumeController這個遠程服務去調用UI顯示安全音量彈框警告,但兩種調節(jié)音量的方法,觸發(fā)效果略有不同:
- adjustStreamVolume:當音量步進方向是上升并且checkSafeMediaVolume返回false時,直接彈出警告框;由于警告框占據(jù)了焦點,此時無法進行UI操作,并且再按音量+鍵時,會繼續(xù)觸發(fā)這個彈框,導致無法實質性地調整音量;
- setStreamVolume:當傳入的音量形參大于安全音量閾值,會觸發(fā)checkSafeMediaVolume返回false,彈出安全音量警告框;并且會通過mPendingVolumeCommand保存設置的音量值,待關掉安全音量后再賦回來。
private boolean checkSafeMediaVolume(int streamType, int index, int device) { synchronized (mSafeMediaVolumeStateLock) { if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && ((device & mSafeMediaVolumeDevices) != 0) && (index > safeMediaVolumeIndex(device))) { return false; } return true; } }
以上是安全音量判斷條件checkSafeMediaVolume,可以看出其判斷主要根據(jù)以下條件:
- mSafeMediaVolumeState是否為active,這個是安全音量功能的開關變量;
- 音頻流是否為STREAM_MUSIC,只針對該音頻流做安全音量;
- 設備類型,默認mSafeMediaVolumeDevices值如下:
/*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | AudioSystem.DEVICE_OUT_USB_HEADSET;
由上可知,只針對耳機播放或者USB耳機才做安全音量功能,如有需要系統(tǒng)工程師可自行配置其他設備;
- 音量大小,只有音量index超過safeMediaVolumeIndex獲取的值,才需要彈出安全音量警示框,而safeMediaVolumeIndex的值則是本文開頭在config.xml中配置的config_safe_media_volume_index所得出的;
UI部分
上面有提到,當滿足安全音量警示框的觸發(fā)條件時,會通過mVolumeController這個遠程服務去調用UI顯示安全音量彈框警告,其調用鏈條有點長,中途略過不表,其最終會走到VolumeDialogImpl.java的showSafetyWarningH,如下:
public class VolumeDialog { ... private void showSafetyWarningH(int flags) { if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0 || mShowing) { synchronized (mSafetyWarningLock) { if (mSafetyWarning != null) { return; } mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) { @Override protected void cleanUp() { synchronized (mSafetyWarningLock) { mSafetyWarning = null; } recheckH(null); } }; mSafetyWarning.show(); } recheckH(null); } rescheduleTimeoutH(); } ... }
UI配置部分主要在SafetyWarningDialog.java,代碼就不貼了,可自行查看,其本質是一個對話框,在彈出時會搶占UI焦點,如果不點擊確定或取消,則無法操作其他UI;
點擊確定后,會調用mAudioManager.disableSafeMediaVolume()來暫時關閉安全音量警告功能,但上面有提到,當點擊確定之后其實是啟動了一個變量mMusicActiveMs的計數(shù),當這個計數(shù)到達一定值(默認是20個小時),安全音量會重新啟動;
但如果點擊了取消,再繼續(xù)調大音量時,安全音量彈框還是會繼續(xù)彈出;
disableSafeMediaVolume()
上面有提到,在安全音量彈框彈出后,點擊確定可以暫時關閉安全音量警告功能,其實最終會調用到AudioService中的disableSafeMediaVolume(),代碼如下:
public void disableSafeMediaVolume(String callingPackage) { enforceVolumeController("disable the safe media volume"); synchronized (mSafeMediaVolumeStateLock) { setSafeMediaVolumeEnabled(false, callingPackage); if (mPendingVolumeCommand != null) { onSetStreamVolume(mPendingVolumeCommand.mStreamType, mPendingVolumeCommand.mIndex, mPendingVolumeCommand.mFlags, mPendingVolumeCommand.mDevice, callingPackage); mPendingVolumeCommand = null; } } }
一方面是調用setSafeMediaVolumeEnabled來暫時關閉安全音量功能,另一方面會把此前臨時掛起的設置音量mPendingVolumeCommand重新設置回去。
小結
簡單來講,Android原生的安全音量功能默認強制打開,在插入耳機后,音量調節(jié)到指定閾值時,會觸發(fā)音量警告彈框,該彈框會搶走焦點,不點擊確定或取消無法進行其他操作;在點擊確定后,默認操作者本人允許設備音量繼續(xù)往上調,但此時系統(tǒng)會開始一個默認為20分鐘的倒計時,在這20分鐘內音量隨意調節(jié)都不會觸發(fā)安全音量彈框,但20分鐘結束后,音量大于閾值時會繼續(xù)觸發(fā)安全音量彈框,提醒使用者注意。
以上就是android 原生安全音量配置邏輯設計詳解的詳細內容,更多關于android 原生安全音量邏輯的資料請關注腳本之家其它相關文章!
相關文章
Android創(chuàng)建和使用數(shù)據(jù)庫SQLIte
這篇文章主要為大家詳細介紹了Android創(chuàng)建和使用數(shù)據(jù)庫SQLIte的相關資料,感興趣的小伙伴們可以參考一下2016-05-05Android自定義View實現(xiàn)LayoutParams的方法詳解
這篇文章主要為大家詳細介紹了Android自定義View實現(xiàn)LayoutParams,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-02-02Android ScrollView嵌套ExpandableListView顯示不正常的問題的解決辦法
這篇文章主要介紹了Android ScrollView嵌套ExpandableListView顯示不正常的問題的解決辦法的相關資料,需要的朋友可以參考下2017-02-02Android Studio ADB網(wǎng)絡調試匯總
這篇文章主要為大家詳細介紹了Android Studio ADB網(wǎng)絡調試的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05Android Studio kotlin生成編輯類注釋代碼
這篇文章主要介紹了Android Studio kotlin生成編輯類注釋代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03