Android Audio音量——硬按键调节音量(七)
前面的文章已经介绍了音量调整及静音设置的相关调用,这里我们来梳理一下通过硬按键来调节音量及静音的相关调用流程。
一、硬按键调用
这里我们从 PhoneWindowManager 开始,该类主要负责管理设备上的所有窗口,包括应用程序窗口和其他系统窗口。同时负责输入事件的分发工作,包括我们这里要分析的硬按键事件。
1、PhoneWindowManager
源码位置:/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
interceptKeyBeforeDispatching
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags) {
final int keyCode = event.getKeyCode();
……
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
if (mUseTvRouting || mHandleVolumeKeysInWM) {
dispatchDirectAudioEvent(event);
return -1;
}
……
}
……
}
该函数在按键事件被分发给应用程序之前进行拦截和处理,也就是来处理音量键的按下事件。这里调用了 dispatchDirectAudioEvent() 函数。
dispatchDirectAudioEvent
private void dispatchDirectAudioEvent(KeyEvent event) {
// 通过 HDMI 音频系统客户端发送按键事件(这里不关注)
……
try {
// 处理按键事件
getAudioService().handleVolumeKey(event, mUseTvRouting, mContext.getOpPackageName(), TAG);
} catch (Exception e) {
……
}
}
static IAudioService getAudioService() {
IAudioService audioService = IAudioService.Stub.asInterface(ServiceManager.checkService(Context.AUDIO_SERVICE));
if (audioService == null) {
Log.w(TAG, "Unable to find IAudioService interface.");
}
return audioService;
}
这里主要负责处理直接与音频相关的按键事件,最终调用 AudioService 中的 handleVolumeKey() 函数。
2、AudioService
源码位置:/frameworks/base/services/core/java/com/android/server/audio/AudioService.java
handleVolumeKey
public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv, @NonNull String callingPackage,
@NonNull String caller) {
int keyEventMode = VOL_ADJUST_NORMAL;
……
int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_FROM_KEY;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP: // 音量上
adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
Binder.getCallingUid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_DOWN: // 音量下
adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
Binder.getCallingUid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_MUTE: // 静音
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
Binder.getCallingUid(), true, VOL_ADJUST_NORMAL);
}
break;
……
}
}
可以看到,这里对于音量上、音量下和静音的硬按键处理都是调用 adjustSuggestedStreamVolume() 函数继续处理。
adjustSuggestedStreamVolume
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
// 检查调用者是否有 MODIFY_AUDIO_SETTINGS 权限
boolean hasModifyAudioSettings = mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
== PackageManager.PERMISSION_GRANTED;
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
caller, Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL);
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller, int uid, boolean hasModifyAudioSettings, int keyEventMode) {
……
// 通知外部音量控制器
boolean hasExternalVolumeController = notifyExternalVolumeController(direction);
……
// 调整音量
adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, hasModifyAudioSettings, keyEventMode);
}
对于音量的调整,我们在前面的文章中已经分析过了,这里就不再重复。这里我们主要看 notifyExternalVolumeController(),该函数其实就是相当于一个回调函数,用来通知外部音量控制器的操作。
notifyExternalVolumeController
private boolean notifyExternalVolumeController(int direction) {
final IAudioPolicyCallback externalVolumeController;
……
sendMsg(mAudioHandler, MSG_NOTIFY_VOL_EVENT, SENDMSG_QUEUE, direction, 0 /*ignored*/,
externalVolumeController, 0 /*delay*/);
return true;
}
这里通过发送消息的方式告知外部控制器音量调整的方向。
onNotifyVolumeEvent
private class AudioHandler extends Handler {
……
private void onNotifyVolumeEvent(@NonNull IAudioPolicyCallback apc,
@AudioManager.VolumeAdjustment int direction) {
try {
apc.notifyVolumeAdjust(direction);
} catch(Exception e) {
}
}
……
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
……
case MSG_NOTIFY_VOL_EVENT:
onNotifyVolumeEvent((IAudioPolicyCallback) msg.obj, msg.arg1);
break;
……
}
}
}
这里首先对 MSG_NOTIFY_VOL_EVENT 消息进行了处理,调用了 onNotifyVolumeEvent() 函数。接着又调用了 IAudioPolicyCallback 的 notifyVolumeAdjust() 函数,这里一个 AIDL 接口,其实现在 AudioPolicy 中。
3、AudioPolicy
源码位置:/frameworks/base/media/java/android/media/audiopolicy/AudioPolicy.java
notifyVolumeAdjust
private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
public void notifyVolumeAdjust(int adjustment) {
sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment);
……
}
};
这里发送一个 MSG_VOL_ADJUST 用来处理音量相关的回调。
private final static int MSG_VOL_ADJUST = 6;
private class EventHandler extends Handler {
……
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
// 焦点状态
……
case MSG_VOL_ADJUST:
if (mVolCb != null) {
mVolCb.onVolumeAdjustment(msg.arg1);
}
break;
……
}
}
}
这里除了处理硬按键调整音量外,还有焦点状态相关的处理,这里我们不去关注。我们先来看一下 mVolCb 参数。
setAudioPolicyVolumeCallback
public static class Builder {
private AudioPolicyVolumeCallback mVolCb;
@NonNull
public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) {
……
mVolCb = vc;
return this;
}
……
}
public static abstract class AudioPolicyVolumeCallback {
public AudioPolicyVolumeCallback() {}
/**
* 当按下按键事件触发音量键相关更改时调用
* @param adjustment 键的音量调节类型
*/
public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {}
}
可以看到 mVolCb 其实就是通过 Builder 中的 setAudioPolicyVolumeCallback() 函数设置进来的一个 AudioPolicyVolumeCallback 回调,下面就来看一下该回调的监听。
4、CarAudioService
不难发现,在 CarAudioService 中的 init() 中调用了 setupDynamicRoutingLocked() 函数,该函数中设置了 AudioPolicyVolumeCallback 回调监听。
源码位置:/packages/services/Car/service/src/com/android/car/audio/CarAudioService.java
setupDynamicRoutingLocked
private void setupDynamicRoutingLocked() {
final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
……
// Attach the {@link AudioPolicyVolumeCallback}
builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
……
}
下面来看一下监听函数的相关处理。
private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
new AudioPolicy.AudioPolicyVolumeCallback() {
@Override
public void onVolumeAdjustment(int adjustment) {
int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
// 获取音频上下文
@AudioContext int suggestedContext = getSuggestedAudioContext();
// 获取音量组 ID
int groupId;
synchronized (mImplLock) {
groupId = getVolumeGroupIdForAudioContextLocked(zoneId, suggestedContext);
}
……
// 获取当前音量
final int currentVolume = getGroupVolume(zoneId, groupId);
final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI;
// 处理不同的音量调整情况
switch (adjustment) {
case AudioManager.ADJUST_LOWER: // 降低音量
int minValue = Math.max(currentVolume - 1, getGroupMinVolume(zoneId, groupId));
setGroupVolume(zoneId, groupId, minValue , flags);
break;
case AudioManager.ADJUST_RAISE: // 提高音量
int maxValue = Math.min(currentVolume + 1, getGroupMaxVolume(zoneId, groupId));
setGroupVolume(zoneId, groupId, maxValue, flags);
break;
case AudioManager.ADJUST_MUTE: // 静音
setMasterMute(true, flags);
callbackMasterMuteChange(zoneId, flags);
break;
case AudioManager.ADJUST_UNMUTE: // 取消静音
setMasterMute(false, flags);
callbackMasterMuteChange(zoneId, flags);
break;
case AudioManager.ADJUST_TOGGLE_MUTE: // 切换静音状态
setMasterMute(!mAudioManager.isMasterMute(), flags);
callbackMasterMuteChange(zoneId, flags);
break;
case AudioManager.ADJUST_SAME:
default:
break;
}
}
};
这里可以看到在静音状态改变后,调用 callbackMasterMuteChange 方法通知静音状态的变化,这与前面文章中静音设置中的回调函数相对应。