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);