From e656e5cbe10be13fe647632d15459ff9300ab569 Mon Sep 17 00:00:00 2001 From: Michal Olech Date: Thu, 21 Jan 2021 15:08:13 +0100 Subject: [PATCH 1/3] [CEC Configuration] Add 'tv_wake_on_one_touch_play' option This ports the Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED to HdmiCecConfig. Bug: 166433775 Test: atest HdmiCecLocalDeviceTvTest Change-Id: I63c7cf35b9769da193e950f362f51a0f79898a5f --- .../hardware/hdmi/HdmiControlManager.java | 77 ++++++++++++++++++- .../android/server/hdmi/HdmiCecConfig.java | 4 + .../server/hdmi/HdmiCecLocalDeviceTv.java | 17 +--- .../server/hdmi/HdmiControlService.java | 17 ++-- .../com/android/server/hdmi/cec_config.xml | 9 +++ .../server/hdmi/FakeHdmiCecConfig.java | 9 +++ .../server/hdmi/HdmiCecLocalDeviceTvTest.java | 64 +++++++++++++++ 7 files changed, 175 insertions(+), 22 deletions(-) diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 78584990e75e2..0a9cc1d75db93 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -480,6 +480,28 @@ public final class HdmiControlManager { @Retention(RetentionPolicy.SOURCE) public @interface VolumeControl {} + // -- Whether TV Wake on One Touch Play is enabled or disabled. + /** + * TV Wake on One Touch Play enabled. + * + * @hide + */ + public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED = 1; + /** + * TV Wake on One Touch Play disabled. + * + * @hide + */ + public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED = 0; + /** + * @hide + */ + @IntDef(prefix = { "TV_WAKE_ON_ONE_TOUCH_PLAY_" }, value = { + TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED, + TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TvWakeOnOneTouchPlay {} // -- Settings available in the CEC Configuration. /** @@ -519,7 +541,6 @@ public final class HdmiControlManager { @SystemApi public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING = "system_audio_mode_muting"; - /** * Controls whether volume control commands via HDMI CEC are enabled. * @@ -555,7 +576,14 @@ public final class HdmiControlManager { */ public static final String CEC_SETTING_NAME_VOLUME_CONTROL_MODE = "volume_control_enabled"; - + /** + * Name of a setting deciding whether the TV will automatically turn on upon reception + * of the CEC command <Text View On> or <Image View On>. + * + * @hide + */ + public static final String CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY = + "tv_wake_on_one_touch_play"; /** * @hide */ @@ -566,6 +594,7 @@ public final class HdmiControlManager { CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, CEC_SETTING_NAME_VOLUME_CONTROL_MODE, + CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, }) public @interface CecSettingName {} @@ -1869,4 +1898,48 @@ public final class HdmiControlManager { throw e.rethrowFromSystemServer(); } } + + /** + * Set the current status of TV Wake on One Touch Play. + * + *

Sets whether the TV should wake up upon reception of <Text View On> + * or <Image View On>. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public void setTvWakeOnOneTouchPlay(@NonNull @TvWakeOnOneTouchPlay int value) { + if (mService == null) { + Log.e(TAG, "HdmiControlService is not available"); + throw new RuntimeException("HdmiControlService is not available"); + } + try { + mService.setCecSettingIntValue(CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, value); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the current status of TV Wake on One Touch Play. + * + *

Reflects whether the TV should wake up upon reception of <Text View On> + * or <Image View On>. + * + * @hide + */ + @NonNull + @TvWakeOnOneTouchPlay + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public int getTvWakeOnOneTouchPlay() { + if (mService == null) { + Log.e(TAG, "HdmiControlService is not available"); + throw new RuntimeException("HdmiControlService is not available"); + } + try { + return mService.getCecSettingIntValue(CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java index e854481985ce7..c6604544e6c70 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java @@ -297,6 +297,8 @@ public class HdmiCecConfig { return STORAGE_SHARED_PREFS; case HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING: return STORAGE_SHARED_PREFS; + case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY: + return STORAGE_GLOBAL_SETTINGS; default: throw new RuntimeException("Invalid CEC setting '" + setting.getName() + "' storage."); @@ -317,6 +319,8 @@ public class HdmiCecConfig { return setting.getName(); case HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING: return setting.getName(); + case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY: + return Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED; default: throw new RuntimeException("Invalid CEC setting '" + setting.getName() + "' storage key."); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index e568aa228b40b..b60390bde38bb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -94,9 +94,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // If true, TV going to standby mode puts other devices also to standby. private boolean mAutoDeviceOff; - // If true, TV wakes itself up when receiving . - private boolean mAutoWakeup; - private final HdmiCecStandbyModeHandler mStandbyHandler; // If true, do not do routing control/send active source for internal source. @@ -163,7 +160,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mPrevPortId = Constants.INVALID_PORT_ID; mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true); - mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true); mSystemAudioControlFeatureEnabled = mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true); mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); @@ -641,7 +637,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // implemented in such a way that Android system is not really put to standby mode // but only the display is set to blank. Then the command leads to the effect of // turning on the display by the invocation of PowerManager.wakeUp(). - if (mService.isPowerStandbyOrTransient() && mAutoWakeup) { + if (mService.isPowerStandbyOrTransient() && getAutoWakeup()) { mService.wakeUp(); } return true; @@ -1215,16 +1211,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mAutoDeviceOff = enabled; } - @ServiceThreadOnly - void setAutoWakeup(boolean enabled) { - assertRunOnServiceThread(); - mAutoWakeup = enabled; - } - @ServiceThreadOnly boolean getAutoWakeup() { assertRunOnServiceThread(); - return mAutoWakeup; + return mService.getHdmiCecConfig().getIntValue( + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY) + == HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED; } @Override @@ -1552,7 +1544,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { pw.println("mSystemAudioMute: " + mSystemAudioMute); pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled); pw.println("mAutoDeviceOff: " + mAutoDeviceOff); - pw.println("mAutoWakeup: " + mAutoWakeup); pw.println("mSkipRoutingControl: " + mSkipRoutingControl); pw.println("mPrevPortId: " + mPrevPortId); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 9b194ae84a378..8febd4ff6c27c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -526,6 +526,16 @@ public class HdmiControlService extends SystemService { initializeCec(INITIATED_BY_ENABLE_CEC); } }); + mHdmiCecConfig.registerChangeListener( + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + new HdmiCecConfig.SettingChangeListener() { + @Override + public void onChange(String setting) { + if (isTvDeviceEnabled()) { + setCecOption(OptionKey.WAKEUP, tv().getAutoWakeup()); + } + } + }); } private void bootCompleted() { @@ -651,7 +661,6 @@ public class HdmiControlService extends SystemService { ContentResolver resolver = getContext().getContentResolver(); String[] settings = new String[] { Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, - Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, Global.MHL_INPUT_SWITCHING_ENABLED, @@ -680,12 +689,6 @@ public class HdmiControlService extends SystemService { setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)); break; - case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED: - if (isTvDeviceEnabled()) { - tv().setAutoWakeup(enabled); - } - setCecOption(OptionKey.WAKEUP, enabled); - break; case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED: for (int type : mLocalDevices) { HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); diff --git a/services/core/java/com/android/server/hdmi/cec_config.xml b/services/core/java/com/android/server/hdmi/cec_config.xml index e6e3c2f5eda18..c690b6b6a11ae 100644 --- a/services/core/java/com/android/server/hdmi/cec_config.xml +++ b/services/core/java/com/android/server/hdmi/cec_config.xml @@ -55,4 +55,13 @@ + + + + + + + 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 8e37f94828fb4..d6f32bce12352 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java @@ -104,6 +104,15 @@ final class FakeHdmiCecConfig extends HdmiCecConfig { + " " + " " + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + ""; FakeHdmiCecConfig(@NonNull Context context) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 81f55d80762b6..352f20bf26bf9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -58,6 +58,8 @@ public class HdmiCecLocalDeviceTvTest { private TestLooper mTestLooper = new TestLooper(); private ArrayList mLocalDevices = new ArrayList<>(); private int mTvPhysicalAddress; + private int mTvLogicalAddress; + private boolean mWokenUp; @Mock private IPowerManager mIPowerManagerMock; @@ -77,6 +79,11 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + void wakeUp() { + mWokenUp = true; + } + @Override boolean isControlEnabled() { return true; @@ -122,6 +129,7 @@ public class HdmiCecLocalDeviceTvTest { mTvPhysicalAddress = 0x0000; mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress); mTestLooper.dispatchAll(); + mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(); mNativeWrapper.clearResultMessages(); } @@ -203,4 +211,60 @@ public class HdmiCecLocalDeviceTvTest { HdmiControlManager.POWER_CONTROL_MODE_TV); assertThat(mHdmiControlService.shouldHandleTvPowerKey()).isFalse(); } + + @Test + public void tvWakeOnOneTouchPlay_TextViewOn_Enabled() { + mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED); + mTestLooper.dispatchAll(); + mWokenUp = false; + HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1, + mTvLogicalAddress); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mWokenUp).isTrue(); + } + + @Test + public void tvWakeOnOneTouchPlay_ImageViewOn_Enabled() { + mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED); + mTestLooper.dispatchAll(); + mWokenUp = false; + HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, + Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mWokenUp).isTrue(); + } + + @Test + public void tvWakeOnOneTouchPlay_TextViewOn_Disabled() { + mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED); + mTestLooper.dispatchAll(); + mWokenUp = false; + HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1, + mTvLogicalAddress); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mWokenUp).isFalse(); + } + + @Test + public void tvWakeOnOneTouchPlay_ImageViewOn_Disabled() { + mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED); + mTestLooper.dispatchAll(); + mWokenUp = false; + HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, + Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mWokenUp).isFalse(); + } } From b695454d8aa447c6feeeccbeb84c69c0a2f3b6c7 Mon Sep 17 00:00:00 2001 From: Michal Olech Date: Thu, 7 Jan 2021 12:59:10 +0100 Subject: [PATCH 2/3] [2.0] Disable sending in attempts to change the active source to a 2.0 device Bug: 169121092 Test: atest DeviceSelectActionTest Change-Id: I41a590819fa8ac4efe307932bec59881f975c715 --- .../server/hdmi/DeviceSelectAction.java | 121 +++--- .../server/hdmi/DeviceSelectActionTest.java | 362 ++++++++++++++++++ 2 files changed, 436 insertions(+), 47 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java index c684a56ec5d48..cb47bb25f1525 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java @@ -23,6 +23,8 @@ import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.RemoteException; import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; /** @@ -47,7 +49,8 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { // State in which we wait for to come in response to the command // we have sent. - private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1; + @VisibleForTesting + static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1; // State in which we wait for the device power status to switch to 'Standby'. // We wait till the status becomes 'Standby' before we send @@ -56,11 +59,13 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { // State in which we wait for the device power status to switch to 'on'. We wait // maximum 100 seconds (20 * 5) before we give up and just send . - private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; + @VisibleForTesting + static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; private final HdmiDeviceInfo mTarget; private final IHdmiControlCallback mCallback; private final HdmiCecMessage mGivePowerStatus; + private final boolean mIsCec20; private int mPowerStatusCounter = 0; @@ -71,13 +76,22 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { * @param target target logical device that will be a new active source * @param callback callback object */ - public DeviceSelectAction(HdmiCecLocalDeviceTv source, - HdmiDeviceInfo target, IHdmiControlCallback callback) { + DeviceSelectAction(HdmiCecLocalDeviceTv source, HdmiDeviceInfo target, + IHdmiControlCallback callback) { + this(source, target, callback, + source.getDeviceInfo().getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0 + && target.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0); + } + + @VisibleForTesting + DeviceSelectAction(HdmiCecLocalDeviceTv source, HdmiDeviceInfo target, + IHdmiControlCallback callback, boolean isCec20) { super(source); mCallback = callback; mTarget = target; mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( getSourceAddress(), getTargetAddress()); + mIsCec20 = isCec20; } int getTargetAddress() { @@ -86,8 +100,18 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { @Override public boolean start() { - // Seq #9 - queryDevicePowerStatus(); + if (mIsCec20) { + sendSetStreamPath(); + } + if (!mIsCec20 || mTarget.getDevicePowerStatus() + == HdmiControlManager.POWER_STATUS_UNKNOWN) { + queryDevicePowerStatus(); + } else if (mTarget.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON) { + invokeCallbackAndFinish(HdmiControlManager.RESULT_SUCCESS); + return true; + } + mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; + addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } @@ -96,14 +120,10 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { @Override public void onSendCompleted(int error) { if (error != SendMessageResult.SUCCESS) { - invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); - finish(); - return; + invokeCallbackAndFinish(HdmiControlManager.RESULT_COMMUNICATION_FAILED); } } }); - mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; - addTimer(mState, HdmiConfig.TIMEOUT_MS); } @Override @@ -113,7 +133,6 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { } int opcode = cmd.getOpcode(); byte[] params = cmd.getParams(); - switch (mState) { case STATE_WAIT_FOR_REPORT_POWER_STATUS: if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { @@ -129,21 +148,23 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { private boolean handleReportPowerStatus(int powerStatus) { switch (powerStatus) { case HdmiControlManager.POWER_STATUS_ON: - sendSetStreamPath(); + selectDevice(); return true; case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY: if (mPowerStatusCounter < 4) { mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY; addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS); } else { - sendSetStreamPath(); + selectDevice(); } return true; case HdmiControlManager.POWER_STATUS_STANDBY: if (mPowerStatusCounter == 0) { turnOnDevice(); + mState = STATE_WAIT_FOR_DEVICE_POWER_ON; + addTimer(mState, TIMEOUT_POWER_ON_MS); } else { - sendSetStreamPath(); + selectDevice(); } return true; case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON: @@ -151,33 +172,13 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { mState = STATE_WAIT_FOR_DEVICE_POWER_ON; addTimer(mState, TIMEOUT_POWER_ON_MS); } else { - sendSetStreamPath(); + selectDevice(); } return true; } return false; } - private void turnOnDevice() { - sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), - HdmiCecKeycode.CEC_KEYCODE_POWER); - sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), - HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION); - mState = STATE_WAIT_FOR_DEVICE_POWER_ON; - addTimer(mState, TIMEOUT_POWER_ON_MS); - } - - private void sendSetStreamPath() { - // Turn the active source invalidated, which remains so till comes from - // the selected device. - tv().getActiveSource().invalidate(); - tv().setActivePath(mTarget.getPhysicalAddress()); - sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( - getSourceAddress(), mTarget.getPhysicalAddress())); - invokeCallback(HdmiControlManager.RESULT_SUCCESS); - finish(); - } - @Override public void handleTimerEvent(int timeoutState) { if (mState != timeoutState) { @@ -187,28 +188,54 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { switch (mState) { case STATE_WAIT_FOR_REPORT_POWER_STATUS: if (tv().isPowerStandbyOrTransient()) { - invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE); - finish(); + invokeCallbackAndFinish(HdmiControlManager.RESULT_INCORRECT_MODE); return; } - sendSetStreamPath(); + selectDevice(); break; case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY: case STATE_WAIT_FOR_DEVICE_POWER_ON: mPowerStatusCounter++; queryDevicePowerStatus(); + mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; + addTimer(mState, HdmiConfig.TIMEOUT_MS); break; } } - private void invokeCallback(int result) { - if (mCallback == null) { - return; - } - try { - mCallback.onComplete(result); - } catch (RemoteException e) { - Slog.e(TAG, "Callback failed:" + e); + private void turnOnDevice() { + if (!mIsCec20) { + sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), + HdmiCecKeycode.CEC_KEYCODE_POWER); + sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), + HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION); } } + + private void selectDevice() { + if (!mIsCec20) { + sendSetStreamPath(); + } + invokeCallbackAndFinish(HdmiControlManager.RESULT_SUCCESS); + } + + private void sendSetStreamPath() { + // Turn the active source invalidated, which remains so till comes from + // the selected device. + tv().getActiveSource().invalidate(); + tv().setActivePath(mTarget.getPhysicalAddress()); + sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( + getSourceAddress(), mTarget.getPhysicalAddress())); + } + + private void invokeCallbackAndFinish(int result) { + if (mCallback != null) { + try { + mCallback.onComplete(result); + } catch (RemoteException e) { + Slog.e(TAG, "Callback failed:" + e); + } + } + finish(); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java new file mode 100644 index 0000000000000..f49cbca12fd89 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2014 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.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON; +import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY; +import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; + +import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; +import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; +import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.DeviceSelectAction.STATE_WAIT_FOR_DEVICE_POWER_ON; +import static com.android.server.hdmi.DeviceSelectAction.STATE_WAIT_FOR_REPORT_POWER_STATUS; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.os.Handler; +import android.os.IPowerManager; +import android.os.IThermalService; +import android.os.Looper; +import android.os.PowerManager; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer; + +import org.junit.Before; +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; + +@SmallTest +@RunWith(JUnit4.class) +public class DeviceSelectActionTest { + + private static final int PORT_1 = 1; + private static final int PORT_2 = 1; + private static final int PHYSICAL_ADDRESS_PLAYBACK_1 = 0x1000; + private static final int PHYSICAL_ADDRESS_PLAYBACK_2 = 0x2000; + + private static final byte[] POWER_ON = new byte[] { POWER_STATUS_ON }; + private static final byte[] POWER_STANDBY = new byte[] { POWER_STATUS_STANDBY }; + private static final byte[] POWER_TRANSIENT_TO_ON = new byte[] { POWER_STATUS_TRANSIENT_TO_ON }; + private static final HdmiCecMessage REPORT_POWER_STATUS_ON = new HdmiCecMessage( + ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); + private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = new HdmiCecMessage( + ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY); + private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = new HdmiCecMessage( + ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON); + private static final HdmiCecMessage SET_STREAM_PATH = HdmiCecMessageBuilder.buildSetStreamPath( + ADDR_TV, PHYSICAL_ADDRESS_PLAYBACK_1); + private static final HdmiDeviceInfo INFO_PLAYBACK_1 = new HdmiDeviceInfo( + ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK, + 0x1234, "Playback 1", + HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private static final HdmiDeviceInfo INFO_PLAYBACK_2 = new HdmiDeviceInfo( + ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, HdmiDeviceInfo.DEVICE_PLAYBACK, + 0x1234, "Playback 2", + HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + + private HdmiControlService mHdmiControlService; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private ArrayList mLocalDevices = new ArrayList<>(); + + @Mock + private IPowerManager mIPowerManagerMock; + @Mock + private IThermalService mIThermalServiceMock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Context context = InstrumentationRegistry.getTargetContext(); + mMyLooper = mTestLooper.getLooper(); + PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, + mIThermalServiceMock, new Handler(mMyLooper)); + + HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context); + + mHdmiControlService = + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + boolean isControlEnabled() { + return true; + } + + @Override + void wakeUp() { + } + + @Override + protected void writeStringSystemProperty(String key, String value) { + // do nothing + } + + @Override + boolean isPowerStandbyOrTransient() { + return false; + } + + @Override + protected PowerManager getPowerManager() { + return powerManager; + } + + @Override + protected HdmiCecConfig getHdmiCecConfig() { + return hdmiCecConfig; + } + }; + + mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService); + mHdmiCecLocalDeviceTv.init(); + mHdmiControlService.setIoLooper(mMyLooper); + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + mLocalDevices.add(mHdmiCecLocalDeviceTv); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_1, + true, false, false); + hdmiPortInfos[1] = + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_2, + true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mHdmiControlService.initService(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mNativeWrapper.setPhysicalAddress(0x0000); + mTestLooper.dispatchAll(); + mNativeWrapper.clearResultMessages(); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_1); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_2); + } + + private static class TestActionTimer implements ActionTimer { + private int mState; + + @Override + public void sendTimerMessage(int state, long delayMillis) { + mState = state; + } + + @Override + public void clearTimerMessage() { + } + + private int getState() { + return mState; + } + } + + private static class TestCallback extends IHdmiControlCallback.Stub { + private final ArrayList mCallbackResult = new ArrayList(); + + @Override + public void onComplete(int result) { + mCallbackResult.add(result); + } + + private int getResult() { + assertThat(mCallbackResult.size()).isEqualTo(1); + return mCallbackResult.get(0); + } + } + + private DeviceSelectAction createDeviceSelectAction(TestActionTimer actionTimer, + TestCallback callback, + boolean isCec20) { + HdmiDeviceInfo hdmiDeviceInfo = + mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(ADDR_PLAYBACK_1); + DeviceSelectAction action = new DeviceSelectAction(mHdmiCecLocalDeviceTv, + hdmiDeviceInfo, callback, isCec20); + action.setActionTimer(actionTimer); + return action; + } + + @Test + public void testDeviceSelect_DeviceInPowerOnStatus_Cec14b() { + // TV was watching playback2 device connected at port 2, and wants to select + // playback1. + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/false); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + action.start(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_ON); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() { + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/false); + action.start(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_STANDBY); + mTestLooper.dispatchAll(); + HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( + ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); + assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.processCommand(REPORT_POWER_STATUS_ON); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void testDeviceSelect_DeviceInStandbyStatusWithSomeTimeouts_Cec14b() { + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/false); + action.start(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_STANDBY); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_ON); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void testDeviceSelect_DeviceInStandbyAfterTimeoutForReportPowerStatus_Cec14b() { + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/false); + action.start(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_STANDBY); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.handleTimerEvent(STATE_WAIT_FOR_REPORT_POWER_STATUS); + // Give up getting power status, and just send + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void testDeviceSelect_DeviceInPowerOnStatus_Cec20() { + mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/true); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + action.start(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void testDeviceSelect_DeviceInPowerUnknownStatus_Cec20() { + mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_UNKNOWN); + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/true); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + action.start(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_ON); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void testDeviceSelect_DeviceInStandbyStatus_Cec20() { + mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_STANDBY); + mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, + "testDeviceSelect"); + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + DeviceSelectAction action = createDeviceSelectAction(actionTimer, callback, + /*isCec20=*/true); + action.start(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS); + action.processCommand(REPORT_POWER_STATUS_STANDBY); + mTestLooper.dispatchAll(); + HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( + ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(userControlPressed); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON); + action.processCommand(REPORT_POWER_STATUS_ON); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } +} From b4e2a5bc6d15629b156a605d1cad757fcade8f99 Mon Sep 17 00:00:00 2001 From: Michal Olech Date: Fri, 22 Jan 2021 15:42:33 +0100 Subject: [PATCH 3/3] [CEC Configuration] Fix race condition with listeners Bug: 178095692 Test: atest HdmiCecConfigTest --iterations 25 Change-Id: I5ac00d596fe3d4b5af5f69d2fbaac9cc38f99ca1 --- .../android/server/hdmi/HdmiCecConfig.java | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java index c6604544e6c70..9da90cf7a4cc9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java @@ -37,6 +37,7 @@ import android.provider.Settings.Global; import android.util.ArrayMap; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.cec.config.CecSettings; import com.android.server.hdmi.cec.config.Setting; @@ -95,6 +96,9 @@ public class HdmiCecConfig { @Nullable private final CecSettings mSystemConfig; @Nullable private final CecSettings mVendorOverride; + private final Object mLock = new Object(); + + @GuardedBy("mLock") private final ArrayMap> mSettingChangeListeners = new ArrayMap<>(); @@ -389,12 +393,14 @@ public class HdmiCecConfig { } private void notifySettingChanged(@NonNull Setting setting) { - Set listeners = mSettingChangeListeners.get(setting); - if (listeners == null) { - return; // No listeners registered, do nothing. - } - for (SettingChangeListener listener: listeners) { - listener.onChange(setting.getName()); + synchronized (mLock) { + Set listeners = mSettingChangeListeners.get(setting); + if (listeners == null) { + return; // No listeners registered, do nothing. + } + for (SettingChangeListener listener: listeners) { + listener.onChange(setting.getName()); + } } } @@ -440,10 +446,12 @@ public class HdmiCecConfig { throw new IllegalArgumentException("Change listeners for setting '" + name + "' not supported."); } - if (!mSettingChangeListeners.containsKey(setting)) { - mSettingChangeListeners.put(setting, new HashSet<>()); + synchronized (mLock) { + if (!mSettingChangeListeners.containsKey(setting)) { + mSettingChangeListeners.put(setting, new HashSet<>()); + } + mSettingChangeListeners.get(setting).add(listener); } - mSettingChangeListeners.get(setting).add(listener); } /** @@ -455,11 +463,13 @@ public class HdmiCecConfig { if (setting == null) { throw new IllegalArgumentException("Setting '" + name + "' does not exist."); } - if (mSettingChangeListeners.containsKey(setting)) { - Set listeners = mSettingChangeListeners.get(setting); - listeners.remove(listener); - if (listeners.isEmpty()) { - mSettingChangeListeners.remove(setting); + synchronized (mLock) { + if (mSettingChangeListeners.containsKey(setting)) { + Set listeners = mSettingChangeListeners.get(setting); + listeners.remove(listener); + if (listeners.isEmpty()) { + mSettingChangeListeners.remove(setting); + } } } }