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
This commit is contained in:
Nathalie Le Clair
2022-09-23 09:35:29 +02:00
parent 40355178f2
commit 3814f1e23e
9 changed files with 184 additions and 19 deletions

View File

@@ -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 */
}

View File

@@ -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.
*

View File

@@ -67,8 +67,6 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
// When it expires, we can assume <User Control Release> 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.

View File

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

View File

@@ -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.
}

View File

@@ -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() {
}

View File

@@ -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<AudioProfile>(), new ArrayList<AudioDescriptor>());
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);
}
}
}

View File

@@ -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();
}
}

View File

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