From bd8b4fa1ae9c95065ed21375d5e20af5cd0a2a81 Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 30 Jan 2019 11:27:49 -0800 Subject: [PATCH] Add sendVolumeKeyEvent API to handle forwarding volume key separately. Note that we handle volume key the same way as other keys before when forwarding them to other cec devices. But the destination device of volume key might be different from other function keys. We might need to take the System Audio Control logic into this forwarding proccess to find the proper audio receiver address. Test: make -j44 Bug: 123369653 Change-Id: I6c9dba2b333e7eaa5137a8d2f5bfed506ae8554b (cherry picked from commit 5db0138297836baf570f354c0b2ecb988d30936b) --- .../android/hardware/hdmi/HdmiClient.java | 20 +++++++++ .../hardware/hdmi/IHdmiControlService.aidl | 1 + .../hdmi/HdmiAudioSystemClientTest.java | 5 +++ .../android/server/hdmi/HdmiCecKeycode.java | 14 ++++++ .../server/hdmi/HdmiCecLocalDevice.java | 45 +++++++++++++++++++ .../server/hdmi/HdmiControlService.java | 24 +++++++++- 6 files changed, 108 insertions(+), 1 deletion(-) 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();