Merge changes from topics "audiodescriptor_apis", "hdmiportinfo_apis"

* changes:
  Parse raw eARC capabilities
  Process eARC capabilities reported by the HAL
  Process eARC status updates from HAL
  Enable and disable eARC in the HAL
  Add HdmiEarcController stub
  Add eARC info to HdmiPortInfo
  Create and remove HdmiEarcLocalDeviceTx
This commit is contained in:
Nathalie Le Clair
2022-12-21 17:28:01 +00:00
committed by Android (Google) Code Review
19 changed files with 1350 additions and 42 deletions

View File

@@ -20429,6 +20429,8 @@ package android.media {
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDescriptor> CREATOR;
field public static final int STANDARD_EDID = 1; // 0x1
field public static final int STANDARD_NONE = 0; // 0x0
field public static final int STANDARD_SADB = 2; // 0x2
field public static final int STANDARD_VSADB = 3; // 0x3
}
public abstract class AudioDeviceCallback {

View File

@@ -4531,12 +4531,14 @@ package android.hardware.hdmi {
public final class HdmiPortInfo implements android.os.Parcelable {
ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean);
ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean, boolean);
method public int describeContents();
method public int getAddress();
method public int getId();
method public int getType();
method public boolean isArcSupported();
method public boolean isCecSupported();
method public boolean isEarcSupported();
method public boolean isMhlSupported();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiPortInfo> CREATOR;

View File

@@ -23,7 +23,7 @@ import android.os.Parcelable;
/**
* A class to encapsulate HDMI port information. Contains the capability of the ports such as
* HDMI-CEC, MHL, ARC(Audio Return Channel), and physical address assigned to each port.
* HDMI-CEC, MHL, ARC(Audio Return Channel), eARC and physical address assigned to each port.
*
* @hide
*/
@@ -40,6 +40,7 @@ public final class HdmiPortInfo implements Parcelable {
private final int mAddress;
private final boolean mCecSupported;
private final boolean mArcSupported;
private final boolean mEarcSupported;
private final boolean mMhlSupported;
/**
@@ -53,11 +54,28 @@ public final class HdmiPortInfo implements Parcelable {
* @param arc {@code true} if audio return channel is supported on the port
*/
public HdmiPortInfo(int id, int type, int address, boolean cec, boolean mhl, boolean arc) {
this(id, type, address, cec, mhl, arc, false);
}
/**
* Constructor.
*
* @param id identifier assigned to each port. 1 for HDMI port 1
* @param type HDMI port input/output type
* @param address physical address of the port
* @param cec {@code true} if HDMI-CEC is supported on the port
* @param mhl {@code true} if MHL is supported on the port
* @param arc {@code true} if audio return channel is supported on the port
* @param earc {@code true} if eARC is supported on the port
*/
public HdmiPortInfo(int id, int type, int address,
boolean cec, boolean mhl, boolean arc, boolean earc) {
mId = id;
mType = type;
mAddress = address;
mCecSupported = cec;
mArcSupported = arc;
mEarcSupported = earc;
mMhlSupported = mhl;
}
@@ -115,6 +133,15 @@ public final class HdmiPortInfo implements Parcelable {
return mArcSupported;
}
/**
* Returns {@code true} if the port supports eARC.
*
* @return {@code true} if the port supports eARC.
*/
public boolean isEarcSupported() {
return mEarcSupported;
}
/**
* Describes the kinds of special objects contained in this Parcelable's
* marshalled representation.
@@ -138,7 +165,8 @@ public final class HdmiPortInfo implements Parcelable {
boolean cec = (source.readInt() == 1);
boolean arc = (source.readInt() == 1);
boolean mhl = (source.readInt() == 1);
return new HdmiPortInfo(id, type, address, cec, mhl, arc);
boolean earc = (source.readInt() == 1);
return new HdmiPortInfo(id, type, address, cec, mhl, arc, earc);
}
@Override
@@ -164,6 +192,7 @@ public final class HdmiPortInfo implements Parcelable {
dest.writeInt(mCecSupported ? 1 : 0);
dest.writeInt(mArcSupported ? 1 : 0);
dest.writeInt(mMhlSupported ? 1 : 0);
dest.writeInt(mEarcSupported ? 1 : 0);
}
@NonNull
@@ -175,7 +204,8 @@ public final class HdmiPortInfo implements Parcelable {
s.append("address: ").append(String.format("0x%04x", mAddress)).append(", ");
s.append("cec: ").append(mCecSupported).append(", ");
s.append("arc: ").append(mArcSupported).append(", ");
s.append("mhl: ").append(mMhlSupported);
s.append("mhl: ").append(mMhlSupported).append(", ");
s.append("earc: ").append(mEarcSupported);
return s.toString();
}
@@ -187,12 +217,12 @@ public final class HdmiPortInfo implements Parcelable {
final HdmiPortInfo other = (HdmiPortInfo) o;
return mId == other.mId && mType == other.mType && mAddress == other.mAddress
&& mCecSupported == other.mCecSupported && mArcSupported == other.mArcSupported
&& mMhlSupported == other.mMhlSupported;
&& mMhlSupported == other.mMhlSupported && mEarcSupported == other.mEarcSupported;
}
@Override
public int hashCode() {
return java.util.Objects.hash(
mId, mType, mAddress, mCecSupported, mArcSupported, mMhlSupported);
mId, mType, mAddress, mCecSupported, mArcSupported, mMhlSupported, mEarcSupported);
}
}

View File

@@ -25,6 +25,8 @@ namespace android {
// keep these values in sync with ExtraAudioDescriptor.java
#define STANDARD_NONE 0
#define STANDARD_EDID 1
#define STANDARD_SADB 2
#define STANDARD_VSADB 3
static inline status_t audioStandardFromNative(audio_standard_t nStandard, int* standard) {
status_t result = NO_ERROR;
@@ -35,6 +37,12 @@ static inline status_t audioStandardFromNative(audio_standard_t nStandard, int*
case AUDIO_STANDARD_EDID:
*standard = STANDARD_EDID;
break;
case AUDIO_STANDARD_SADB:
*standard = STANDARD_SADB;
break;
case AUDIO_STANDARD_VSADB:
*standard = STANDARD_VSADB;
break;
default:
result = BAD_VALUE;
}

View File

@@ -37,26 +37,38 @@ public class HdmiPortInfoTest {
boolean isCec = true;
boolean isMhl = false;
boolean isArcSupported = false;
boolean isEarcSupported = false;
new EqualsTester()
.addEqualityGroup(
new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported),
new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported))
new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
isEarcSupported),
new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
isEarcSupported))
.addEqualityGroup(
new HdmiPortInfo(
portId + 1, portType, address, isCec, isMhl, isArcSupported))
portId + 1, portType, address, isCec, isMhl, isArcSupported,
isEarcSupported))
.addEqualityGroup(
new HdmiPortInfo(
portId, portType + 1, address, isCec, isMhl, isArcSupported))
portId, portType + 1, address, isCec, isMhl, isArcSupported,
isEarcSupported))
.addEqualityGroup(
new HdmiPortInfo(
portId, portType, address + 1, isCec, isMhl, isArcSupported))
portId, portType, address + 1, isCec, isMhl, isArcSupported,
isEarcSupported))
.addEqualityGroup(
new HdmiPortInfo(portId, portType, address, !isCec, isMhl, isArcSupported))
new HdmiPortInfo(portId, portType, address, !isCec, isMhl, isArcSupported,
isEarcSupported))
.addEqualityGroup(
new HdmiPortInfo(portId, portType, address, isCec, !isMhl, isArcSupported))
new HdmiPortInfo(portId, portType, address, isCec, !isMhl, isArcSupported,
isEarcSupported))
.addEqualityGroup(
new HdmiPortInfo(portId, portType, address, isCec, isMhl, !isArcSupported))
new HdmiPortInfo(portId, portType, address, isCec, isMhl, !isArcSupported,
isEarcSupported))
.addEqualityGroup(
new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
!isEarcSupported))
.testEquals();
}
}

View File

@@ -41,11 +41,21 @@ public class AudioDescriptor implements Parcelable {
* The Extended Display Identification Data (EDID) standard for a short audio descriptor.
*/
public static final int STANDARD_EDID = 1;
/**
* The standard for a Speaker Allocation Data Block (SADB).
*/
public static final int STANDARD_SADB = 2;
/**
* The standard for a Vendor-Specific Audio Data Block (VSADB).
*/
public static final int STANDARD_VSADB = 3;
/** @hide */
@IntDef({
STANDARD_NONE,
STANDARD_EDID,
STANDARD_SADB,
STANDARD_VSADB,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AudioDescriptorStandard {}

View File

@@ -585,6 +585,10 @@ public class AidlConversion {
switch (standard) {
case AudioDescriptor.STANDARD_EDID:
return AudioStandard.EDID;
case AudioDescriptor.STANDARD_SADB:
return AudioStandard.SADB;
case AudioDescriptor.STANDARD_VSADB:
return AudioStandard.VSADB;
case AudioDescriptor.STANDARD_NONE:
default:
return AudioStandard.NONE;
@@ -599,6 +603,10 @@ public class AidlConversion {
switch (standard) {
case AudioStandard.EDID:
return AudioDescriptor.STANDARD_EDID;
case AudioStandard.SADB:
return AudioDescriptor.STANDARD_SADB;
case AudioStandard.VSADB:
return AudioDescriptor.STANDARD_VSADB;
case AudioStandard.NONE:
default:
return AudioDescriptor.STANDARD_NONE;

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

@@ -70,6 +70,10 @@ import java.util.function.Predicate;
* <p>It can be created only by {@link HdmiCecController#create}
*
* <p>Declared as package-private, accessed by {@link HdmiControlService} only.
*
* <p>Also manages HDMI HAL methods that are shared between CEC and eARC. To make eARC
* fully independent of the presence of a CEC HAL, we should split this class into HdmiCecController
* and HdmiController TODO(b/255751565).
*/
final class HdmiCecController {
private static final String TAG = "HdmiCecController";
@@ -412,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.
*
@@ -1066,6 +1095,8 @@ final class HdmiCecController {
HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.length];
int i = 0;
for (android.hardware.tv.hdmi.HdmiPortInfo portInfo : hdmiPortInfos) {
// TODO: the earc argument is stubbed for now.
// To be replaced by portInfo.earcSupported.
hdmiPortInfo[i] =
new HdmiPortInfo(
portInfo.portId,
@@ -1073,7 +1104,8 @@ final class HdmiCecController {
portInfo.physicalAddress,
portInfo.cecSupported,
false,
portInfo.arcSupported);
portInfo.arcSupported,
false);
i++;
}
return hdmiPortInfo;
@@ -1234,7 +1266,8 @@ final class HdmiCecController {
portInfo.physicalAddress,
portInfo.cecSupported,
false,
portInfo.arcSupported);
portInfo.arcSupported,
false);
i++;
}
return hdmiPortInfo;
@@ -1415,7 +1448,8 @@ final class HdmiCecController {
portInfo.physicalAddress,
portInfo.cecSupported,
false,
portInfo.arcSupported);
portInfo.arcSupported,
false);
i++;
}
return hdmiPortInfo;

View File

@@ -54,7 +54,7 @@ import java.util.concurrent.ArrayBlockingQueue;
* Class that models a logical CEC device hosted in this system. Handles initialization, CEC
* commands that call for actions customized per device type.
*/
abstract class HdmiCecLocalDevice {
abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
private static final String TAG = "HdmiCecLocalDevice";
private static final int MAX_HDMI_ACTIVE_SOURCE_HISTORY = 10;
@@ -67,8 +67,6 @@ abstract class HdmiCecLocalDevice {
// 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 {
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 {
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

@@ -470,7 +470,8 @@ public class HdmiCecNetwork {
for (HdmiPortInfo info : cecPortInfo) {
if (mhlSupportedPorts.contains(info.getId())) {
result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
info.isCecSupported(), true, info.isArcSupported()));
info.isCecSupported(), true, info.isArcSupported(),
info.isEarcSupported()));
} else {
result.add(info);
}

View File

@@ -18,6 +18,7 @@ package com.android.server.hdmi;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_ENABLED;
import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED;
@@ -126,6 +127,8 @@ import java.util.stream.Collectors;
/**
* Provides a service for sending and processing HDMI control messages,
* HDMI-CEC and MHL control command, and providing the information on both standard.
*
* Additionally takes care of establishing and managing an eARC connection.
*/
public class HdmiControlService extends SystemService {
private static final String TAG = "HdmiControlService";
@@ -184,13 +187,14 @@ public class HdmiControlService extends SystemService {
static final String PERMISSION = "android.permission.HDMI_CEC";
// The reason code to initiate initializeCec().
// The reason code to initiate initializeCec() and initializeEarc().
static final int INITIATED_BY_ENABLE_CEC = 0;
static final int INITIATED_BY_BOOT_UP = 1;
static final int INITIATED_BY_SCREEN_ON = 2;
static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
static final int INITIATED_BY_HOTPLUG = 4;
static final int INITIATED_BY_SOUNDBAR_MODE = 5;
static final int INITIATED_BY_ENABLE_EARC = 6;
// The reason code representing the intent action that drives the standby
// procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
@@ -384,6 +388,18 @@ public class HdmiControlService extends SystemService {
@HdmiControlManager.HdmiCecControl
private int mHdmiControlEnabled;
// Set to true while the eARC feature is supported by the hardware on at least one port
// and the eARC HAL is present.
@GuardedBy("mLock")
@VisibleForTesting
protected boolean mEarcSupported;
// Set to true while the eARC feature is enabled.
@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".
@@ -417,6 +433,12 @@ public class HdmiControlService extends SystemService {
private HdmiCecPowerStatusController mPowerStatusController;
@Nullable
private HdmiEarcController mEarcController;
@Nullable
private HdmiEarcLocalDevice mEarcLocalDevice;
@ServiceThreadOnly
private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@@ -631,9 +653,13 @@ public class HdmiControlService extends SystemService {
mPowerStatusController = new HdmiCecPowerStatusController(this);
}
mPowerStatusController.setPowerStatus(getInitialPowerStatus());
mProhibitMode = false;
setProhibitMode(false);
mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
synchronized (mLock) {
mEarcEnabled = (mHdmiCecConfig.getIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED) == EARC_FEATURE_ENABLED);
}
setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
@@ -646,7 +672,6 @@ public class HdmiControlService extends SystemService {
}
if (mCecController == null) {
Slog.i(TAG, "Device does not support HDMI-CEC.");
return;
}
if (mMhlController == null) {
mMhlController = HdmiMhlControllerStub.create(this);
@@ -654,15 +679,56 @@ public class HdmiControlService extends SystemService {
if (!mMhlController.isReady()) {
Slog.i(TAG, "Device does not support MHL-control.");
}
if (mEarcController == null) {
mEarcController = HdmiEarcController.create(this);
}
if (mEarcController == null) {
Slog.i(TAG, "Device does not support eARC.");
}
if (mCecController == null && mEarcController == null) {
return;
}
mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
if (mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
if (isCecControlEnabled()) {
initializeCec(INITIATED_BY_BOOT_UP);
} else {
mCecController.enableCec(false);
}
mMhlDevices = Collections.emptyList();
synchronized (mLock) {
mMhlDevices = Collections.emptyList();
}
mHdmiCecNetwork.initPortInfo();
List<HdmiPortInfo> ports = getPortInfo();
synchronized (mLock) {
mEarcSupported = false;
for (HdmiPortInfo port : 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 &= (mEarcController != null);
}
if (isEarcSupported()) {
if (isEarcEnabled()) {
initializeEarc(INITIATED_BY_BOOT_UP);
} else {
setEarcEnabledInHal(false);
}
}
mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
new HdmiCecConfig.SettingChangeListener() {
@Override
@@ -743,8 +809,16 @@ public class HdmiControlService extends SystemService {
mCecController.enableWakeupByOtp(tv().getAutoWakeup());
}
}
},
mServiceThreadExecutor);
}, mServiceThreadExecutor);
mHdmiCecConfig.registerChangeListener(HdmiControlManager.SETTING_NAME_EARC_ENABLED,
new HdmiCecConfig.SettingChangeListener() {
@Override
public void onChange(String setting) {
@HdmiControlManager.HdmiCecControl int enabled = mHdmiCecConfig.getIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED);
setEarcEnabled(enabled);
}
}, mServiceThreadExecutor);
}
/** Returns true if the device screen is off */
@@ -1083,7 +1157,8 @@ public class HdmiControlService extends SystemService {
}
@ServiceThreadOnly
private void initializeCecLocalDevices(final int initiatedBy) {
@VisibleForTesting
protected void initializeCecLocalDevices(final int initiatedBy) {
assertRunOnServiceThread();
// A container for [Device type, Local device info].
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
@@ -1245,7 +1320,6 @@ public class HdmiControlService extends SystemService {
* Returns {@link Looper} of main thread. Use this {@link Looper} instance
* for tasks that are running on main service thread.
*/
@VisibleForTesting
protected Looper getServiceLooper() {
return mHandler.getLooper();
}
@@ -2568,7 +2642,9 @@ public class HdmiControlService extends SystemService {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println("mProhibitMode: " + mProhibitMode);
synchronized (mLock) {
pw.println("mProhibitMode: " + mProhibitMode);
}
pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
pw.println("mIsCecAvailable: " + mIsCecAvailable);
pw.println("mCecVersion: " + mCecVersion);
@@ -2577,9 +2653,9 @@ public class HdmiControlService extends SystemService {
// System settings
pw.println("System_settings:");
pw.increaseIndent();
pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
pw.println("mMhlInputChangeEnabled: " + isMhlInputChangeEnabled());
pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
pw.println("mHdmiCecVolumeControlEnabled: " + mHdmiCecVolumeControl);
pw.println("mHdmiCecVolumeControlEnabled: " + getHdmiCecVolumeControl());
pw.decreaseIndent();
// CEC settings
@@ -2605,6 +2681,14 @@ public class HdmiControlService extends SystemService {
pw.increaseIndent();
mMhlController.dump(pw);
pw.decreaseIndent();
pw.print("eARC local device: ");
pw.increaseIndent();
if (mEarcLocalDevice == null) {
pw.println("None. eARC is either disabled or not available.");
} else {
mEarcLocalDevice.dump(pw);
}
pw.decreaseIndent();
mHdmiCecNetwork.dump(pw);
if (mCecController != null) {
pw.println("mCecController: ");
@@ -3313,6 +3397,18 @@ public class HdmiControlService extends SystemService {
}
}
private boolean isEarcEnabled() {
synchronized (mLock) {
return mEarcEnabled;
}
}
private boolean isEarcSupported() {
synchronized (mLock) {
return mEarcSupported;
}
}
@ServiceThreadOnly
int getPowerStatus() {
assertRunOnServiceThread();
@@ -3384,7 +3480,7 @@ public class HdmiControlService extends SystemService {
mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
false);
if (mCecController != null) {
if (mHdmiControlEnabled == HDMI_CEC_CONTROL_ENABLED) {
if (isCecControlEnabled()) {
int startReason = -1;
switch (wakeUpAction) {
case WAKE_UP_SCREEN_ON:
@@ -3406,6 +3502,25 @@ public class HdmiControlService extends SystemService {
} else {
Slog.i(TAG, "Device does not support HDMI-CEC.");
}
if (isEarcSupported()) {
if (isEarcEnabled()) {
int startReason = -1;
switch (wakeUpAction) {
case WAKE_UP_SCREEN_ON:
startReason = INITIATED_BY_SCREEN_ON;
break;
case WAKE_UP_BOOT_UP:
startReason = INITIATED_BY_BOOT_UP;
break;
default:
Slog.e(TAG, "wakeUpAction " + wakeUpAction + " not defined.");
return;
}
initializeEarc(startReason);
} else {
setEarcEnabledInHal(false);
}
}
// TODO: Initialize MHL local devices.
}
@@ -4296,4 +4411,133 @@ public class HdmiControlService extends SystemService {
getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC,
volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags);
}
private void initializeEarc(int initiatedBy) {
Slog.i(TAG, "eARC initialized, reason = " + initiatedBy);
setEarcEnabledInHal(true);
initializeEarcLocalDevice(initiatedBy);
}
@ServiceThreadOnly
@VisibleForTesting
protected void initializeEarcLocalDevice(final int initiatedBy) {
// TODO remove initiatedBy argument if it stays unused
assertRunOnServiceThread();
if (mEarcLocalDevice == null) {
mEarcLocalDevice = HdmiEarcLocalDevice.create(this, HdmiDeviceInfo.DEVICE_TV);
}
// TODO create HdmiEarcLocalDeviceRx if we're an audio system device.
}
@ServiceThreadOnly
@VisibleForTesting
protected void setEarcEnabled(@HdmiControlManager.EarcFeature int enabled) {
assertRunOnServiceThread();
synchronized (mLock) {
mEarcEnabled = (enabled == EARC_FEATURE_ENABLED);
if (!isEarcSupported()) {
Slog.i(TAG, "Enabled/disabled eARC setting, but the hardware doesn´t support eARC. "
+ "This settings change doesn´t have an effect.");
return;
}
if (mEarcEnabled) {
onEnableEarc();
return;
}
}
runOnServiceThread(new Runnable() {
@Override
public void run() {
onDisableEarc();
}
});
}
@VisibleForTesting
protected void setEarcSupported(boolean supported) {
synchronized (mLock) {
mEarcSupported = supported;
}
}
@ServiceThreadOnly
private void onEnableEarc() {
initializeEarc(INITIATED_BY_ENABLE_EARC);
}
@ServiceThreadOnly
private void onDisableEarc() {
disableEarcLocalDevice();
setEarcEnabledInHal(false);
clearEarcLocalDevice();
}
@ServiceThreadOnly
@VisibleForTesting
protected void clearEarcLocalDevice() {
assertRunOnServiceThread();
mEarcLocalDevice = null;
}
@ServiceThreadOnly
@VisibleForTesting
protected void addEarcLocalDevice(HdmiEarcLocalDevice localDevice) {
assertRunOnServiceThread();
mEarcLocalDevice = localDevice;
}
@ServiceThreadOnly
@VisibleForTesting
HdmiEarcLocalDevice getEarcLocalDevice() {
assertRunOnServiceThread();
return mEarcLocalDevice;
}
private void disableEarcLocalDevice() {
if (mEarcLocalDevice == null) {
return;
}
mEarcLocalDevice.disableDevice();
}
@ServiceThreadOnly
@VisibleForTesting
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);
}
}
@ServiceThreadOnly
void handleEarcCapabilitiesReported(byte[] rawCapabilities, int portId) {
assertRunOnServiceThread();
if (!getPortInfo(portId).isEarcSupported()) {
Slog.w(TAG,
"Tried to process eARC capabilities from 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 eARC capabilities, but even if it did, it won't take effect.
if (mEarcLocalDevice != null) {
mEarcLocalDevice.handleEarcCapabilitiesReported(rawCapabilities);
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.hdmi;
import android.os.Handler;
import android.os.Looper;
import com.android.internal.annotations.VisibleForTesting;
final class HdmiEarcController {
private static final String TAG = "HdmiEarcController";
// Handler instance to process HAL calls.
private Handler mControlHandler;
private final HdmiControlService mService;
// Private constructor. Use HdmiEarcController.create().
private HdmiEarcController(HdmiControlService service) {
mService = service;
}
/**
* A factory method to get {@link HdmiEarcController}. If it fails to initialize
* inner device or has no device it will return {@code null}.
*
* <p>Declared as package-private, accessed by {@link HdmiControlService} only.
* @param service {@link HdmiControlService} instance used to create internal handler
* and to pass callback for incoming message or event.
* @return {@link HdmiEarcController} if device is initialized successfully. Otherwise,
* returns {@code null}.
*/
static HdmiEarcController create(HdmiControlService service) {
// TODO add the native wrapper and return null if eARC HAL is not present.
HdmiEarcController controller = new HdmiEarcController(service);
controller.init();
return controller;
}
private void init() {
mControlHandler = new Handler(mService.getServiceLooper());
}
private void assertRunOnServiceThread() {
if (Looper.myLooper() != mControlHandler.getLooper()) {
throw new IllegalStateException("Should run on service thread.");
}
}
@VisibleForTesting
void runOnServiceThread(Runnable runnable) {
mControlHandler.post(new WorkSourceUidPreservingRunnable(runnable));
}
/**
* Enable eARC in the HAL
* @param enabled
*/
@HdmiAnnotations.ServiceThreadOnly
void setEarcEnabled(boolean enabled) {
assertRunOnServiceThread();
// Stub.
// TODO: bind to native.
// 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;
}
/**
* Ask the HAL to report the last eARC capabilities that the connected audio system reported.
* @return the raw eARC capabilities
*/
@HdmiAnnotations.ServiceThreadOnly
byte[] getLastReportedCaps() {
// Stub. TODO: bind to native.
return new byte[] {};
}
final class EarcCallback {
public void onStateChange(@Constants.EarcStatus int status, int portId) {
runOnServiceThread(
() -> mService.handleEarcStateChange(status, portId));
}
public void onCapabilitiesReported(byte[] rawCapabilities, int portId) {
runOnServiceThread(
() -> mService.handleEarcCapabilitiesReported(rawCapabilities, portId));
}
}
// TODO: bind to native.
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.
*/
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 abstract void handleEarcCapabilitiesReported(byte[] rawCapabilities);
protected void disableDevice() {
}
/** Dump internal status of HdmiEarcLocalDevice object */
protected void dump(final IndentingPrintWriter pw) {
// Should be overridden in the more specific classes
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.os.Handler;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Represents a local eARC device of type TX residing in the Android system.
* Only TV panel devices can have a local eARC TX device.
*/
public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice {
private static final String TAG = "HdmiEarcLocalDeviceTx";
// How long to wait for the audio system to report its capabilities after eARC was connected
static final long REPORT_CAPS_MAX_DELAY_MS = 2_000;
// eARC Capability Data Structure parameters
private static final int EARC_CAPS_PAYLOAD_LENGTH = 0x02;
private static final int EARC_CAPS_DATA_START = 0x03;
// Table 55 CTA Data Block Tag Codes
private static final int TAGCODE_AUDIO_DATA_BLOCK = 0x01; // Includes one or more Short Audio
// Descriptors
private static final int TAGCODE_SADB_DATA_BLOCK = 0x04; // Speaker Allocation Data Block
private static final int TAGCODE_USE_EXTENDED_TAG = 0x07; // Use Extended Tag
// Table 56 Extended Tag Format (2nd byte of Data Block)
private static final int EXTENDED_TAGCODE_VSADB = 0x11; // Vendor-Specific Audio Data Block
// eARC capability mask and shift
private static final int EARC_CAPS_TAGCODE_MASK = 0xE0;
private static final int EARC_CAPS_TAGCODE_SHIFT = 0x05;
private static final int EARC_CAPS_LENGTH_MASK = 0x1F;
// Handler and runnable for waiting for the audio system to report its capabilities after eARC
// was connected
private Handler mReportCapsHandler;
private ReportCapsRunnable mReportCapsRunnable;
HdmiEarcLocalDeviceTx(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_TV);
mReportCapsHandler = new Handler(service.getServiceLooper());
mReportCapsRunnable = new ReportCapsRunnable();
}
protected void handleEarcStateChange(@Constants.EarcStatus int status) {
synchronized (mLock) {
HdmiLogger.debug(TAG, "eARC state change [old:%b new %b]", mEarcStatus,
status);
mEarcStatus = status;
}
mReportCapsHandler.removeCallbacksAndMessages(null);
if (status == HDMI_EARC_STATUS_IDLE) {
notifyEarcStatusToAudioService(false, new ArrayList<>());
} else if (status == HDMI_EARC_STATUS_ARC_PENDING) {
notifyEarcStatusToAudioService(false, new ArrayList<>());
} else if (status == HDMI_EARC_STATUS_EARC_CONNECTED) {
mReportCapsHandler.postDelayed(mReportCapsRunnable, REPORT_CAPS_MAX_DELAY_MS);
}
}
protected void handleEarcCapabilitiesReported(byte[] rawCapabilities) {
synchronized (mLock) {
if (mEarcStatus == HDMI_EARC_STATUS_EARC_CONNECTED
&& mReportCapsHandler.hasCallbacks(mReportCapsRunnable)) {
mReportCapsHandler.removeCallbacksAndMessages(null);
List<AudioDescriptor> audioDescriptors = parseCapabilities(rawCapabilities);
notifyEarcStatusToAudioService(true, audioDescriptors);
}
}
}
private void notifyEarcStatusToAudioService(
boolean enabled, List<AudioDescriptor> audioDescriptors) {
AudioDeviceAttributes attributes = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "",
new ArrayList<AudioProfile>(), audioDescriptors);
mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
}
/**
* Runnable for waiting for a certain amount of time for the audio system to report its
* capabilities after eARC was connected. If the audio system doesn´t report its capabilities in
* this time, we inform AudioService about the connection state only, without any specified
* capabilities.
*/
private class ReportCapsRunnable implements Runnable {
@Override
public void run() {
synchronized (mLock) {
if (mEarcStatus == HDMI_EARC_STATUS_EARC_CONNECTED) {
notifyEarcStatusToAudioService(true, new ArrayList<>());
}
}
}
}
private List<AudioDescriptor> parseCapabilities(byte[] rawCapabilities) {
List<AudioDescriptor> audioDescriptors = new ArrayList<>();
if (rawCapabilities.length < EARC_CAPS_DATA_START + 1) {
Slog.i(TAG, "Raw eARC capabilities array doesn´t contain any blocks.");
return audioDescriptors;
}
int earcCapsSize = rawCapabilities[EARC_CAPS_PAYLOAD_LENGTH];
if (rawCapabilities.length < earcCapsSize) {
Slog.i(TAG, "Raw eARC capabilities array is shorter than the reported payload length.");
return audioDescriptors;
}
int firstByteOfBlock = EARC_CAPS_DATA_START;
while (firstByteOfBlock < earcCapsSize) {
// Tag Code: Bit 5-7
int tagCode =
(rawCapabilities[firstByteOfBlock] & EARC_CAPS_TAGCODE_MASK)
>> EARC_CAPS_TAGCODE_SHIFT;
// Length: Bit 0-4
int length = rawCapabilities[firstByteOfBlock] & EARC_CAPS_LENGTH_MASK;
if (length == 0) {
// End Marker of eARC capability.
break;
}
AudioDescriptor descriptor;
switch (tagCode) {
case TAGCODE_AUDIO_DATA_BLOCK:
int earcSadLen = length;
if (length % 3 != 0) {
Slog.e(TAG, "Invalid length of SAD block: expected a factor of 3 but got "
+ length % 3);
break;
}
byte[] earcSad = new byte[earcSadLen];
System.arraycopy(rawCapabilities, firstByteOfBlock + 1, earcSad, 0, earcSadLen);
for (int i = 0; i < earcSadLen; i += 3) {
descriptor = new AudioDescriptor(
AudioDescriptor.STANDARD_EDID,
AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
Arrays.copyOfRange(earcSad, i, i + 3));
audioDescriptors.add(descriptor);
}
break;
case TAGCODE_SADB_DATA_BLOCK:
//Include Tag code size
int earcSadbLen = length + 1;
byte[] earcSadb = new byte[earcSadbLen];
System.arraycopy(rawCapabilities, firstByteOfBlock, earcSadb, 0, earcSadbLen);
descriptor = new AudioDescriptor(
AudioDescriptor.STANDARD_SADB,
AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
earcSadb);
audioDescriptors.add(descriptor);
break;
case TAGCODE_USE_EXTENDED_TAG:
if (rawCapabilities[firstByteOfBlock + 1] == EXTENDED_TAGCODE_VSADB) {
int earcVsadbLen = length + 1; //Include Tag code size
byte[] earcVsadb = new byte[earcVsadbLen];
System.arraycopy(rawCapabilities, firstByteOfBlock, earcVsadb, 0,
earcVsadbLen);
descriptor = new AudioDescriptor(
AudioDescriptor.STANDARD_VSADB,
AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
earcVsadb);
audioDescriptors.add(descriptor);
}
break;
default:
Slog.w(TAG, "This tagcode was not handled: " + tagCode);
break;
}
firstByteOfBlock += (length + 1);
}
return audioDescriptors;
}
/** Dump internal status of HdmiEarcLocalDeviceTx object */
protected void dump(final IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("TX, mEarcStatus: " + mEarcStatus);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.hdmi;
/**
* Class that models an HDMI device hosted in this system.
* Can be used to share methods between CEC and eARC local devices.
* Currently just a placeholder.
*/
abstract class HdmiLocalDevice {
private static final String TAG = "HdmiLocalDevice";
protected final HdmiControlService mService;
protected final int mDeviceType;
protected final Object mLock;
protected HdmiLocalDevice(HdmiControlService service, int deviceType) {
mService = service;
mDeviceType = deviceType;
mLock = service.getServiceLock();
}
}

View File

@@ -605,4 +605,36 @@ public class HdmiCecNetworkTest {
assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
}
@Test
public void disableCec_clearCecLocalDevices() {
mHdmiCecNetwork.clearLocalDevices();
mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV,
new HdmiCecLocalDeviceTv(mHdmiControlService));
assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
HdmiCecLocalDeviceTv.class);
mHdmiControlService.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(0);
}
@Test
public void disableEarc_doNotClearCecLocalDevices() {
mHdmiCecNetwork.clearLocalDevices();
mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV,
new HdmiCecLocalDeviceTv(mHdmiControlService));
assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
HdmiCecLocalDeviceTv.class);
mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
HdmiCecLocalDeviceTv.class);
}
}

View File

@@ -22,6 +22,7 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +30,8 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -127,13 +130,13 @@ public class HdmiControlServiceTest {
mLocalDevices.add(mPlaybackDeviceSpy);
mHdmiPortInfo = new HdmiPortInfo[4];
mHdmiPortInfo[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false, false);
mHdmiPortInfo[1] =
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false);
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false, false);
mHdmiPortInfo[2] =
new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true, true);
mHdmiPortInfo[3] =
new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiControlServiceSpy.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
@@ -1082,6 +1085,174 @@ public class HdmiControlServiceTest {
assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
}
@Test
public void disableEarc_clearEarcLocalDevice() {
mHdmiControlServiceSpy.setEarcSupported(true);
mHdmiControlServiceSpy.clearEarcLocalDevice();
mHdmiControlServiceSpy.addEarcLocalDevice(
new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy));
assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull();
mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNull();
}
@Test
public void disableCec_doNotClearEarcLocalDevice() {
mHdmiControlServiceSpy.setEarcSupported(true);
mHdmiControlServiceSpy.clearEarcLocalDevice();
mHdmiControlServiceSpy.addEarcLocalDevice(
new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy));
assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull();
mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mTestLooper.dispatchAll();
assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull();
}
@Test
public void enableCec_initializeCecLocalDevices() {
mHdmiControlServiceSpy.setEarcSupported(true);
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mTestLooper.dispatchAll();
mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).initializeCecLocalDevices(anyInt());
verify(mHdmiControlServiceSpy, times(0)).initializeEarcLocalDevice(anyInt());
}
@Test
public void enableEarc_initializeEarcLocalDevices() {
mHdmiControlServiceSpy.setEarcSupported(true);
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(0)).initializeCecLocalDevices(anyInt());
verify(mHdmiControlServiceSpy, times(1)).initializeEarcLocalDevice(anyInt());
}
@Test
public void disableCec_DoNotInformHalAboutEarc() {
mHdmiControlServiceSpy.setEarcSupported(true);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean());
}
@Test
public void disableEarc_informHalAboutEarc() {
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false);
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true);
}
@Test
public void enableCec_DoNotInformHalAboutEarc() {
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean());
}
@Test
public void enableEarc_informHalAboutEarc() {
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true);
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false);
}
@Test
public void bootWithEarcEnabled_informHalAboutEarc() {
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.initService();
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true);
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false);
}
@Test
public void bootWithEarcDisabled_informHalAboutEarc() {
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.initService();
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false);
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true);
}
@Test
public void wakeUpWithEarcEnabled_informHalAboutEarc() {
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_ENABLED);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true);
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false);
}
@Test
public void wakeUpWithEarcDisabled_informHalAboutEarc() {
mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED,
HdmiControlManager.EARC_FEATURE_DISABLED);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
Mockito.clearInvocations(mHdmiControlServiceSpy);
mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false);
verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true);
}
protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
private boolean mCanGoToStandby;

