From 3814f1e23e50e857fdaff9a4e91328a4868f8be9 Mon Sep 17 00:00:00 2001 From: Nathalie Le Clair Date: Fri, 23 Sep 2022 09:35:29 +0200 Subject: [PATCH] Process eARC status updates from HAL Also inform the HDMI HAL which hotplug signal it should use for features other than eARC, e.g. EDID updates. When eARC is enabled, eARC uses the physical HPD signal and the other applications need to use the HPD bit instead. Bug: 260547935 Test: make and atest Change-Id: I7dc17270a16ece368d6db6e3674108f960fad63b --- .../com/android/server/hdmi/Constants.java | 20 +++++++++ .../server/hdmi/HdmiCecController.java | 25 +++++++++++ .../server/hdmi/HdmiCecLocalDevice.java | 8 +--- .../server/hdmi/HdmiControlService.java | 42 +++++++++++++++---- .../server/hdmi/HdmiEarcController.java | 20 +++++++++ .../server/hdmi/HdmiEarcLocalDevice.java | 27 +++++++++++- .../server/hdmi/HdmiEarcLocalDeviceTx.java | 41 +++++++++++++++++- .../android/server/hdmi/HdmiLocalDevice.java | 14 +++++++ .../server/hdmi/HdmiControlServiceTest.java | 6 ++- 9 files changed, 184 insertions(+), 19 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index aaa9ee5f59267..5353092c9a0b9 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -599,6 +599,26 @@ final class Constants { }) @interface RcProfileSource {} + static final int HDMI_EARC_STATUS_IDLE = 0; // IDLE1 + static final int HDMI_EARC_STATUS_EARC_PENDING = 1; // DISC1 and DISC2 + static final int HDMI_EARC_STATUS_ARC_PENDING = 2; // IDLE2 for ARC + static final int HDMI_EARC_STATUS_EARC_CONNECTED = 3; // eARC connected + @IntDef({ + HDMI_EARC_STATUS_IDLE, + HDMI_EARC_STATUS_EARC_PENDING, + HDMI_EARC_STATUS_ARC_PENDING, + HDMI_EARC_STATUS_EARC_CONNECTED + }) + @interface EarcStatus {} + + static final int HDMI_HPD_TYPE_PHYSICAL = 0; // Default. Physical hotplug signal. + static final int HDMI_HPD_TYPE_STATUS_BIT = 1; // HDMI_HPD status bit. + @IntDef({ + HDMI_HPD_TYPE_PHYSICAL, + HDMI_HPD_TYPE_STATUS_BIT + }) + @interface HpdSignalType {} + private Constants() { /* cannot be instantiated */ } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 2bf7812e1c3de..426f81638bb33 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -416,6 +416,31 @@ final class HdmiCecController { mNativeWrapperImpl.enableSystemCecControl(enabled); } + /** + * Configures the type of HDP signal that the driver and HAL use for actions other than eARC, + * such as signaling EDID updates. + */ + @ServiceThreadOnly + void setHpdSignalType(@Constants.HpdSignalType int signal, int portId) { + assertRunOnServiceThread(); + // Stub. + // TODO: bind to native. + // TODO: handle error return values here, with logging. + } + + /** + * Gets the type of the HDP signal that the driver and HAL use for actions other than eARC, + * such as signaling EDID updates. + */ + @ServiceThreadOnly + @Constants.HpdSignalType + int getHpdSignalType(int portId) { + assertRunOnServiceThread(); + // Stub. + // TODO: bind to native. + return Constants.HDMI_HPD_TYPE_PHYSICAL; + } + /** * Informs CEC HAL about the current system language. * diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 689183fbbf74a..91f58db407f21 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -67,8 +67,6 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { // When it expires, we can assume is received. private static final int FOLLOWER_SAFETY_TIMEOUT = 550; - protected final HdmiControlService mService; - protected final int mDeviceType; protected int mPreferredAddress; @GuardedBy("mLock") private HdmiDeviceInfo mDeviceInfo; @@ -154,8 +152,6 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { private int mActiveRoutingPath; protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); - @VisibleForTesting - protected final Object mLock; // A collection of FeatureAction. // Note that access to this collection should happen in service thread. @@ -188,9 +184,7 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { protected PendingActionClearedCallback mPendingActionClearedCallback; protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) { - mService = service; - mDeviceType = deviceType; - mLock = service.getServiceLock(); + super(service, deviceType); } // Factory method that returns HdmiCecLocalDevice of corresponding type. diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 45c7de9763b7a..735dddf2f4274 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -398,6 +398,8 @@ public class HdmiControlService extends SystemService { @GuardedBy("mLock") private boolean mEarcEnabled; + private int mEarcPortId = -1; + // Set to true while the service is in normal mode. While set to false, no input change is // allowed. Used for situations where input change can confuse users such as channel auto-scan, // system upgrade, etc., a.k.a. "prohibit mode". @@ -702,15 +704,21 @@ public class HdmiControlService extends SystemService { synchronized (mLock) { mEarcSupported = false; for (HdmiPortInfo port : ports) { - if (mEarcSupported && port.isEarcSupported()) { - // The HDMI specification only allows 1 active eARC connection. Android does not - // support devices with multiple eARC-enabled ports. + boolean earcSupportedOnPort = port.isEarcSupported(); + if (earcSupportedOnPort && mEarcSupported) { + // This means that more than 1 port supports eARC. + // The HDMI specification only allows 1 active eARC connection. + // Android does not support devices with multiple eARC-enabled ports. + // Consider eARC not supported in this case. Slog.e(TAG, "HDMI eARC supported on more than 1 port."); + mEarcSupported = false; + mEarcPortId = -1; break; + } else if (earcSupportedOnPort) { + mEarcPortId = port.getId(); + mEarcSupported = earcSupportedOnPort; } - mEarcSupported |= port.isEarcSupported(); } - mEarcSupported &= (mEarcController != null); } if (isEarcSupported()) { @@ -4416,8 +4424,8 @@ public class HdmiControlService extends SystemService { protected void initializeEarcLocalDevice(final int initiatedBy) { // TODO remove initiatedBy argument if it stays unused assertRunOnServiceThread(); - if (isTvDevice() && mEarcLocalDevice == null) { - mEarcLocalDevice = new HdmiEarcLocalDeviceTx(); + if (mEarcLocalDevice == null) { + mEarcLocalDevice = HdmiEarcLocalDevice.create(this, HdmiDeviceInfo.DEVICE_TV); } // TODO create HdmiEarcLocalDeviceRx if we're an audio system device. } @@ -4483,10 +4491,11 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly @VisibleForTesting - protected HdmiEarcLocalDevice getEarcLocalDevice() { + HdmiEarcLocalDevice getEarcLocalDevice() { assertRunOnServiceThread(); return mEarcLocalDevice; } + private void disableEarcLocalDevice() { if (mEarcLocalDevice == null) { return; @@ -4499,5 +4508,22 @@ public class HdmiControlService extends SystemService { protected void setEarcEnabledInHal(boolean enabled) { assertRunOnServiceThread(); mEarcController.setEarcEnabled(enabled); + mCecController.setHpdSignalType( + enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT : Constants.HDMI_HPD_TYPE_PHYSICAL, + mEarcPortId); + } + + @ServiceThreadOnly + void handleEarcStateChange(int status, int portId) { + assertRunOnServiceThread(); + if (!getPortInfo(portId).isEarcSupported()) { + Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC."); + return; + } + // If eARC is disabled, the local device is null. In this case, the HAL shouldn't have + // reported connection state changes, but even if it did, it won't take effect. + if (mEarcLocalDevice != null) { + mEarcLocalDevice.handleEarcStateChange(status); + } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcController.java b/services/core/java/com/android/server/hdmi/HdmiEarcController.java index e4acbd11acb2d..e38b2cd82c15d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiEarcController.java +++ b/services/core/java/com/android/server/hdmi/HdmiEarcController.java @@ -78,5 +78,25 @@ final class HdmiEarcController { // TODO: handle error return values here, with logging. } + /** + * Getter for the current eARC state. + * @param portId the ID of the port on which to get the connection state + * @return the current eARC state + */ + @HdmiAnnotations.ServiceThreadOnly + @Constants.EarcStatus + int getState(int portId) { + // Stub. + // TODO: bind to native. + return Constants.HDMI_EARC_STATUS_IDLE; + } + + final class EarcCallback { + public void onStateChange(@Constants.EarcStatus int status, int portId) { + runOnServiceThread( + () -> mService.handleEarcStateChange(status, portId)); + } + } + // TODO: bind to native. } diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java index 720dff5e77adb..231775e374792 100644 --- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java @@ -16,15 +16,40 @@ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiDeviceInfo; import android.util.IndentingPrintWriter; +import com.android.internal.annotations.GuardedBy; + /** * Class that models a local eARC device hosted in this system. * The class contains methods that are common between eARC TX and eARC RX devices. */ -public class HdmiEarcLocalDevice extends HdmiLocalDevice { +abstract class HdmiEarcLocalDevice extends HdmiLocalDevice { private static final String TAG = "HdmiEarcLocalDevice"; + // The current status of the eARC connection, as reported by the HAL + @GuardedBy("mLock") + @Constants.EarcStatus + protected int mEarcStatus; + + protected HdmiEarcLocalDevice(HdmiControlService service, int deviceType) { + super(service, deviceType); + } + + // Factory method that returns HdmiCecLocalDevice of corresponding type. + static HdmiEarcLocalDevice create(HdmiControlService service, int deviceType) { + switch (deviceType) { + case HdmiDeviceInfo.DEVICE_TV: + return new HdmiEarcLocalDeviceTx(service); + default: + return null; + } + } + + protected abstract void handleEarcStateChange(@Constants.EarcStatus int status); + + protected void disableDevice() { } diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java index c1c1e7794676d..b01daa51fb482 100644 --- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java @@ -16,8 +16,19 @@ package com.android.server.hdmi; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_IDLE; + +import android.hardware.hdmi.HdmiDeviceInfo; +import android.media.AudioDescriptor; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioProfile; import android.util.IndentingPrintWriter; +import java.util.ArrayList; + /** * Represents a local eARC device of type TX residing in the Android system. * Only TV panel devices can have a local eARC TX device. @@ -25,8 +36,36 @@ import android.util.IndentingPrintWriter; public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice { private static final String TAG = "HdmiEarcLocalDeviceTx"; + HdmiEarcLocalDeviceTx(HdmiControlService service) { + super(service, HdmiDeviceInfo.DEVICE_TV); + } + + protected void handleEarcStateChange(@Constants.EarcStatus int status) { + synchronized (mLock) { + HdmiLogger.debug(TAG, "eARC state change [old:%b new %b]", mEarcStatus, + status); + mEarcStatus = status; + } + if (status == HDMI_EARC_STATUS_IDLE) { + notifyEarcStatusToAudioService(false); + } else if (status == HDMI_EARC_STATUS_ARC_PENDING) { + notifyEarcStatusToAudioService(false); + } else if (status == HDMI_EARC_STATUS_EARC_CONNECTED) { + notifyEarcStatusToAudioService(true); + } + } + + private void notifyEarcStatusToAudioService(boolean enabled) { + AudioDeviceAttributes attributes = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "", + new ArrayList(), new ArrayList()); + mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); + } + /** Dump internal status of HdmiEarcLocalDeviceTx object */ protected void dump(final IndentingPrintWriter pw) { - pw.println("TX"); + synchronized (mLock) { + pw.println("TX, mEarcStatus: " + mEarcStatus); + } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiLocalDevice.java index 96fb28c5321ca..1e68734aed3a5 100644 --- a/services/core/java/com/android/server/hdmi/HdmiLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiLocalDevice.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import com.android.internal.annotations.VisibleForTesting; + /** * Class that models an HDMI device hosted in this system. * Can be used to share methods between CEC and eARC local devices. @@ -23,4 +25,16 @@ package com.android.server.hdmi; */ abstract class HdmiLocalDevice { private static final String TAG = "HdmiLocalDevice"; + + protected final HdmiControlService mService; + protected final int mDeviceType; + + @VisibleForTesting + protected final Object mLock; + + protected HdmiLocalDevice(HdmiControlService service, int deviceType) { + mService = service; + mDeviceType = deviceType; + mLock = service.getServiceLock(); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index ff873faf02b7d..2b555a07a50fd 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -1089,7 +1089,8 @@ public class HdmiControlServiceTest { public void disableEarc_clearEarcLocalDevice() { mHdmiControlServiceSpy.setEarcSupported(true); mHdmiControlServiceSpy.clearEarcLocalDevice(); - mHdmiControlServiceSpy.addEarcLocalDevice(new HdmiEarcLocalDeviceTx()); + mHdmiControlServiceSpy.addEarcLocalDevice( + new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy)); assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull(); mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED); @@ -1101,7 +1102,8 @@ public class HdmiControlServiceTest { public void disableCec_doNotClearEarcLocalDevice() { mHdmiControlServiceSpy.setEarcSupported(true); mHdmiControlServiceSpy.clearEarcLocalDevice(); - mHdmiControlServiceSpy.addEarcLocalDevice(new HdmiEarcLocalDeviceTx()); + mHdmiControlServiceSpy.addEarcLocalDevice( + new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy)); assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull(); mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);