Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e7e7c59e authored by Marvin Ramin's avatar Marvin Ramin
Browse files

Revert "Revert "[CEC Configuration] Add listener support to the HdmiCecConfig""

This reverts commit 906e3ea6.

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
parent 449b6f96
Loading
Loading
Loading
Loading
+134 −0
Original line number Diff line number Diff line
@@ -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.
     */
+1 −0
Original line number Diff line number Diff line
@@ -496,6 +496,7 @@ public class HdmiControlService extends SystemService {
        if (mMessageValidator == null) {
            mMessageValidator = new HdmiCecMessageValidator(this);
        }
        mHdmiCecConfig.registerGlobalSettingsObserver(mIoLooper);
    }

    private void bootCompleted() {
+132 −0
Original line number Diff line number Diff line
@@ -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);
        }
    }
}