Merge "Revert "Revert "[CEC Configuration] Add listener support to the HdmiCecConfig"""
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 98d130f..a759629 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.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 @@
@Nullable private final CecSettings mProductConfig;
@Nullable private final CecSettings mVendorOverride;
+ private final ArrayMap<Setting, Set<SettingChangeListener>>
+ 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 @@
}
}
+ 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 @@
} else if (storage == STORAGE_SHARED_PREFS) {
Slog.d(TAG, "Setting '" + storageKey + "' shared pref.");
mStorageAdapter.storeSharedPref(storageKey, value);
+ notifySettingChanged(setting);
}
}
@@ -318,6 +355,103 @@
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<SettingChangeListener> 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<SettingChangeListener> 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 a1d13e9..7617b71 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 @@
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 777713e..ef39e7e 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 @@
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.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 @@
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,
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ + "<cec-settings>"
+ + " <setting name=\"system_audio_mode_muting\""
+ + " value-type=\"int\""
+ + " user-configurable=\"true\">"
+ + " <allowed-values>"
+ + " <value int-value=\"0\" />"
+ + " <value int-value=\"1\" />"
+ + " </allowed-values>"
+ + " <default-value int-value=\"1\" />"
+ + " </setting>"
+ + "</cec-settings>", 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,
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ + "<cec-settings>"
+ + " <setting name=\"system_audio_mode_muting\""
+ + " value-type=\"int\""
+ + " user-configurable=\"true\">"
+ + " <allowed-values>"
+ + " <value int-value=\"0\" />"
+ + " <value int-value=\"1\" />"
+ + " </allowed-values>"
+ + " <default-value int-value=\"1\" />"
+ + " </setting>"
+ + "</cec-settings>", 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,
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ + "<cec-settings>"
+ + " <setting name=\"hdmi_cec_enabled\""
+ + " value-type=\"int\""
+ + " user-configurable=\"true\">"
+ + " <allowed-values>"
+ + " <value int-value=\"0\" />"
+ + " <value int-value=\"1\" />"
+ + " </allowed-values>"
+ + " <default-value int-value=\"1\" />"
+ + " </setting>"
+ + "</cec-settings>", 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);
+ }
+ }
}