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 方法通知静音状态的变化,这与前面文章中静音设置中的回调函数相对应。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c小旭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值