From fd133451c234358bfa5fb8f230a05a626a014f98 Mon Sep 17 00:00:00 2001 From: Nathalie Le Clair Date: Thu, 4 Aug 2022 14:41:15 +0200 Subject: [PATCH] Create and remove HdmiEarcLocalDeviceTx Create when HdmiControlService is started, or eARC gets enabled. Remove when eARC gets disabled. Test: atest Bug: 240388108 Change-Id: I1c563de3022ab3b614b6b71fc36250daced9b06b --- .../server/hdmi/HdmiControlService.java | 165 ++++++++++++++++-- .../server/hdmi/HdmiEarcLocalDevice.java | 32 ++++ .../server/hdmi/HdmiEarcLocalDeviceTx.java | 32 ++++ .../server/hdmi/HdmiCecNetworkTest.java | 32 ++++ .../server/hdmi/HdmiControlServiceTest.java | 49 ++++++ 5 files changed, 300 insertions(+), 10 deletions(-) create mode 100644 services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java create mode 100644 services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 2f15e579b2149..820700b0af236 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -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; @@ -184,13 +185,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 +386,15 @@ public class HdmiControlService extends SystemService { @HdmiControlManager.HdmiCecControl private int mHdmiControlEnabled; + // Set to true while the eARC feature is supported by the hardware 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; + // 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 +428,9 @@ public class HdmiControlService extends SystemService { private HdmiCecPowerStatusController mPowerStatusController; + @Nullable + private HdmiEarcLocalDevice mEarcLocalDevice; + @ServiceThreadOnly private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault()); @@ -631,9 +645,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); @@ -655,11 +673,16 @@ public class HdmiControlService extends SystemService { Slog.i(TAG, "Device does not support MHL-control."); } mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController); - if (mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) { + if (isCecControlEnabled()) { initializeCec(INITIATED_BY_BOOT_UP); } else { mCecController.enableCec(false); } + synchronized (mLock) { + if (mEarcSupported && mEarcEnabled) { + initializeEarc(INITIATED_BY_BOOT_UP); + } + } mMhlDevices = Collections.emptyList(); mHdmiCecNetwork.initPortInfo(); @@ -743,8 +766,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 +1114,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 localDevices = new ArrayList<>(); @@ -2568,7 +2600,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 +2611,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 +2639,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 +3355,12 @@ public class HdmiControlService extends SystemService { } } + private boolean isEarcSupportedAndEnabled() { + synchronized (mLock) { + return mEarcSupported && mEarcEnabled; + } + } + @ServiceThreadOnly int getPowerStatus() { assertRunOnServiceThread(); @@ -3384,7 +3432,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 +3454,22 @@ public class HdmiControlService extends SystemService { } else { Slog.i(TAG, "Device does not support HDMI-CEC."); } + if (isEarcSupportedAndEnabled()) { + 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); + } // TODO: Initialize MHL local devices. } @@ -4296,4 +4360,85 @@ 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); + initializeEarcLocalDevice(initiatedBy); + } + + @ServiceThreadOnly + @VisibleForTesting + protected void initializeEarcLocalDevice(final int initiatedBy) { + // TODO remove initiatedBy argument if it stays unused + assertRunOnServiceThread(); + if (isTvDevice() && mEarcLocalDevice == null) { + mEarcLocalDevice = new HdmiEarcLocalDeviceTx(); + } + // 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 (!mEarcSupported) { + 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() { + clearEarcLocalDevice(); + } + + @ServiceThreadOnly + @VisibleForTesting + protected void clearEarcLocalDevice() { + assertRunOnServiceThread(); + mEarcLocalDevice = null; + } + + @ServiceThreadOnly + @VisibleForTesting + protected void addEarcLocalDevice(HdmiEarcLocalDevice localDevice) { + assertRunOnServiceThread(); + mEarcLocalDevice = localDevice; + } + + @ServiceThreadOnly + @VisibleForTesting + protected HdmiEarcLocalDevice getEarcLocalDevice() { + assertRunOnServiceThread(); + return mEarcLocalDevice; + } + } diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java new file mode 100644 index 0000000000000..1e9d338fc2d68 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java @@ -0,0 +1,32 @@ +/* + * 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.util.IndentingPrintWriter; + +/** + * Class that models a local eARC device hosted in this system. + * The class contains methods that are common between eARC TX and eARC RX devices. + */ +public class HdmiEarcLocalDevice { + private static final String TAG = "HdmiEarcLocalDevice"; + + /** Dump internal status of HdmiEarcLocalDevice object */ + protected void dump(final IndentingPrintWriter pw) { + // Should be overridden in the more specific classes + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java new file mode 100644 index 0000000000000..c1c1e7794676d --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java @@ -0,0 +1,32 @@ +/* + * 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.util.IndentingPrintWriter; + +/** + * 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"; + + /** Dump internal status of HdmiEarcLocalDeviceTx object */ + protected void dump(final IndentingPrintWriter pw) { + pw.println("TX"); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java index b8a1ba3633735..a82a79f06f456 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java @@ -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); + } } 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 49a0a9a52c47d..f07ee6c2be4e7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -29,6 +29,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -1082,6 +1083,54 @@ public class HdmiControlServiceTest { assertThat(mHdmiControlServiceSpy.audioSystem()).isNull(); } + @Test + public void disableEarc_clearEarcLocalDevice() { + mHdmiControlServiceSpy.setEarcSupported(true); + mHdmiControlServiceSpy.clearEarcLocalDevice(); + mHdmiControlServiceSpy.addEarcLocalDevice(new HdmiEarcLocalDeviceTx()); + 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()); + 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()); + } + protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { private boolean mCanGoToStandby;