一.使用场景
Safe Media,字面意思是安全音量。前提是有耳机插入,并且有音乐或者FM播放,在我们调节音量的时候,如果音量达到安全音量值,会提出系统提示,提醒用户高音量下使用耳机会损坏听力。在用户选择OK后,安全音量失效,声音可以继续调高。但是此时系统开始计时,如果累计达到20h,音量自动强制降到安全音量,并弹出系统提示,提醒用户长时间在高音量下使用会损坏听力。
二.处理逻辑
在音量的调节的实现在AudioService中,AudioService位于frameworks/base/media/java/android/media/下。
在我们点击上下键按钮调节音量时,会调用
private void adjustStreamVolume(int streamType, int direction, int flags,String callingPackage, int uid) {
……
if ((direction == AudioManager.ADJUST_RAISE) &&
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
} else if (streamState.adjustIndex(direction * step, device)) {
// Post message to set system volume (it in turn will post a message
// to persist). Do not change volume if stream is muted.
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}
……
}
首先有俩个判断,第一个direction == AudioManager.ADJUST_RAISE判断是否按音量上键,如果按下键就不用检查了。第二个判断是checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device),是检查aliasIndex + step是否超过安全音量。
private boolean checkSafeMediaVolume(int streamType, int index, int device) {
synchronized (mSafeMediaVolumeState) {
if(!prefs.getBoolean(firstShowSafeDialog,false) && (index > mSafeMediaVolumeIndex) &&
((device & mSafeMediaVolumeDevices) != 0)){
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(firstShowSafeDialog, true);
edit.commit();
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
return false;
}
if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) &&
((device & mSafeMediaVolumeDevices) != 0) &&
(index > mSafeMediaVolumeIndex)) {
return false;
}
return true;
}
}
在这个方法中,我们会判断mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE,即Safe Media的状态。(device & mSafeMediaVolumeDevices) != 0检查耳机是否插入,index > mSafeMediaVolumeIndex检查我们要设置的音量是否超过安全音量。如果这些条件都满足,调用mVolumeController.postDisplaySafeVolumeWarning(flags)弹出系统dialog。
点击OK后,调用setSafeMediaVolumeEnabled(false);
private void setSafeMediaVolumeEnabled(boolean on) {
synchronized (mSafeMediaVolumeState) {
if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
(mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {
if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
enforceSafeMediaVolume();
} else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
mMusicActiveMs = 1; // nonzero = confirmed
saveMusicActiveMs();
sendMsg(mAudioHandler,
MSG_CHECK_MUSIC_ACTIVE,
SENDMSG_REPLACE,
0,
0,
null,
MUSIC_ACTIVE_POLL_PERIOD_MS);
}
}
}
}
将mSafeMediaVolumeState的状态设置成SAFE_MEDIA_VOLUME_INACTIVE,然后发送消息
sendMsg(mAudioHandler,
MSG_CHECK_MUSIC_ACTIVE,
SENDMSG_REPLACE,
0,
0,
null,
MUSIC_ACTIVE_POLL_PERIOD_MS);
进入handleMessage处理消息
public void handleMessage(Message msg) {
switch (msg.what) {
……
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive();
break;
……
}
}
调用了onCheckMusicActive();
private void onCheckMusicActive() {
synchronized (mSafeMediaVolumeState) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
if ((device & mSafeMediaVolumeDevices) != 0) {
sendMsg(mAudioHandler,
MSG_CHECK_MUSIC_ACTIVE,
SENDMSG_REPLACE,
0,
0,
null,
MUSIC_ACTIVE_POLL_PERIOD_MS);
int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
(index > mSafeMediaVolumeIndex)) {
// Approximate cumulative active music time
mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
setSafeMediaVolumeEnabled(true); mVolumeController.postDisplaySafeVolumeWarningWithType(AudioManager.FLAG_SHOW_UI_WARNINGS,1);
mMusicActiveMs = 0;
}
saveMusicActiveMs();
}
}
}
}
}
在这个方法里,我们发现同样多很多判断,耳机设备是否插入,Safe Media的状态,STREAM_MUSIC的状态,以及当前音量值是否超过安全音量。如果耳机插入和Safe Media是SAFE_MEDIA_VOLUME_INACTIVE的状态,我们看到同样发了一个消息。
sendMsg(mAudioHandler,
MSG_CHECK_MUSIC_ACTIVE,
SENDMSG_REPLACE,
0,
0,
null,
MUSIC_ACTIVE_POLL_PERIOD_MS);
消息是与之前是相同的,因此进入一个消息循环,MUSIC_ACTIVE_POLL_PERIOD_MS是个表示1分钟的常量。也就是每隔一分钟发一个消息的循环执行。
在前面的条件满足的前提下,如果STREAM_MUSIC的状态是Active的,并且音量大于安全音量,会执行
mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
saveMusicActiveMs();
累加并保存时间。
如果累加时间到达一定值
mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX
UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX是设定的20小时的一个常量。执行
setSafeMediaVolumeEnabled(true);
private void setSafeMediaVolumeEnabled(boolean on) {
synchronized (mSafeMediaVolumeState) {
if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
(mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {
if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
enforceSafeMediaVolume();
} else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
……
}
}
}
}
将mSafeMediaVolumeState的状态设定SAFE_MEDIA_VOLUME_ACTIVE,并且enforceSafeMediaVolume()
private void enforceSafeMediaVolume() {
VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
int devices = mSafeMediaVolumeDevices;
int i = 0;
while (devices != 0) {
int device = 1 << i++;
if ((device & devices) == 0) {
continue;
}
int index = streamState.getIndex(device);
if (index > mSafeMediaVolumeIndex) {
streamState.setIndex(mSafeMediaVolumeIndex, device);
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}
devices &= ~device;
}
}
获取耳机设备devices = mSafeMediaVolumeDevices,获取设备音量index = streamState.getIndex(device),如果设备音量大于安全音量,强制将设备设置成安全音量streamState.setIndex(mSafeMediaVolumeIndex, device)。
并且会执行
mVolumeController.postDisplaySafeVolumeWarningWithType(AudioManager.FLAG_SHOW_UI_WARNINGS,1);
弹出系统Dialog,提示用户长时间高音量使用耳机会损坏听力。