View File

@@ -0,0 +1,315 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.hdmi;
import static android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
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_EARC_PENDING;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioDescriptor;
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
/** Tests for {@link HdmiEarcLocalDeviceTx} class. */
public class HdmiEarcLocalDeviceTxTest {
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private HdmiEarcLocalDevice mHdmiEarcLocalDeviceTx;
private FakeNativeWrapper mNativeWrapper;
private FakePowerManagerWrapper mPowerManager;
private byte[] mEarcCapabilities = new byte[]{
0x01, 0x01, 0x1a, 0x35, 0x0f, 0x7f, 0x07, 0x15, 0x07, 0x50, 0x3d, 0x1f, (byte) 0xc0,
0x57, 0x06, 0x03, 0x67, 0x7e, 0x03, 0x5f, 0x7e, 0x03, 0x5f, 0x7e, 0x01, (byte) 0x83,
0x5f, 0x00, 0x00, 0x00, 0x00, 0x00};
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
@Mock
private AudioManager mAudioManager;
@Captor
ArgumentCaptor<AudioDeviceAttributes> mAudioAttributesCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isCecControlEnabled() {
return true;
}
@Override
boolean isTvDevice() {
return true;
}
@Override
protected void writeStringSystemProperty(String key, String value) {
// do nothing
}
@Override
boolean isPowerStandby() {
return false;
}
@Override
AudioManager getAudioManager() {
return mAudioManager;
}
};
mHdmiControlService.setIoLooper(mMyLooper);
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
mNativeWrapper = new FakeNativeWrapper();
mHdmiCecController = HdmiCecController.createWithNativeWrapper(
mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mTestLooper.dispatchAll();
mHdmiControlService.initializeEarcLocalDevice(HdmiControlService.INITIATED_BY_BOOT_UP);
mHdmiEarcLocalDeviceTx = mHdmiControlService.getEarcLocalDevice();
}
@Test
public void earcGetsConnected_capsReportedInTime_sad() {
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS - 200);
mTestLooper.dispatchAll();
mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(new byte[]{
0x01, 0x01, 0x1a, 0x35, 0x0f, 0x7f, 0x07, 0x15, 0x07, 0x50, 0x3d, 0x1f, (byte) 0xc0,
0x57, 0x06, 0x03, 0x67, 0x7e, 0x03, 0x5f, 0x7e, 0x03, 0x5f, 0x7e, 0x01, 0x00, 0x5f,
0x00, 0x00, 0x00, 0x00, 0x00
});
mTestLooper.dispatchAll();
verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
mAudioAttributesCaptor.capture(), eq(1));
AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
List<AudioDescriptor> expectedDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {15, 127, 7}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {21, 7, 80}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {61, 31, -64}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {87, 6, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {103, 126, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {95, 126, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {95, 126, 1})));
assertThat(descriptors).isEqualTo(expectedDescriptors);
}
@Test
public void earcGetsConnected_capsReportedInTime_sad_sadb() {
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS - 200);
mTestLooper.dispatchAll();
mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(new byte[]{
0x01, 0x01, 0x1a, 0x35, 0x0f, 0x7f, 0x07, 0x15, 0x07, 0x50, 0x3d, 0x1f, (byte) 0xc0,
0x57, 0x06, 0x03, 0x67, 0x7e, 0x03, 0x5f, 0x7e, 0x03, 0x5f, 0x7e, 0x01, (byte) 0x83,
0x5f, 0x00, 0x00, 0x00, 0x00, 0x00});
mTestLooper.dispatchAll();
verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
mAudioAttributesCaptor.capture(), eq(1));
AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
List<AudioDescriptor> expectedDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {15, 127, 7}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {21, 7, 80}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {61, 31, -64}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {87, 6, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {103, 126, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {95, 126, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {95, 126, 1}),
new AudioDescriptor(AudioDescriptor.STANDARD_SADB, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {-125, 95, 0, 0})));
assertThat(descriptors).isEqualTo(expectedDescriptors);
}
@Test
public void earcGetsConnected_capsReportedInTime_sad_sadb_vsadb() {
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS - 200);
mTestLooper.dispatchAll();
mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(new byte[]{
0x01, 0x01, 0x21, 0x35, 0x5F, 0x7E, 0x03, 0x5F, 0x7E, 0x01, 0x67, 0x7E, 0x03, 0x57,
0x06, 0x03, 0x3D, 0x1E, (byte) 0xC0, 0x15, 0x07, 0x50, 0x0F, 0x7F, 0x07,
(byte) 0x83, 0x5F, 0x00, 0x00, (byte) 0xE6, 0x11, 0x46, (byte) 0xD0, 0x00, 0x70,
0x00, 0x03, 0x01, (byte) 0x80, 0x00, (byte) 0x9D, (byte) 0xAD, (byte) 0x9E, 0x7B,
0x08, (byte) 0xC1, (byte) 0xA8, 0x23, (byte) 0x9B, 0x49, 0x5C, (byte) 0xF5, 0x6B,
(byte) 0xAC, 0x22, (byte) 0xC2, (byte) 0x80, 0x48, 0x67, 0x7F, 0x59, 0x1C, 0x20,
0x71, 0x35, 0x25, (byte) 0x9F, 0x43, 0x70, 0x1E, 0x32, 0x15, 0x60, (byte) 0xED,
(byte) 0xC8, 0x77, (byte) 0xA3, 0x24, 0x2E, (byte) 0xDA, (byte) 0x94, 0x6D, 0x35,
0x34, 0x0F, 0x30, 0x62, 0x1A, 0x3B, (byte) 0xC9, 0x5A, (byte) 0xE6, (byte) 0xD8,
0x22, 0x11, 0x56, (byte) 0xA6, (byte) 0x99, (byte) 0xCF, (byte) 0xE3, 0x1B,
(byte) 0x88, (byte) 0xA0, 0x2A, 0x5B, 0x6C, 0x5E, 0x53, 0x01, 0x47, 0x69, 0x51,
0x61, (byte) 0xC7, (byte) 0xCB, 0x1B, 0x28, 0x14, 0x23, 0x10, (byte) 0xB1, 0x34,
0x5E, 0x57, (byte) 0x97, (byte) 0xB3, 0x78, 0x03, 0x79, (byte) 0x8A, (byte) 0xFE,
0x1E, (byte) 0xC8, (byte) 0xAB, 0x14, 0x74, 0x73, (byte) 0xFA, (byte) 0xBB,
(byte) 0xF7, 0x4E, 0x00, (byte) 0xFC, 0x5C, (byte) 0xDC, (byte) 0x8B, (byte) 0xC9,
0x1E, 0x16, 0x35, (byte) 0xB1, (byte) 0x98, (byte) 0xEB, 0x2B, (byte) 0xE6,
(byte) 0xFC, (byte) 0xCC, 0x3C, 0x30, 0x19, 0x40, (byte) 0xC0, 0x50, (byte) 0xF2,
0x58, 0x30, 0x4B, 0x0C, 0x7A, (byte) 0xE0, (byte) 0xFF, 0x7A, 0x64, 0x78,
(byte) 0xF8, 0x56, (byte) 0xF8, 0x6E, 0x72, 0x42, 0x49, 0x4E, (byte) 0xA6,
(byte) 0x95, (byte) 0xF5, 0x4C, 0x4F, (byte) 0xFF, 0x7F, 0x21, (byte) 0xA2,
(byte) 0x98, 0x33, (byte) 0x90, (byte) 0xFD, 0x17, 0x08, 0x13, (byte) 0xB2, 0x00,
(byte) 0xA9, (byte) 0xB5, (byte) 0xBD, (byte) 0xB5, (byte) 0xC1, (byte) 0xC7, 0x45,
(byte) 0xD9, (byte) 0xDC, (byte) 0x8B, 0x58, (byte) 0xB3, 0x5D, 0x5E, 0x72,
(byte) 0xE6, (byte) 0x8D, (byte) 0xDD, 0x0B, 0x21, (byte) 0xF3, (byte) 0x9A,
(byte) 0x8E, 0x1B, 0x79, 0x59, (byte) 0xE1, 0x3F, (byte) 0xAC, 0x24, (byte) 0xA0,
(byte) 0xC8, 0x56, (byte) 0xFD, (byte) 0x85, (byte) 0x8F, 0x6A, (byte) 0x80, 0x41,
(byte) 0xA8, 0x5D, 0x2C, (byte) 0xC2, 0x69, (byte) 0xA1, 0x0D, (byte) 0x82, 0x04,
0x5D, (byte) 0xCA, (byte) 0xB4, (byte) 0x9F, 0x3A, 0x2D, (byte) 0xBF, 0x24});
mTestLooper.dispatchAll();
verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
mAudioAttributesCaptor.capture(), eq(1));
AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
List<AudioDescriptor> expectedDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {95, 126, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {95, 126, 1}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {103, 126, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {87, 6, 3}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {61, 30, -64}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {21, 7, 80}),
new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {15, 127, 7}),
new AudioDescriptor(AudioDescriptor.STANDARD_SADB, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {-125, 95, 0, 0}),
new AudioDescriptor(AudioDescriptor.STANDARD_VSADB, AUDIO_ENCAPSULATION_TYPE_NONE,
new byte[] {-26, 17, 70, -48, 0, 112, 0})));
assertThat(descriptors).isEqualTo(expectedDescriptors);
}
@Test
public void earcGetsConnected_capsReportedTooLate() {
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS + 1);
mTestLooper.dispatchAll();
verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
mAudioAttributesCaptor.capture(), eq(1));
AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
assertThat(descriptors).hasSize(0);
Mockito.clearInvocations(mAudioManager);
mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(mEarcCapabilities);
mTestLooper.dispatchAll();
verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
}
@Test
public void earcGetsConnected_earcGetsDisconnectedBeforeCapsReported() {
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
mTestLooper.dispatchAll();
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_ARC_PENDING);
mTestLooper.dispatchAll();
verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), eq(1));
verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
mAudioAttributesCaptor.capture(), eq(0));
AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
assertThat(descriptors).hasSize(0);
Mockito.clearInvocations(mAudioManager);
mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(mEarcCapabilities);
mTestLooper.dispatchAll();
verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
}
@Test
public void earcGetsConnected_earcBecomesPendingBeforeCapsReported() {
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
mTestLooper.dispatchAll();
mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING);
mTestLooper.dispatchAll();
verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
Mockito.clearInvocations(mAudioManager);
mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(mEarcCapabilities);
mTestLooper.dispatchAll();
verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
}
}