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

Commit d4da9ce1 authored by Michal Olech's avatar Michal Olech
Browse files

[CEC Configuration] Add listener support to the HdmiCecConfig

Bug: 172905515
Test: atest HdmiCecConfigTest
Change-Id: I45bd7417a83fd4345205ddfb433d11273ebd33d1
parent ddfc4a5e
Loading
Loading
Loading
Loading
+134 −0
Original line number Original line Diff line number Diff line
@@ -22,12 +22,19 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.StringDef;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiControlManager;
import android.net.Uri;
import android.os.Environment;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.provider.Settings.Global;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.Slog;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
@@ -88,6 +95,23 @@ public class HdmiCecConfig {
    @Nullable private final CecSettings mSystemConfig;
    @Nullable private final CecSettings mSystemConfig;
    @Nullable private final CecSettings mVendorOverride;
    @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.
     * 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
    @VisibleForTesting
    HdmiCecConfig(@NonNull Context context,
    HdmiCecConfig(@NonNull Context context,
                  @NonNull StorageAdapter storageAdapter,
                  @NonNull StorageAdapter storageAdapter,
@@ -311,6 +347,7 @@ public class HdmiCecConfig {
        } else if (storage == STORAGE_SHARED_PREFS) {
        } else if (storage == STORAGE_SHARED_PREFS) {
            Slog.d(TAG, "Setting '" + storageKey + "' shared pref.");
            Slog.d(TAG, "Setting '" + storageKey + "' shared pref.");
            mStorageAdapter.storeSharedPref(storageKey, value);
            mStorageAdapter.storeSharedPref(storageKey, value);
            notifySettingChanged(setting);
        }
        }
    }
    }


@@ -318,6 +355,103 @@ public class HdmiCecConfig {
        return Integer.decode(value.getIntValue());
        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.
     * Returns a list of all settings based on the XML metadata.
     */
     */
+1 −0
Original line number Original line Diff line number Diff line
@@ -563,6 +563,7 @@ public class HdmiControlService extends SystemService {
        if (mMessageValidator == null) {
        if (mMessageValidator == null) {
            mMessageValidator = new HdmiCecMessageValidator(this);
            mMessageValidator = new HdmiCecMessageValidator(this);
        }
        }
        mHdmiCecConfig.registerGlobalSettingsObserver(mIoLooper);
    }
    }


    private void bootCompleted() {
    private void bootCompleted() {
+111 −0
Original line number Original line Diff line number Diff line
@@ -18,13 +18,17 @@ package com.android.server.hdmi;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertThat;


import static junit.framework.Assert.assertTrue;
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.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertThrows;


import android.annotation.NonNull;
import android.content.Context;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiControlManager;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings.Global;
import android.provider.Settings.Global;


@@ -38,15 +42,21 @@ import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoAnnotations;


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@SmallTest
@SmallTest
@Presubmit
@Presubmit
@RunWith(JUnit4.class)
@RunWith(JUnit4.class)
public final class HdmiCecConfigTest {
public final class HdmiCecConfigTest {
    private static final String TAG = "HdmiCecConfigTest";
    private static final String TAG = "HdmiCecConfigTest";


    private static final int TIMEOUT_CONTENT_CHANGE_SEC = 4;

    private Context mContext;
    private Context mContext;


    @Mock private HdmiCecConfig.StorageAdapter mStorageAdapter;
    @Mock private HdmiCecConfig.StorageAdapter mStorageAdapter;
    @Mock private HdmiCecConfig.SettingChangeListener mSettingChangeListener;


    @Before
    @Before
    public void setUp() throws Exception {
    public void setUp() throws Exception {
@@ -1019,4 +1029,105 @@ public final class HdmiCecConfigTest {
                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
                Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED));
                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 val = Global.getString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED);
        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(Looper.getMainLooper());
        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);
        // Flip the value of the setting.
        Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED,
                         ((val == null || val.equals("1")) ? "0" : "1"));
        if (!notifyLatch.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
            fail("Timed out waiting for the notify callback");
        }
        hdmiCecConfig.unregisterGlobalSettingsObserver();
        // Restore the previous value of the setting in the system.
        Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED, val);
    }
}
}