From e7e7c59ed065ae1ebeae1d5f738aa88c350bc2da Mon Sep 17 00:00:00 2001 From: Marvin Ramin Date: Thu, 3 Dec 2020 09:33:06 +0100 Subject: [PATCH] Revert "Revert "[CEC Configuration] Add listener support to the HdmiCecConfig"" This reverts commit 906e3ea6a838f5e9fc3d14a9e89b2a0393266881. Failing test was modified to trigger the Settings listener more reliably. Running the test many iterations did not show any failures locally. Bug: 172905515 Test: atest HdmiCecConfigTest --iterations 25 Change-Id: Id81979ef3c38dfa7aa8db3a66a3d23db562bd0ea --- .../android/server/hdmi/HdmiCecConfig.java | 134 ++++++++++++++++++ .../server/hdmi/HdmiControlService.java | 1 + .../server/hdmi/HdmiCecConfigTest.java | 132 +++++++++++++++++ 3 files changed, 267 insertions(+) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java index a261fa1f2741f..2374ece1dd65b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java @@ -22,12 +22,19 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; +import android.database.ContentObserver; import android.hardware.hdmi.HdmiControlManager; +import android.net.Uri; import android.os.Environment; +import android.os.Handler; +import android.os.Looper; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings.Global; +import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -88,6 +95,23 @@ public class HdmiCecConfig { @Nullable private final CecSettings mProductConfig; @Nullable private final CecSettings mVendorOverride; + private final ArrayMap> + mSettingChangeListeners = new ArrayMap<>(); + + private SettingsObserver mSettingsObserver; + + /** + * Listener used to get notifications when value of a setting changes. + */ + public interface SettingChangeListener { + /** + * Called when value of a setting changes. + * + * @param setting name of a CEC setting that changed + */ + void onChange(@NonNull @CecSettingName String setting); + } + /** * Setting storage input/output helper class. */ @@ -159,6 +183,18 @@ public class HdmiCecConfig { } } + private class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + String setting = uri.getLastPathSegment(); + HdmiCecConfig.this.notifyGlobalSettingChanged(setting); + } + } + @VisibleForTesting HdmiCecConfig(@NonNull Context context, @NonNull StorageAdapter storageAdapter, @@ -311,6 +347,7 @@ public class HdmiCecConfig { } else if (storage == STORAGE_SHARED_PREFS) { Slog.d(TAG, "Setting '" + storageKey + "' shared pref."); mStorageAdapter.storeSharedPref(storageKey, value); + notifySettingChanged(setting); } } @@ -318,6 +355,103 @@ public class HdmiCecConfig { return Integer.decode(value.getIntValue()); } + private void notifyGlobalSettingChanged(String setting) { + switch (setting) { + case Global.HDMI_CONTROL_ENABLED: + notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED); + break; + case Global.HDMI_CEC_VERSION: + notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION); + break; + case Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP: + notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP); + break; + } + } + + private void notifySettingChanged(@NonNull @CecSettingName String name) { + Setting setting = getSetting(name); + if (setting == null) { + throw new IllegalArgumentException("Setting '" + name + "' does not exist."); + } + notifySettingChanged(setting); + } + + 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()); + } + } + + /** + * This method registers Global Setting change observer. + * Needs to be called once after initialization of HdmiCecConfig. + */ + public void registerGlobalSettingsObserver(Looper looper) { + Handler handler = new Handler(looper); + mSettingsObserver = new SettingsObserver(handler); + ContentResolver resolver = mContext.getContentResolver(); + String[] settings = new String[] { + Global.HDMI_CONTROL_ENABLED, + Global.HDMI_CEC_VERSION, + Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, + }; + for (String setting: settings) { + resolver.registerContentObserver(Global.getUriFor(setting), false, + mSettingsObserver, UserHandle.USER_ALL); + } + } + + /** + * This method unregisters Global Setting change observer. + */ + public void unregisterGlobalSettingsObserver() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.unregisterContentObserver(mSettingsObserver); + } + + /** + * Register change listener for a given setting name. + */ + public void registerChangeListener(@NonNull @CecSettingName String name, + SettingChangeListener listener) { + Setting setting = getSetting(name); + if (setting == null) { + throw new IllegalArgumentException("Setting '" + name + "' does not exist."); + } + @Storage int storage = getStorage(setting); + if (storage != STORAGE_GLOBAL_SETTINGS && storage != STORAGE_SHARED_PREFS) { + throw new IllegalArgumentException("Change listeners for setting '" + name + + "' not supported."); + } + if (!mSettingChangeListeners.containsKey(setting)) { + mSettingChangeListeners.put(setting, new HashSet<>()); + } + mSettingChangeListeners.get(setting).add(listener); + } + + /** + * Remove change listener for a given setting name. + */ + public void removeChangeListener(@NonNull @CecSettingName String name, + SettingChangeListener listener) { + Setting setting = getSetting(name); + 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); + } + } + } + /** * Returns a list of all settings based on the XML metadata. */ diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 56b73ba04d893..f3d0623f93f71 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -496,6 +496,7 @@ public class HdmiControlService extends SystemService { if (mMessageValidator == null) { mMessageValidator = new HdmiCecMessageValidator(this); } + mHdmiCecConfig.registerGlobalSettingsObserver(mIoLooper); } private void bootCompleted() { 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 d10e075c51360..dbfadc841c1a6 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java @@ -18,13 +18,17 @@ package com.android.server.hdmi; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.annotation.NonNull; import android.content.Context; import android.hardware.hdmi.HdmiControlManager; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.provider.Settings.Global; @@ -38,15 +42,23 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @SmallTest @Presubmit @RunWith(JUnit4.class) public final class HdmiCecConfigTest { private static final String TAG = "HdmiCecConfigTest"; + private static final int TIMEOUT_CONTENT_CHANGE_SEC = 4; + + private final TestLooper mTestLooper = new TestLooper(); + private Context mContext; @Mock private HdmiCecConfig.StorageAdapter mStorageAdapter; + @Mock private HdmiCecConfig.SettingChangeListener mSettingChangeListener; @Before public void setUp() throws Exception { @@ -1019,4 +1031,124 @@ public final class HdmiCecConfigTest { HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED)); } + + @Test + public void registerChangeListener_SharedPref_BasicSanity() { + HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( + mContext, mStorageAdapter, + "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "", null); + hdmiCecConfig.registerChangeListener( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + mSettingChangeListener); + hdmiCecConfig.setIntValue( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED); + verify(mSettingChangeListener).onChange( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING); + } + + @Test + public void removeChangeListener_SharedPref_BasicSanity() { + HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( + mContext, mStorageAdapter, + "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "", null); + hdmiCecConfig.registerChangeListener( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + mSettingChangeListener); + hdmiCecConfig.removeChangeListener( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + mSettingChangeListener); + hdmiCecConfig.setIntValue( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED); + verify(mSettingChangeListener, never()).onChange( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING); + } + + /** + * Externally modified Global Settings still need to be supported. This test verifies that + * setting change notification is being forwarded to listeners registered via HdmiCecConfig. + */ + @Test + public void globalSettingObserver_BasicSanity() throws Exception { + CountDownLatch notifyLatch = new CountDownLatch(1); + // Get current value of the setting in the system. + String originalValue = Global.getString(mContext.getContentResolver(), + Global.HDMI_CONTROL_ENABLED); + try { + HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( + mContext, mStorageAdapter, + "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "", null); + hdmiCecConfig.registerGlobalSettingsObserver(mTestLooper.getLooper()); + HdmiCecConfig.SettingChangeListener latchUpdateListener = + new HdmiCecConfig.SettingChangeListener() { + @Override + public void onChange( + @NonNull @HdmiControlManager.CecSettingName String setting) { + notifyLatch.countDown(); + assertThat(setting).isEqualTo( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED); + } + }; + hdmiCecConfig.registerChangeListener( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, + latchUpdateListener); + mTestLooper.dispatchAll(); + + // Flip the value of the setting. + String valueToSet = ((originalValue == null || originalValue.equals("1")) ? "0" : "1"); + Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED, + valueToSet); + assertThat(Global.getString(mContext.getContentResolver(), + Global.HDMI_CONTROL_ENABLED)).isEqualTo(valueToSet); + + // Write Setting a 2nd time as the listener doesn't always trigger on the first write + // in the test. + Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED, + valueToSet); + mTestLooper.dispatchAll(); + + if (!notifyLatch.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) { + fail("Timed out waiting for the notify callback"); + } + hdmiCecConfig.unregisterGlobalSettingsObserver(); + } finally { + // Restore the previous value of the setting in the system. + Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED, + originalValue); + } + } }