diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java index 45a79e1f3cae3..bff8c39e8c563 100644 --- a/core/java/android/hardware/hdmi/HdmiClient.java +++ b/core/java/android/hardware/hdmi/HdmiClient.java @@ -55,6 +55,26 @@ public abstract class HdmiClient { } } + /** + * Sends a volume key event to the primary audio receiver in the system. This method should only + * be called when the volume key is not handled by the local device. HDMI framework handles the + * logic of finding the address of the receiver. + * + * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. + * @param isPressed true if this is key press event + * + * @hide + * TODO(b/110094868): unhide for Q + */ + public void sendVolumeKeyEvent(int keyCode, boolean isPressed) { + try { + mService.sendVolumeKeyEvent(getDeviceType(), keyCode, isPressed); + } catch (RemoteException e) { + Log.e(TAG, "sendVolumeKeyEvent threw exception ", e); + throw e.rethrowFromSystemServer(); + } + } + /** * Sends vendor-specific command. * diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 1cd9920aa250f..95eaf75d6510f 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -47,6 +47,7 @@ interface IHdmiControlService { void deviceSelect(int deviceId, IHdmiControlCallback callback); void portSelect(int portId, IHdmiControlCallback callback); void sendKeyEvent(int deviceType, int keyCode, boolean isPressed); + void sendVolumeKeyEvent(int deviceType, int keyCode, boolean isPressed); List getPortInfo(); boolean canChangeSystemAudioMode(); boolean getSystemAudioMode(); diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java index 28a8afe434e4d..0a729a98a6a8c 100644 --- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java @@ -180,6 +180,11 @@ public class HdmiAudioSystemClientTest { public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { } + @Override + public void sendVolumeKeyEvent( + final int deviceType, final int keyCode, final boolean isPressed) { + } + @Override public void oneTouchPlay(final IHdmiControlCallback callback) { } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java index adc1cd7fc1d3a..2c0cacdd26e52 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java @@ -449,6 +449,20 @@ final class HdmiCecKeycode { return HdmiCecKeycode.androidKeyToCecKey(androidKeycode) != null; } + /** + * Returns {@code true} if given Android keycode is volume control related, + * otherwise {@code false}. + */ + static boolean isVolumeKeycode(int androidKeycode) { + int cecKeyCode = HdmiCecKeycode.androidKeyToCecKey(androidKeycode)[0]; + return isSupportedKeycode(androidKeycode) + && (cecKeyCode == CEC_KEYCODE_VOLUME_UP + || cecKeyCode == CEC_KEYCODE_VOLUME_DOWN + || cecKeyCode == CEC_KEYCODE_MUTE + || cecKeyCode == CEC_KEYCODE_MUTE_FUNCTION + || cecKeyCode == CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); + } + /** * Returns CEC keycode to control audio mute status. * diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 414f6bbfb9955..78b091e436ac6 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -1015,6 +1015,40 @@ abstract class HdmiCecLocalDevice { } } + /** + * Send a volume key event to other CEC device. The logical address of target device will be + * given by {@link #findAudioReceiverAddress()}. + * + * @param keyCode key code defined in {@link android.view.KeyEvent} + * @param isPressed {@code true} for key down event + * @see #findAudioReceiverAddress() + */ + @ServiceThreadOnly + protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) { + assertRunOnServiceThread(); + if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) { + Slog.w(TAG, "Not a volume key: " + keyCode); + return; + } + List action = getActions(SendKeyAction.class); + int logicalAddress = findAudioReceiverAddress(); + if (logicalAddress == Constants.ADDR_INVALID || logicalAddress == mAddress) { + // Don't send key event to invalid device or itself. + Slog.w( + TAG, + "Discard volume key event: " + + keyCode + + ", pressed:" + + isPressed + + ", receiverAddr=" + + logicalAddress); + } else if (!action.isEmpty()) { + action.get(0).processKeyEvent(keyCode, isPressed); + } else if (isPressed) { + addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); + } + } + /** * Returns the logical address of the device which will receive key events via {@link * #sendKeyEvent}. @@ -1026,6 +1060,17 @@ abstract class HdmiCecLocalDevice { return Constants.ADDR_INVALID; } + /** + * Returns the logical address of the audio receiver device which will receive volume key events + * via {@link#sendVolumeKeyEvent}. + * + * @see #sendVolumeKeyEvent(int, boolean) + */ + protected int findAudioReceiverAddress() { + Slog.w(TAG, "findAudioReceiverAddress is not implemented"); + return Constants.ADDR_INVALID; + } + @ServiceThreadOnly void invokeCallback(IHdmiControlCallback callback, int result) { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index f3a1e46bc1e43..d5b9d9c771d0e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1526,7 +1526,7 @@ public class HdmiControlService extends SystemService { if (mCecController != null) { HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); if (localDevice == null) { - Slog.w(TAG, "Local device not available"); + Slog.w(TAG, "Local device not available to send key event."); return; } localDevice.sendKeyEvent(keyCode, isPressed); @@ -1535,6 +1535,28 @@ public class HdmiControlService extends SystemService { }); } + @Override + public void sendVolumeKeyEvent( + final int deviceType, final int keyCode, final boolean isPressed) { + enforceAccessPermission(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + if (mCecController == null) { + Slog.w(TAG, "CEC controller not available to send volume key event."); + return; + } + HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + if (localDevice == null) { + Slog.w(TAG, "Local device " + deviceType + + " not available to send volume key event."); + return; + } + localDevice.sendVolumeKeyEvent(keyCode, isPressed); + } + }); + } + @Override public void oneTouchPlay(final IHdmiControlCallback callback) { enforceAccessPermission();