Revert "Revert "[CEC Configuration] Add listener support to the HdmiCecConfig""
This reverts commit 906e3ea6a8.
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
This commit is contained in:
@@ -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<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 @@ 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<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.
|
||||
*/
|
||||
|
||||
@@ -496,6 +496,7 @@ public class HdmiControlService extends SystemService {
|
||||
if (mMessageValidator == null) {
|
||||
mMessageValidator = new HdmiCecMessageValidator(this);
|
||||
}
|
||||
mHdmiCecConfig.registerGlobalSettingsObserver(mIoLooper);
|
||||
}
|
||||
|
||||
private void bootCompleted() {
|
||||
|
||||
@@ -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,
|
||||
"<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user