Merge "HDMICEC: Dynamic soundbar mode feature"
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b32b3b6..eac990d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4173,6 +4173,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public String getPowerStateChangeOnActiveSourceLost();
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getRoutingControl();
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSadPresenceInQuery(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSoundbarMode();
method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioControl();
method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioModeMuting();
@@ -4194,6 +4195,7 @@
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setRoutingControl(@NonNull int);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadPresenceInQuery(@NonNull String, int);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSadsPresenceInQuery(@NonNull java.util.List<java.lang.String>, int);
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSoundbarMode(int);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioControl(@NonNull int);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioModeMuting(@NonNull int);
@@ -4221,6 +4223,7 @@
field public static final String CEC_SETTING_NAME_QUERY_SAD_TRUEHD = "query_sad_truehd";
field public static final String CEC_SETTING_NAME_QUERY_SAD_WMAPRO = "query_sad_wmapro";
field public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
+ field public static final String CEC_SETTING_NAME_SOUNDBAR_MODE = "soundbar_mode";
field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL = "system_audio_control";
field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING = "system_audio_mode_muting";
field public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep";
@@ -4302,6 +4305,8 @@
field public static final int ROUTING_CONTROL_DISABLED = 0; // 0x0
field public static final int ROUTING_CONTROL_ENABLED = 1; // 0x1
field public static final String SETTING_NAME_EARC_ENABLED = "earc_enabled";
+ field public static final int SOUNDBAR_MODE_DISABLED = 0; // 0x0
+ field public static final int SOUNDBAR_MODE_ENABLED = 1; // 0x1
field public static final int SYSTEM_AUDIO_CONTROL_DISABLED = 0; // 0x0
field public static final int SYSTEM_AUDIO_CONTROL_ENABLED = 1; // 0x1
field public static final int SYSTEM_AUDIO_MODE_MUTING_DISABLED = 0; // 0x0
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 96773f8..b0b7a41 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -398,6 +398,30 @@
@Retention(RetentionPolicy.SOURCE)
public @interface RoutingControl {}
+ // -- Whether the Soundbar mode feature is enabled or disabled.
+ /**
+ * Soundbar mode feature enabled.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+ */
+ public static final int SOUNDBAR_MODE_ENABLED = 1;
+ /**
+ * Soundbar mode feature disabled.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+ */
+ public static final int SOUNDBAR_MODE_DISABLED = 0;
+ /**
+ * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+ * @hide
+ */
+ @IntDef(prefix = { "SOUNDBAR_MODE" }, value = {
+ SOUNDBAR_MODE_ENABLED,
+ SOUNDBAR_MODE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SoundbarMode {}
+
// -- Scope of CEC power control messages sent by a playback device.
/**
* Send CEC power control messages to TV only:
@@ -820,6 +844,14 @@
*/
public static final String CEC_SETTING_NAME_ROUTING_CONTROL = "routing_control";
/**
+ * Name of a setting deciding whether the Soundbar mode feature is enabled.
+ * Before exposing this setting make sure the hardware supports it, otherwise, you may
+ * experience multiple issues.
+ *
+ * @see HdmiControlManager#setSoundbarMode(int)
+ */
+ public static final String CEC_SETTING_NAME_SOUNDBAR_MODE = "soundbar_mode";
+ /**
* Name of a setting deciding on the power control mode.
*
* @see HdmiControlManager#setPowerControlMode(String)
@@ -1070,6 +1102,8 @@
@StringDef(value = {
CEC_SETTING_NAME_HDMI_CEC_ENABLED,
CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ CEC_SETTING_NAME_ROUTING_CONTROL,
+ CEC_SETTING_NAME_SOUNDBAR_MODE,
CEC_SETTING_NAME_POWER_CONTROL_MODE,
CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -1222,7 +1256,16 @@
case HdmiDeviceInfo.DEVICE_PLAYBACK:
return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
- return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null;
+ try {
+ if ((mService.getCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE)
+ == SOUNDBAR_MODE_ENABLED && mHasPlaybackDevice)
+ || mHasAudioSystemDevice) {
+ return new HdmiAudioSystemClient(mService);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return null;
case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
return (mHasSwitchDevice || mIsSwitchDevice)
? new HdmiSwitchClient(mService) : null;
@@ -2290,6 +2333,54 @@
}
/**
+ * Set the status of Soundbar mode feature.
+ *
+ * <p>This allows to enable/disable Soundbar mode on the playback device.
+ * The setting's effect will be available on devices where the hardware supports this feature.
+ * If enabled, an audio system local device will be allocated and try to establish an ARC
+ * connection with the TV. If disabled, the ARC connection will be terminated and the audio
+ * system local device will be removed from the network.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+ */
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public void setSoundbarMode(@SoundbarMode int value) {
+ if (mService == null) {
+ Log.e(TAG, "setSoundbarMode: HdmiControlService is not available");
+ throw new RuntimeException("HdmiControlService is not available");
+ }
+ try {
+ mService.setCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the current status of Soundbar mode feature.
+ *
+ * <p>Reflects whether Soundbar mode is currently enabled on the playback device.
+ * If enabled, an audio system local device will be allocated and try to establish an ARC
+ * connection with the TV. If disabled, the ARC connection will be terminated and the audio
+ * system local device will be removed from the network.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SOUNDBAR_MODE
+ */
+ @SoundbarMode
+ @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+ public int getSoundbarMode() {
+ if (mService == null) {
+ Log.e(TAG, "getSoundbarMode: HdmiControlService is not available");
+ throw new RuntimeException("HdmiControlService is not available");
+ }
+ try {
+ return mService.getCecSettingIntValue(CEC_SETTING_NAME_SOUNDBAR_MODE);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set the status of Power Control.
*
* <p>Specifies to which devices Power Control messages should be sent:
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 86b0715..ccce9ba 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5354,6 +5354,12 @@
<bool name="config_cecRoutingControlDisabled_allowed">true</bool>
<bool name="config_cecRoutingControlDisabled_default">true</bool>
+ <bool name="config_cecSoundbarMode_userConfigurable">true</bool>
+ <bool name="config_cecSoundbarModeEnabled_allowed">true</bool>
+ <bool name="config_cecSoundbarModeEnabled_default">false</bool>
+ <bool name="config_cecSoundbarModeDisabled_allowed">true</bool>
+ <bool name="config_cecSoundbarModeDisabled_default">true</bool>
+
<bool name="config_cecPowerControlMode_userConfigurable">true</bool>
<bool name="config_cecPowerControlModeTv_allowed">true</bool>
<bool name="config_cecPowerControlModeTv_default">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1a86af0..ae033ca 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4556,6 +4556,12 @@
<java-symbol type="bool" name="config_cecRoutingControlDisabled_allowed" />
<java-symbol type="bool" name="config_cecRoutingControlDisabled_default" />
+ <java-symbol type="bool" name="config_cecSoundbarMode_userConfigurable" />
+ <java-symbol type="bool" name="config_cecSoundbarModeEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecSoundbarModeEnabled_default" />
+ <java-symbol type="bool" name="config_cecSoundbarModeDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecSoundbarModeDisabled_default" />
+
<java-symbol type="bool" name="config_cecPowerControlMode_userConfigurable" />
<java-symbol type="bool" name="config_cecPowerControlModeTv_allowed" />
<java-symbol type="bool" name="config_cecPowerControlModeTv_default" />
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3231240..ae929c4 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7180,6 +7180,24 @@
state == CONNECTION_STATE_CONNECTED ? "connected" : "disconnected")
.record();
mDeviceBroker.setWiredDeviceConnectionState(attributes, state, caller);
+ // The Dynamic Soundbar mode feature introduces dynamic presence for an HDMI Audio System
+ // Client. For example, the device can start with the Audio System Client unavailable.
+ // When the feature is activated the client becomes available, therefore Audio Service
+ // requests a new HDMI Audio System Client instance when the ARC status is changed.
+ if (attributes.getInternalType() == AudioSystem.DEVICE_IN_HDMI_ARC) {
+ updateHdmiAudioSystemClient();
+ }
+ }
+
+ /**
+ * Replace the current HDMI Audio System Client.
+ * See {@link #setWiredDeviceConnectionState(AudioDeviceAttributes, int, String)}.
+ */
+ private void updateHdmiAudioSystemClient() {
+ Slog.d(TAG, "Hdmi Audio System Client is updated");
+ synchronized (mHdmiClientLock) {
+ mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
+ }
}
/** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
index 049a339..4855be6 100644
--- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
@@ -27,7 +27,7 @@
private static final int STATE_ARC_TERMINATED = 2;
// the required maximum response time specified in CEC 9.2
- private static final int TIMEOUT_MS = 1000;
+ public static final int TIMEOUT_MS = 1000;
ArcTerminationActionFromAvr(HdmiCecLocalDevice source) {
super(source);
@@ -85,6 +85,8 @@
}
private void handleTerminateArcTimeout() {
+ // Disable ARC if TV didn't respond with <Report ARC Terminated> in time.
+ audioSystem().setArcStatus(false);
HdmiLogger.debug("handleTerminateArcTimeout");
finish();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 827aafa..6925507 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -318,6 +318,16 @@
R.bool.config_cecRoutingControlDisabled_allowed,
R.bool.config_cecRoutingControlDisabled_default);
+ Setting soundbarMode = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ R.bool.config_cecSoundbarMode_userConfigurable);
+ soundbarMode.registerValue(HdmiControlManager.SOUNDBAR_MODE_ENABLED,
+ R.bool.config_cecSoundbarModeEnabled_allowed,
+ R.bool.config_cecSoundbarModeEnabled_default);
+ soundbarMode.registerValue(HdmiControlManager.SOUNDBAR_MODE_DISABLED,
+ R.bool.config_cecSoundbarModeDisabled_allowed,
+ R.bool.config_cecSoundbarModeDisabled_default);
+
Setting powerControlMode = registerSetting(
HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
R.bool.config_cecPowerControlMode_userConfigurable);
@@ -714,6 +724,8 @@
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE:
+ return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
@@ -789,6 +801,8 @@
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL:
return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE:
+ return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 4542c39..b4d7fb9 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1359,7 +1359,8 @@
List<SendKeyAction> action = getActions(SendKeyAction.class);
int logicalAddress = findAudioReceiverAddress();
if (logicalAddress == Constants.ADDR_INVALID
- || logicalAddress == mDeviceInfo.getLogicalAddress()) {
+ || mService.getAllCecLocalDevices().stream().anyMatch(
+ device -> device.getDeviceInfo().getLogicalAddress() == logicalAddress)) {
// Don't send key event to invalid device or itself.
Slog.w(
TAG,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 728ed44..ccaa9255d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -226,6 +226,8 @@
@Override
@ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+ terminateAudioReturnChannel();
+
super.disableDevice(initiatedByCec, callback);
assertRunOnServiceThread();
mService.unregisterTvInputCallback(mTvInputCallback);
@@ -884,7 +886,7 @@
private void notifyArcStatusToAudioService(boolean enabled) {
// Note that we don't set any name to ARC.
mService.getAudioManager()
- .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
+ .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI_ARC, enabled ? 1 : 0, "", "");
}
void reportAudioStatus(int source) {
@@ -1088,6 +1090,16 @@
}
}
+ private void terminateAudioReturnChannel() {
+ // remove pending initiation actions
+ removeAction(ArcInitiationActionFromAvr.class);
+ if (!isArcEnabled()
+ || !mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
+ return;
+ }
+ addAndStartAction(new ArcTerminationActionFromAvr(this));
+ }
+
/** Reports if System Audio Mode is supported by the connected TV */
interface TvSystemAudioModeSupportedCallback {
@@ -1312,6 +1324,9 @@
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
+ if (mService.isDeviceDiscoveryHandledByPlayback()) {
+ return;
+ }
if (hasAction(DeviceDiscoveryAction.class)) {
Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
removeAction(DeviceDiscoveryAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3b06786..43cd71a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -19,6 +19,8 @@
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.HDMI_CEC_CONTROL_ENABLED;
+import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
+import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED;
import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
import static com.android.server.hdmi.Constants.DISABLED;
@@ -188,6 +190,7 @@
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;
// The reason code representing the intent action that drives the standby
// procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
@@ -693,6 +696,14 @@
}
}
}, mServiceThreadExecutor);
+ mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ setSoundbarMode(mHdmiCecConfig.getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE));
+ }
+ }, mServiceThreadExecutor);
mHdmiCecConfig.registerChangeListener(
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
new HdmiCecConfig.SettingChangeListener() {
@@ -770,6 +781,11 @@
}
@VisibleForTesting
+ void setAudioManager(AudioManager audioManager) {
+ mAudioManager = audioManager;
+ }
+
+ @VisibleForTesting
void setCecController(HdmiCecController cecController) {
mCecController = cecController;
}
@@ -847,6 +863,47 @@
}
/**
+ * Triggers the address allocation that states the presence of a local device audio system in
+ * the network.
+ */
+ @VisibleForTesting
+ public void setSoundbarMode(final int settingValue) {
+ HdmiCecLocalDevicePlayback playback = playback();
+ HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+ if (playback == null) {
+ Slog.w(TAG, "Device type not compatible to change soundbar mode.");
+ return;
+ }
+ if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+ Slog.w(TAG, "Device type doesn't support ARC.");
+ return;
+ }
+ if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) {
+ if (audioSystem.isArcEnabled()) {
+ audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem));
+ }
+ if (isSystemAudioActivated()) {
+ audioSystem.terminateSystemAudioMode();
+ }
+ }
+ mAddressAllocated = false;
+ initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+ }
+
+ /**
+ * Checks if the Device Discovery is handled by the local device playback.
+ * See {@link HdmiCecLocalDeviceAudioSystem#launchDeviceDiscovery}.
+ */
+ public boolean isDeviceDiscoveryHandledByPlayback() {
+ HdmiCecLocalDevicePlayback playback = playback();
+ if (playback != null && (playback.hasAction(DeviceDiscoveryAction.class)
+ || playback.hasAction(HotplugDetectionAction.class))) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Called when the initialization of local devices is complete.
*/
private void onInitializeCecComplete(int initiatedBy) {
@@ -993,12 +1050,30 @@
initializeCecLocalDevices(initiatedBy);
}
+ /**
+ * If the Soundbar mode is turned on, adds the local device type audio system in the list of
+ * local devices types. This method is called when the local devices are initialized such that
+ * the list of local devices is in sync with the Soundbar mode setting.
+ * @return the list of integer device types
+ */
+ @ServiceThreadOnly
+ private List<Integer> getCecLocalDeviceTypes() {
+ ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
+ if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+ == SOUNDBAR_MODE_ENABLED
+ && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+ && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+ allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ }
+ return allLocalDeviceTypes;
+ }
+
@ServiceThreadOnly
private void initializeCecLocalDevices(final int initiatedBy) {
assertRunOnServiceThread();
// A container for [Device type, Local device info].
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
- for (int type : mCecLocalDevices) {
+ for (int type : getCecLocalDeviceTypes()) {
HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1051,9 +1126,10 @@
// Address allocation completed for all devices. Notify each device.
if (allocatingDevices.size() == ++finished[0]) {
- if (initiatedBy != INITIATED_BY_HOTPLUG) {
- // In case of the hotplug we don't call
- // onInitializeCecComplete()
+ if (initiatedBy != INITIATED_BY_HOTPLUG
+ && initiatedBy != INITIATED_BY_SOUNDBAR_MODE) {
+ // In case of the hotplug or soundbar mode setting toggle
+ // we don't call onInitializeCecComplete()
// since we reallocate the logical address only.
onInitializeCecComplete(initiatedBy);
}
@@ -1413,7 +1489,7 @@
if (connected && !isTvDevice()
&& getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
- for (int type : mCecLocalDevices) {
+ for (int type : getCecLocalDeviceTypes()) {
HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
@@ -3397,7 +3473,8 @@
}
@ServiceThreadOnly
- private void clearCecLocalDevices() {
+ @VisibleForTesting
+ protected void clearCecLocalDevices() {
assertRunOnServiceThread();
if (mCecController == null) {
return;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 5b11466..09cd47a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -143,8 +143,12 @@
Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+ mTestLooper.dispatchAll();
- assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+ mTestLooper.moveTimeForward(ArcTerminationActionFromAvr.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index 5722ff3..167e0f8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -80,6 +80,17 @@
R.bool.config_cecRoutingControlDisabled_default);
doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSoundbarMode_userConfigurable);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSoundbarModeEnabled_allowed);
+ doReturn(false).when(resources).getBoolean(
+ R.bool.config_cecSoundbarModeEnabled_default);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSoundbarModeDisabled_allowed);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSoundbarModeDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(
R.bool.config_cecPowerControlMode_userConfigurable);
doReturn(true).when(resources).getBoolean(
R.bool.config_cecPowerControlModeTv_allowed);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index 392d7f1..870b681 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -76,6 +76,7 @@
.containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -116,6 +117,7 @@
.containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
@@ -157,6 +159,7 @@
.containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL,
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 08d0e90..7c6c990 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -354,7 +354,7 @@
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
- assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
assertThat(mMusicMute).isTrue();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index cb2d255..3ed8983 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -31,6 +31,7 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -45,6 +46,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
@@ -85,9 +88,13 @@
private boolean mActiveMediaSessionsPaused;
private FakePowerManagerInternalWrapper mPowerManagerInternal =
new FakePowerManagerInternalWrapper();
+ @Mock
+ protected AudioManager mAudioManager;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
@@ -103,6 +110,11 @@
}
@Override
+ AudioManager getAudioManager() {
+ return mAudioManager;
+ }
+
+ @Override
void pauseActiveMediaSessions() {
mActiveMediaSessionsPaused = true;
}
@@ -1424,6 +1436,32 @@
}
@Test
+ public void sendVolumeKeyEvent_toLocalDevice_discardMessage() {
+ HdmiCecLocalDeviceAudioSystem audioSystem =
+ new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+ audioSystem.init();
+ mLocalDevices.add(audioSystem);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
+ HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ mHdmiControlService.setSystemAudioActivated(true);
+
+ mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+ mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+ HdmiCecMessage keyPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+ HdmiCecMessage keyReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+ mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(keyPressed);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(keyReleased);
+ }
+
+ @Test
public void handleSetStreamPath_broadcastsActiveSource() {
HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
mPlaybackPhysicalAddress);
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 81d569b..ef2b212 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -44,6 +44,7 @@
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
+import android.media.AudioManager;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
@@ -58,7 +59,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mock;
import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
@@ -86,8 +89,12 @@
private HdmiPortInfo[] mHdmiPortInfo;
private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
+ @Mock protected AudioManager mAudioManager;
+
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
@@ -132,6 +139,7 @@
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlServiceSpy.setAudioManager(mAudioManager);
mTestLooper.dispatchAll();
}
@@ -1026,6 +1034,48 @@
.containsExactly(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM);
}
+ @Test
+ public void setSoundbarMode_enabled_addAudioSystemLocalDevice() {
+ // Initialize the local devices excluding the audio system.
+ mHdmiControlServiceSpy.clearCecLocalDevices();
+ mLocalDevices.remove(mAudioSystemDeviceSpy);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+
+ // Enable the setting and check if the audio system local device is found in the network.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+ }
+
+ @Test
+ public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() {
+ // Initialize the local devices excluding the audio system.
+ mHdmiControlServiceSpy.clearCecLocalDevices();
+ mLocalDevices.remove(mAudioSystemDeviceSpy);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+
+ // Enable the setting and check if the audio system local device is found in the network.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+
+ // Disable the setting and check if the audio system local device is removed from the
+ // network.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+ }
+
protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
private boolean mCanGoToStandby;