網站首頁 編程語言 正文
前言
接到一個開發需求,需要定制化開發一個安全音量功能;此前有了解過為了符合歐盟等有關國家和地區的規定,原生Android是有自帶一個安全音量功能的,想要定制則先要了解這個功能原先長什么樣子,下面我們就從一個系統工程師的角度出發去探尋一下,原生Android的安全音量功能是如何實現的。
安全音量配置
安全音量的相關配置都在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則是表明觸發安全音量彈框的音量大小值。
安全音量相關流程
安全音量的主要流程都在AudioService里面,其大致流程如下圖所示:
onSystemReady 初始化
系統啟動過程略去不表,在系統啟動完成后會調用onSystemReady;在onSystemReady中,service會發送一個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);
...
}
發送的MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED會調用onConfigureSafeVolume()來進行安全音量的配置
onConfigureSafeVolume() 安全音量配置
private void onConfigureSafeVolume(boolean force, String caller) {
synchronized (mSafeMediaVolumeStateLock) {
//Mobile contry code,國家代碼,主要用來區分不同國家,部分國家策略可能會不一致
int mcc = mContext.getResources().getConfiguration().mcc;
if ((mMcc != mcc) || ((mMcc == 0) && force)) {
//從config_safe_media_volume_index中獲取回來的安全音量觸發閾值
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_index) * 10;
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
//根據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主要用于計數,當安全音量彈框彈出時,如果按了確定,這個值便開始遞增,當其達到UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX時,則重新使能安全音量
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
enforceSafeMediaVolume(caller);
} else {
//跑到這里則表示已經彈過安全音量警示了,并且按了確定,所以把值設置為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;
//持久化當前安全音量的狀態
sendMsg(mAudioHandler,
MSG_PERSIST_SAFE_VOLUME_STATE,
SENDMSG_QUEUE,
persistedState,
0,
null,
0);
}
}
}
由上可知,onConfigureSafeVolume()主要用于配置和使能安全音量功能,并且通過發送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);
}
安全音量觸發
從實際操作可知,安全音量觸發條件是:音量增大到指定值。 從調節音量的代碼出發,在調用mAudioManager.adjustStreamVolume和mAudioManager.setStreamVolume時,最終會調用到AudioService中的同名方法,在執行該方法的內部:
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);
}
....
....
由以上代碼可以看出,其安全音量彈框警告的觸發地方就在checkSafeMediaVolume方法附近處,并且都是通過mVolumeController這個遠程服務去調用UI顯示安全音量彈框警告,但兩種調節音量的方法,觸發效果略有不同:
- adjustStreamVolume:當音量步進方向是上升并且checkSafeMediaVolume返回false時,直接彈出警告框;由于警告框占據了焦點,此時無法進行UI操作,并且再按音量+鍵時,會繼續觸發這個彈框,導致無法實質性地調整音量;
- setStreamVolume:當傳入的音量形參大于安全音量閾值,會觸發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,可以看出其判斷主要根據以下條件:
- 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耳機才做安全音量功能,如有需要系統工程師可自行配置其他設備;
- 音量大小,只有音量index超過safeMediaVolumeIndex獲取的值,才需要彈出安全音量警示框,而safeMediaVolumeIndex的值則是本文開頭在config.xml中配置的config_safe_media_volume_index所得出的;
UI部分
上面有提到,當滿足安全音量警示框的觸發條件時,會通過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的計數,當這個計數到達一定值(默認是20個小時),安全音量會重新啟動;
但如果點擊了取消,再繼續調大音量時,安全音量彈框還是會繼續彈出;
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原生的安全音量功能默認強制打開,在插入耳機后,音量調節到指定閾值時,會觸發音量警告彈框,該彈框會搶走焦點,不點擊確定或取消無法進行其他操作;在點擊確定后,默認操作者本人允許設備音量繼續往上調,但此時系統會開始一個默認為20分鐘的倒計時,在這20分鐘內音量隨意調節都不會觸發安全音量彈框,但20分鐘結束后,音量大于閾值時會繼續觸發安全音量彈框,提醒使用者注意。
原文鏈接:https://juejin.cn/post/7178817360810737722
相關推薦
- 2022-09-27 React?Native?中限制導入某些組件和模塊的方法_React
- 2022-06-19 docker容器非root用戶提權的問題解決_docker
- 2022-11-17 Python?Scala中使用def語句定義方法的詳細過程_python
- 2022-08-30 如何解決React?useEffect鉤子帶來的無限循環問題_React
- 2022-08-30 C語言淺析函數的用法_C 語言
- 2022-06-06 PowerShell yarn : 無法加載文件 C:\Users\Admin\AppData\Ro
- 2022-11-15 C語言自研定時器計劃任務語法詳解_C 語言
- 2022-07-28 C++超詳細講解函數重載_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支