1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.hdmi;
18 
19 import static android.hardware.hdmi.HdmiControlManager.CecSettingName;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.StringDef;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.SharedPreferences;
28 import android.database.ContentObserver;
29 import android.hardware.hdmi.HdmiControlManager;
30 import android.net.Uri;
31 import android.os.Environment;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.SystemProperties;
35 import android.os.UserHandle;
36 import android.provider.Settings.Global;
37 import android.util.ArrayMap;
38 
39 import com.android.internal.R;
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.util.ConcurrentUtils;
43 
44 import java.io.File;
45 import java.util.ArrayList;
46 import java.util.LinkedHashMap;
47 import java.util.List;
48 import java.util.Map.Entry;
49 import java.util.concurrent.Executor;
50 
51 /**
52  * The {@link HdmiCecConfig} class is used for getting information about
53  * available HDMI CEC settings.
54  */
55 public class HdmiCecConfig {
56     private static final String TAG = "HdmiCecConfig";
57 
58     private static final String ETC_DIR = "etc";
59     private static final String CONFIG_FILE = "cec_config.xml";
60     private static final String SHARED_PREFS_DIR = "shared_prefs";
61     private static final String SHARED_PREFS_NAME = "cec_config.xml";
62 
63     private static final int STORAGE_SYSPROPS = 0;
64     private static final int STORAGE_GLOBAL_SETTINGS = 1;
65     private static final int STORAGE_SHARED_PREFS = 2;
66 
67     @IntDef({
68         STORAGE_SYSPROPS,
69         STORAGE_GLOBAL_SETTINGS,
70         STORAGE_SHARED_PREFS,
71     })
72     private @interface Storage {}
73 
74     private static final String VALUE_TYPE_STRING = "string";
75     private static final String VALUE_TYPE_INT = "int";
76 
77     @StringDef({
78         VALUE_TYPE_STRING,
79         VALUE_TYPE_INT,
80     })
81     private @interface ValueType {}
82 
83     @NonNull private final Context mContext;
84     @NonNull private final StorageAdapter mStorageAdapter;
85 
86     private final Object mLock = new Object();
87 
88     @GuardedBy("mLock")
89     private final ArrayMap<Setting, ArrayMap<SettingChangeListener, Executor>>
90             mSettingChangeListeners = new ArrayMap<>();
91 
92     private SettingsObserver mSettingsObserver;
93 
94     private LinkedHashMap<String, Setting> mSettings = new LinkedHashMap<>();
95 
96     /**
97      * Exception thrown when the CEC Configuration setup verification fails.
98      * This usually means a settings lacks default value or storage/storage key.
99      */
100     public static class VerificationException extends RuntimeException {
VerificationException(String message)101         public VerificationException(String message) {
102             super(message);
103         }
104     }
105 
106     /**
107      * Listener used to get notifications when value of a setting changes.
108      */
109     public interface SettingChangeListener {
110         /**
111          * Called when value of a setting changes.
112          *
113          * @param setting name of a CEC setting that changed
114          */
onChange(@onNull @ecSettingName String setting)115         void onChange(@NonNull @CecSettingName String setting);
116     }
117 
118     /**
119      * Setting storage input/output helper class.
120      */
121     public static class StorageAdapter {
122         @NonNull private final Context mContext;
123         @NonNull private final SharedPreferences mSharedPrefs;
124 
StorageAdapter(@onNull Context context)125         StorageAdapter(@NonNull Context context) {
126             mContext = context;
127             // The package info in the context isn't initialized in the way it is for normal apps,
128             // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
129             // build the path manually below using the same policy that appears in ContextImpl.
130             final Context deviceContext = mContext.createDeviceProtectedStorageContext();
131             final File prefsFile = new File(new File(Environment.getDataSystemDirectory(),
132                                                      SHARED_PREFS_DIR), SHARED_PREFS_NAME);
133             mSharedPrefs = deviceContext.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
134         }
135 
136         /**
137          * Read the value from a system property.
138          * Returns the given default value if the system property is not set.
139          */
retrieveSystemProperty(@onNull String storageKey, @NonNull String defaultValue)140         public String retrieveSystemProperty(@NonNull String storageKey,
141                                              @NonNull String defaultValue) {
142             return SystemProperties.get(storageKey, defaultValue);
143         }
144 
145         /**
146          * Write the value to a system property.
147          */
storeSystemProperty(@onNull String storageKey, @NonNull String value)148         public void storeSystemProperty(@NonNull String storageKey,
149                                         @NonNull String value) {
150             SystemProperties.set(storageKey, value);
151         }
152 
153         /**
154          * Read the value from a global setting.
155          * Returns the given default value if the system property is not set.
156          */
retrieveGlobalSetting(@onNull String storageKey, @NonNull String defaultValue)157         public String retrieveGlobalSetting(@NonNull String storageKey,
158                                             @NonNull String defaultValue) {
159             String value = Global.getString(mContext.getContentResolver(), storageKey);
160             return value != null ? value : defaultValue;
161         }
162 
163         /**
164          * Write the value to a global setting.
165          */
storeGlobalSetting(@onNull String storageKey, @NonNull String value)166         public void storeGlobalSetting(@NonNull String storageKey,
167                                        @NonNull String value) {
168             Global.putString(mContext.getContentResolver(), storageKey, value);
169         }
170 
171         /**
172          * Read the value from a shared preference.
173          * Returns the given default value if the preference is not set.
174          */
retrieveSharedPref(@onNull String storageKey, @NonNull String defaultValue)175         public String retrieveSharedPref(@NonNull String storageKey,
176                                          @NonNull String defaultValue) {
177             return mSharedPrefs.getString(storageKey, defaultValue);
178         }
179 
180         /**
181          * Write the value to a shared preference.
182          */
storeSharedPref(@onNull String storageKey, @NonNull String value)183         public void storeSharedPref(@NonNull String storageKey,
184                                     @NonNull String value) {
185             mSharedPrefs.edit().putString(storageKey, value).apply();
186         }
187     }
188 
189     private class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler)190         SettingsObserver(Handler handler) {
191             super(handler);
192         }
193 
194         @Override
onChange(boolean selfChange, Uri uri)195         public void onChange(boolean selfChange, Uri uri) {
196             String setting = uri.getLastPathSegment();
197             HdmiCecConfig.this.notifyGlobalSettingChanged(setting);
198         }
199     }
200 
201     private class Value {
202         private final String mStringValue;
203         private final Integer mIntValue;
204 
Value(@onNull String value)205         Value(@NonNull String value) {
206             mStringValue = value;
207             mIntValue = null;
208         }
209 
Value(@onNull Integer value)210         Value(@NonNull Integer value) {
211             mStringValue = null;
212             mIntValue = value;
213         }
214 
getStringValue()215         String getStringValue() {
216             return mStringValue;
217         }
218 
getIntValue()219         Integer getIntValue() {
220             return mIntValue;
221         }
222     }
223 
224     protected class Setting {
225         @NonNull private final Context mContext;
226         @NonNull private final @CecSettingName String mName;
227         private final boolean mUserConfigurable;
228 
229         private Value mDefaultValue = null;
230         private List<Value> mAllowedValues = new ArrayList<>();
231 
Setting(@onNull Context context, @NonNull @CecSettingName String name, int userConfResId)232         Setting(@NonNull Context context,
233                 @NonNull @CecSettingName String name,
234                 int userConfResId) {
235             mContext = context;
236             mName = name;
237             mUserConfigurable = mContext.getResources().getBoolean(userConfResId);
238         }
239 
getName()240         public @CecSettingName String getName() {
241             return mName;
242         }
243 
getValueType()244         public @ValueType String getValueType() {
245             return getDefaultValue().getStringValue() != null
246                     ? VALUE_TYPE_STRING
247                     : VALUE_TYPE_INT;
248         }
249 
getDefaultValue()250         public Value getDefaultValue() {
251             if (mDefaultValue == null) {
252                 throw new VerificationException("Invalid CEC setup for '"
253                     + this.getName() + "' setting. "
254                     + "Setting has no default value.");
255             }
256             return mDefaultValue;
257         }
258 
getUserConfigurable()259         public boolean getUserConfigurable() {
260             return mUserConfigurable;
261         }
262 
registerValue(@onNull Value value, int allowedResId, int defaultResId)263         private void registerValue(@NonNull Value value,
264                                    int allowedResId, int defaultResId) {
265             if (mContext.getResources().getBoolean(allowedResId)) {
266                 mAllowedValues.add(value);
267                 if (mContext.getResources().getBoolean(defaultResId)) {
268                     if (mDefaultValue != null) {
269                         throw new VerificationException("Invalid CEC setup for '"
270                             + this.getName() + "' setting. "
271                             + "Setting already has a default value.");
272                     }
273                     mDefaultValue = value;
274                 }
275             }
276         }
277 
registerValue(@onNull String value, int allowedResId, int defaultResId)278         public void registerValue(@NonNull String value, int allowedResId,
279                                   int defaultResId) {
280             registerValue(new Value(value), allowedResId, defaultResId);
281         }
282 
registerValue(int value, int allowedResId, int defaultResId)283         public void registerValue(int value, int allowedResId,
284                                   int defaultResId) {
285             registerValue(new Value(value), allowedResId, defaultResId);
286         }
287 
288 
getAllowedValues()289         public List<Value> getAllowedValues() {
290             return mAllowedValues;
291         }
292     }
293 
294     @VisibleForTesting
HdmiCecConfig(@onNull Context context, @NonNull StorageAdapter storageAdapter)295     HdmiCecConfig(@NonNull Context context,
296                   @NonNull StorageAdapter storageAdapter) {
297         mContext = context;
298         mStorageAdapter = storageAdapter;
299 
300         Setting hdmiCecEnabled = registerSetting(
301                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
302                 R.bool.config_cecHdmiCecEnabled_userConfigurable);
303         hdmiCecEnabled.registerValue(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED,
304                 R.bool.config_cecHdmiCecControlEnabled_allowed,
305                 R.bool.config_cecHdmiCecControlEnabled_default);
306         hdmiCecEnabled.registerValue(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED,
307                 R.bool.config_cecHdmiCecControlDisabled_allowed,
308                 R.bool.config_cecHdmiCecControlDisabled_default);
309 
310         Setting hdmiCecVersion = registerSetting(
311                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
312                 R.bool.config_cecHdmiCecVersion_userConfigurable);
313         hdmiCecVersion.registerValue(HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
314                 R.bool.config_cecHdmiCecVersion14b_allowed,
315                 R.bool.config_cecHdmiCecVersion14b_default);
316         hdmiCecVersion.registerValue(HdmiControlManager.HDMI_CEC_VERSION_2_0,
317                 R.bool.config_cecHdmiCecVersion20_allowed,
318                 R.bool.config_cecHdmiCecVersion20_default);
319 
320         Setting powerControlMode = registerSetting(
321                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
322                 R.bool.config_cecPowerControlMode_userConfigurable);
323         powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV,
324                 R.bool.config_cecPowerControlModeTv_allowed,
325                 R.bool.config_cecPowerControlModeTv_default);
326         powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST,
327                 R.bool.config_cecPowerControlModeBroadcast_allowed,
328                 R.bool.config_cecPowerControlModeBroadcast_default);
329         powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_NONE,
330                 R.bool.config_cecPowerControlModeNone_allowed,
331                 R.bool.config_cecPowerControlModeNone_default);
332 
333         Setting powerStateChangeOnActiveSourceLost = registerSetting(
334                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
335                 R.bool.config_cecPowerStateChangeOnActiveSourceLost_userConfigurable);
336         powerStateChangeOnActiveSourceLost.registerValue(
337                 HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE,
338                 R.bool.config_cecPowerStateChangeOnActiveSourceLostNone_allowed,
339                 R.bool.config_cecPowerStateChangeOnActiveSourceLostNone_default);
340         powerStateChangeOnActiveSourceLost.registerValue(
341                 HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW,
342                 R.bool.config_cecPowerStateChangeOnActiveSourceLostStandbyNow_allowed,
343                 R.bool.config_cecPowerStateChangeOnActiveSourceLostStandbyNow_default);
344 
345         Setting systemAudioModeMuting = registerSetting(
346                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
347                 R.bool.config_cecSystemAudioModeMuting_userConfigurable);
348         systemAudioModeMuting.registerValue(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED,
349                 R.bool.config_cecSystemAudioModeMutingEnabled_allowed,
350                 R.bool.config_cecSystemAudioModeMutingEnabled_default);
351         systemAudioModeMuting.registerValue(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED,
352                 R.bool.config_cecSystemAudioModeMutingDisabled_allowed,
353                 R.bool.config_cecSystemAudioModeMutingDisabled_default);
354 
355         Setting volumeControlMode = registerSetting(
356                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
357                 R.bool.config_cecVolumeControlMode_userConfigurable);
358         volumeControlMode.registerValue(HdmiControlManager.VOLUME_CONTROL_ENABLED,
359                 R.bool.config_cecVolumeControlModeEnabled_allowed,
360                 R.bool.config_cecVolumeControlModeEnabled_default);
361         volumeControlMode.registerValue(HdmiControlManager.VOLUME_CONTROL_DISABLED,
362                 R.bool.config_cecVolumeControlModeDisabled_allowed,
363                 R.bool.config_cecVolumeControlModeDisabled_default);
364 
365         Setting tvWakeOnOneTouchPlay = registerSetting(
366                 HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
367                 R.bool.config_cecTvWakeOnOneTouchPlay_userConfigurable);
368         tvWakeOnOneTouchPlay.registerValue(HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED,
369                 R.bool.config_cecTvWakeOnOneTouchPlayEnabled_allowed,
370                 R.bool.config_cecTvWakeOnOneTouchPlayEnabled_default);
371         tvWakeOnOneTouchPlay.registerValue(HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED,
372                 R.bool.config_cecTvWakeOnOneTouchPlayDisabled_allowed,
373                 R.bool.config_cecTvWakeOnOneTouchPlayDisabled_default);
374 
375         Setting tvSendStandbyOnSleep = registerSetting(
376                 HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
377                 R.bool.config_cecTvSendStandbyOnSleep_userConfigurable);
378         tvSendStandbyOnSleep.registerValue(HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED,
379                 R.bool.config_cecTvSendStandbyOnSleepEnabled_allowed,
380                 R.bool.config_cecTvSendStandbyOnSleepEnabled_default);
381         tvSendStandbyOnSleep.registerValue(HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_DISABLED,
382                 R.bool.config_cecTvSendStandbyOnSleepDisabled_allowed,
383                 R.bool.config_cecTvSendStandbyOnSleepDisabled_default);
384 
385         Setting rcProfileTv = registerSetting(
386                 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
387                 R.bool.config_cecRcProfileTv_userConfigurable);
388         rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_NONE,
389                 R.bool.config_cecRcProfileTvNone_allowed,
390                 R.bool.config_cecRcProfileTvNone_default);
391         rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_ONE,
392                 R.bool.config_cecRcProfileTvOne_allowed,
393                 R.bool.config_cecRcProfileTvOne_default);
394         rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_TWO,
395                 R.bool.config_cecRcProfileTvTwo_allowed,
396                 R.bool.config_cecRcProfileTvTwo_default);
397         rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_THREE,
398                 R.bool.config_cecRcProfileTvThree_allowed,
399                 R.bool.config_cecRcProfileTvThree_default);
400         rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_FOUR,
401                 R.bool.config_cecRcProfileTvFour_allowed,
402                 R.bool.config_cecRcProfileTvFour_default);
403 
404         Setting rcProfileSourceRootMenu = registerSetting(
405                 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
406                 R.bool.config_cecRcProfileSourceRootMenu_userConfigurable);
407         rcProfileSourceRootMenu.registerValue(
408                 HdmiControlManager.RC_PROFILE_SOURCE_ROOT_MENU_HANDLED,
409                 R.bool.config_cecRcProfileSourceRootMenuHandled_allowed,
410                 R.bool.config_cecRcProfileSourceRootMenuHandled_default);
411         rcProfileSourceRootMenu.registerValue(
412                 HdmiControlManager.RC_PROFILE_SOURCE_ROOT_MENU_NOT_HANDLED,
413                 R.bool.config_cecRcProfileSourceRootMenuNotHandled_allowed,
414                 R.bool.config_cecRcProfileSourceRootMenuNotHandled_default);
415 
416         Setting rcProfileSourceSetupMenu = registerSetting(
417                 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
418                 R.bool.config_cecRcProfileSourceSetupMenu_userConfigurable);
419         rcProfileSourceSetupMenu.registerValue(
420                 HdmiControlManager.RC_PROFILE_SOURCE_SETUP_MENU_HANDLED,
421                 R.bool.config_cecRcProfileSourceSetupMenuHandled_allowed,
422                 R.bool.config_cecRcProfileSourceSetupMenuHandled_default);
423         rcProfileSourceSetupMenu.registerValue(
424                 HdmiControlManager.RC_PROFILE_SOURCE_SETUP_MENU_NOT_HANDLED,
425                 R.bool.config_cecRcProfileSourceSetupMenuNotHandled_allowed,
426                 R.bool.config_cecRcProfileSourceSetupMenuNotHandled_default);
427 
428         Setting rcProfileSourceContentsMenu = registerSetting(
429                 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
430                 R.bool.config_cecRcProfileSourceContentsMenu_userConfigurable);
431         rcProfileSourceContentsMenu.registerValue(
432                 HdmiControlManager.RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED,
433                 R.bool.config_cecRcProfileSourceContentsMenuHandled_allowed,
434                 R.bool.config_cecRcProfileSourceContentsMenuHandled_default);
435         rcProfileSourceContentsMenu.registerValue(
436                 HdmiControlManager.RC_PROFILE_SOURCE_CONTENTS_MENU_NOT_HANDLED,
437                 R.bool.config_cecRcProfileSourceContentsMenuNotHandled_allowed,
438                 R.bool.config_cecRcProfileSourceContentsMenuNotHandled_default);
439 
440         Setting rcProfileSourceTopMenu = registerSetting(
441                 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
442                 R.bool.config_cecRcProfileSourceTopMenu_userConfigurable);
443         rcProfileSourceTopMenu.registerValue(
444                 HdmiControlManager.RC_PROFILE_SOURCE_TOP_MENU_HANDLED,
445                 R.bool.config_cecRcProfileSourceTopMenuHandled_allowed,
446                 R.bool.config_cecRcProfileSourceTopMenuHandled_default);
447         rcProfileSourceTopMenu.registerValue(
448                 HdmiControlManager.RC_PROFILE_SOURCE_TOP_MENU_NOT_HANDLED,
449                 R.bool.config_cecRcProfileSourceTopMenuNotHandled_allowed,
450                 R.bool.config_cecRcProfileSourceTopMenuNotHandled_default);
451 
452         Setting rcProfileSourceMediaContextSensitiveMenu = registerSetting(
453                 HdmiControlManager
454                     .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
455                 R.bool.config_cecRcProfileSourceMediaContextSensitiveMenu_userConfigurable);
456         rcProfileSourceMediaContextSensitiveMenu.registerValue(
457                 HdmiControlManager.RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED,
458                 R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_allowed,
459                 R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_default);
460         rcProfileSourceMediaContextSensitiveMenu.registerValue(
461                 HdmiControlManager.RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_NOT_HANDLED,
462                 R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed,
463                 R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default);
464 
465         verifySettings();
466     }
467 
HdmiCecConfig(@onNull Context context)468     HdmiCecConfig(@NonNull Context context) {
469         this(context, new StorageAdapter(context));
470     }
471 
registerSetting(@onNull @ecSettingName String name, int userConfResId)472     private Setting registerSetting(@NonNull @CecSettingName String name,
473                                int userConfResId) {
474         Setting setting = new Setting(mContext, name, userConfResId);
475         mSettings.put(name, setting);
476         return setting;
477     }
478 
verifySettings()479     private void verifySettings() {
480         for (Setting setting: mSettings.values()) {
481             // This will throw an exception when a setting
482             // doesn't have a default value assigned.
483             setting.getDefaultValue();
484             getStorage(setting);
485             getStorageKey(setting);
486         }
487     }
488 
489     @Nullable
getSetting(@onNull String name)490     private Setting getSetting(@NonNull String name) {
491         return mSettings.containsKey(name) ? mSettings.get(name) : null;
492     }
493 
494     @Storage
getStorage(@onNull Setting setting)495     private int getStorage(@NonNull Setting setting) {
496         switch (setting.getName()) {
497             case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED:
498                 return STORAGE_GLOBAL_SETTINGS;
499             case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION:
500                 return STORAGE_SHARED_PREFS;
501             case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
502                 return STORAGE_GLOBAL_SETTINGS;
503             case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
504                 return STORAGE_GLOBAL_SETTINGS;
505             case HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST:
506                 return STORAGE_SHARED_PREFS;
507             case HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING:
508                 return STORAGE_SHARED_PREFS;
509             case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
510                 return STORAGE_GLOBAL_SETTINGS;
511             case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
512                 return STORAGE_GLOBAL_SETTINGS;
513             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
514                 return STORAGE_SHARED_PREFS;
515             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
516                 return STORAGE_SHARED_PREFS;
517             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU:
518                 return STORAGE_SHARED_PREFS;
519             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU:
520                 return STORAGE_SHARED_PREFS;
521             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU:
522                 return STORAGE_SHARED_PREFS;
523             case HdmiControlManager
524                     .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU:
525                 return STORAGE_SHARED_PREFS;
526             default:
527                 throw new VerificationException("Invalid CEC setting '" + setting.getName()
528                         + "' storage.");
529         }
530     }
531 
getStorageKey(@onNull Setting setting)532     private String getStorageKey(@NonNull Setting setting) {
533         switch (setting.getName()) {
534             case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED:
535                 return Global.HDMI_CONTROL_ENABLED;
536             case HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION:
537                 return setting.getName();
538             case HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE:
539                 return Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP;
540             case HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE:
541                 return Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED;
542             case HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST:
543                 return setting.getName();
544             case HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING:
545                 return setting.getName();
546             case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
547                 return Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED;
548             case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
549                 return Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED;
550             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
551                 return setting.getName();
552             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
553                 return setting.getName();
554             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU:
555                 return setting.getName();
556             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU:
557                 return setting.getName();
558             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU:
559                 return setting.getName();
560             case HdmiControlManager
561                     .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU:
562                 return setting.getName();
563             default:
564                 throw new VerificationException("Invalid CEC setting '" + setting.getName()
565                     + "' storage key.");
566         }
567     }
568 
retrieveValue(@onNull Setting setting, @NonNull String defaultValue)569     protected String retrieveValue(@NonNull Setting setting, @NonNull String defaultValue) {
570         @Storage int storage = getStorage(setting);
571         String storageKey = getStorageKey(setting);
572         if (storage == STORAGE_SYSPROPS) {
573             HdmiLogger.debug("Reading '" + storageKey + "' sysprop.");
574             return mStorageAdapter.retrieveSystemProperty(storageKey, defaultValue);
575         } else if (storage == STORAGE_GLOBAL_SETTINGS) {
576             HdmiLogger.debug("Reading '" + storageKey + "' global setting.");
577             return mStorageAdapter.retrieveGlobalSetting(storageKey, defaultValue);
578         } else if (storage == STORAGE_SHARED_PREFS) {
579             HdmiLogger.debug("Reading '" + storageKey + "' shared preference.");
580             return mStorageAdapter.retrieveSharedPref(storageKey, defaultValue);
581         }
582         return null;
583     }
584 
storeValue(@onNull Setting setting, @NonNull String value)585     protected void storeValue(@NonNull Setting setting, @NonNull String value) {
586         @Storage int storage = getStorage(setting);
587         String storageKey = getStorageKey(setting);
588         if (storage == STORAGE_SYSPROPS) {
589             HdmiLogger.debug("Setting '" + storageKey + "' sysprop.");
590             mStorageAdapter.storeSystemProperty(storageKey, value);
591         } else if (storage == STORAGE_GLOBAL_SETTINGS) {
592             HdmiLogger.debug("Setting '" + storageKey + "' global setting.");
593             mStorageAdapter.storeGlobalSetting(storageKey, value);
594         } else if (storage == STORAGE_SHARED_PREFS) {
595             HdmiLogger.debug("Setting '" + storageKey + "' shared pref.");
596             mStorageAdapter.storeSharedPref(storageKey, value);
597             notifySettingChanged(setting);
598         }
599     }
600 
notifyGlobalSettingChanged(String setting)601     private void notifyGlobalSettingChanged(String setting) {
602         switch (setting) {
603             case Global.HDMI_CONTROL_ENABLED:
604                 notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
605                 break;
606             case Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP:
607                 notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
608                 break;
609             case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
610                 notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE);
611                 break;
612             case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
613                 notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY);
614                 break;
615             case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
616                 notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
617                 break;
618         }
619     }
620 
notifySettingChanged(@onNull @ecSettingName String name)621     private void notifySettingChanged(@NonNull @CecSettingName String name) {
622         Setting setting = getSetting(name);
623         if (setting == null) {
624             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
625         }
626         notifySettingChanged(setting);
627     }
628 
notifySettingChanged(@onNull Setting setting)629     protected void notifySettingChanged(@NonNull Setting setting) {
630         synchronized (mLock) {
631             ArrayMap<SettingChangeListener, Executor> listeners =
632                     mSettingChangeListeners.get(setting);
633             if (listeners == null) {
634                 return;  // No listeners registered, do nothing.
635             }
636             for (Entry<SettingChangeListener, Executor> entry: listeners.entrySet()) {
637                 SettingChangeListener listener = entry.getKey();
638                 Executor executor = entry.getValue();
639                 executor.execute(new Runnable() {
640                     @Override
641                     public void run() {
642                         listener.onChange(setting.getName());
643                     }
644                 });
645             }
646         }
647     }
648 
649     /**
650      * This method registers Global Setting change observer.
651      * Needs to be called once after initialization of HdmiCecConfig.
652      */
registerGlobalSettingsObserver(Looper looper)653     public void registerGlobalSettingsObserver(Looper looper) {
654         Handler handler = new Handler(looper);
655         mSettingsObserver = new SettingsObserver(handler);
656         ContentResolver resolver = mContext.getContentResolver();
657         String[] settings = new String[] {
658                 Global.HDMI_CONTROL_ENABLED,
659                 Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
660                 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
661                 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
662                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
663         };
664         for (String setting: settings) {
665             resolver.registerContentObserver(Global.getUriFor(setting), false,
666                                              mSettingsObserver, UserHandle.USER_ALL);
667         }
668     }
669 
670     /**
671      * This method unregisters Global Setting change observer.
672      */
unregisterGlobalSettingsObserver()673     public void unregisterGlobalSettingsObserver() {
674         ContentResolver resolver = mContext.getContentResolver();
675         resolver.unregisterContentObserver(mSettingsObserver);
676     }
677 
678     /**
679      * Register change listener for a given setting name using DirectExecutor.
680      */
registerChangeListener(@onNull @ecSettingName String name, SettingChangeListener listener)681     public void registerChangeListener(@NonNull @CecSettingName String name,
682                                        SettingChangeListener listener) {
683         registerChangeListener(name, listener, ConcurrentUtils.DIRECT_EXECUTOR);
684     }
685 
686     /**
687      * Register change listener for a given setting name and executor.
688      */
registerChangeListener(@onNull @ecSettingName String name, SettingChangeListener listener, Executor executor)689     public void registerChangeListener(@NonNull @CecSettingName String name,
690                                        SettingChangeListener listener,
691                                        Executor executor) {
692         Setting setting = getSetting(name);
693         if (setting == null) {
694             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
695         }
696         @Storage int storage = getStorage(setting);
697         if (storage != STORAGE_GLOBAL_SETTINGS && storage != STORAGE_SHARED_PREFS) {
698             throw new IllegalArgumentException("Change listeners for setting '" + name
699                     + "' not supported.");
700         }
701         synchronized (mLock) {
702             if (!mSettingChangeListeners.containsKey(setting)) {
703                 mSettingChangeListeners.put(setting, new ArrayMap<>());
704             }
705             mSettingChangeListeners.get(setting).put(listener, executor);
706         }
707     }
708 
709     /**
710      * Remove change listener for a given setting name.
711      */
removeChangeListener(@onNull @ecSettingName String name, SettingChangeListener listener)712     public void removeChangeListener(@NonNull @CecSettingName String name,
713                                      SettingChangeListener listener) {
714         Setting setting = getSetting(name);
715         if (setting == null) {
716             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
717         }
718         synchronized (mLock) {
719             if (mSettingChangeListeners.containsKey(setting)) {
720                 ArrayMap<SettingChangeListener, Executor> listeners =
721                         mSettingChangeListeners.get(setting);
722                 listeners.remove(listener);
723                 if (listeners.isEmpty()) {
724                     mSettingChangeListeners.remove(setting);
725                 }
726             }
727         }
728     }
729 
730     /**
731      * Returns a list of all settings based on the XML metadata.
732      */
getAllSettings()733     public @CecSettingName List<String> getAllSettings() {
734         return new ArrayList<>(mSettings.keySet());
735     }
736 
737     /**
738      * Returns a list of user-modifiable settings based on the XML metadata.
739      */
getUserSettings()740     public @CecSettingName List<String> getUserSettings() {
741         List<String> settings = new ArrayList<>();
742         for (Setting setting: mSettings.values()) {
743             if (setting.getUserConfigurable()) {
744                 settings.add(setting.getName());
745             }
746         }
747         return settings;
748     }
749 
750     /**
751      * For a given setting name returns true if and only if the value type of that
752      * setting is a string.
753      */
isStringValueType(@onNull @ecSettingName String name)754     public boolean isStringValueType(@NonNull @CecSettingName String name) {
755         Setting setting = getSetting(name);
756         if (setting == null) {
757             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
758         }
759         return getSetting(name).getValueType().equals(VALUE_TYPE_STRING);
760     }
761 
762     /**
763      * For a given setting name returns true if and only if the value type of that
764      * setting is an int.
765      */
isIntValueType(@onNull @ecSettingName String name)766     public boolean isIntValueType(@NonNull @CecSettingName String name) {
767         Setting setting = getSetting(name);
768         if (setting == null) {
769             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
770         }
771         return getSetting(name).getValueType().equals(VALUE_TYPE_INT);
772     }
773 
774     /**
775      * For a given setting name returns values that are allowed for that setting (string).
776      */
getAllowedStringValues(@onNull @ecSettingName String name)777     public List<String> getAllowedStringValues(@NonNull @CecSettingName String name) {
778         Setting setting = getSetting(name);
779         if (setting == null) {
780             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
781         }
782         if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
783             throw new IllegalArgumentException("Setting '" + name
784                     + "' is not a string-type setting.");
785         }
786         List<String> allowedValues = new ArrayList<String>();
787         for (Value allowedValue : setting.getAllowedValues()) {
788             allowedValues.add(allowedValue.getStringValue());
789         }
790         return allowedValues;
791     }
792 
793     /**
794      * For a given setting name returns values that are allowed for that setting (string).
795      */
getAllowedIntValues(@onNull @ecSettingName String name)796     public List<Integer> getAllowedIntValues(@NonNull @CecSettingName String name) {
797         Setting setting = getSetting(name);
798         if (setting == null) {
799             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
800         }
801         if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
802             throw new IllegalArgumentException("Setting '" + name
803                     + "' is not a string-type setting.");
804         }
805         List<Integer> allowedValues = new ArrayList<Integer>();
806         for (Value allowedValue : setting.getAllowedValues()) {
807             allowedValues.add(allowedValue.getIntValue());
808         }
809         return allowedValues;
810     }
811 
812     /**
813      * For a given setting name returns the default value for that setting (string).
814      */
getDefaultStringValue(@onNull @ecSettingName String name)815     public String getDefaultStringValue(@NonNull @CecSettingName String name) {
816         Setting setting = getSetting(name);
817         if (setting == null) {
818             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
819         }
820         if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
821             throw new IllegalArgumentException("Setting '" + name
822                     + "' is not a string-type setting.");
823         }
824         return getSetting(name).getDefaultValue().getStringValue();
825     }
826 
827     /**
828      * For a given setting name returns the default value for that setting (int).
829      */
getDefaultIntValue(@onNull @ecSettingName String name)830     public int getDefaultIntValue(@NonNull @CecSettingName String name) {
831         Setting setting = getSetting(name);
832         if (setting == null) {
833             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
834         }
835         if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
836             throw new IllegalArgumentException("Setting '" + name
837                     + "' is not a string-type setting.");
838         }
839         return getSetting(name).getDefaultValue().getIntValue();
840     }
841 
842     /**
843      * For a given setting name returns the current value of that setting (string).
844      */
getStringValue(@onNull @ecSettingName String name)845     public String getStringValue(@NonNull @CecSettingName String name) {
846         Setting setting = getSetting(name);
847         if (setting == null) {
848             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
849         }
850         if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
851             throw new IllegalArgumentException("Setting '" + name
852                     + "' is not a string-type setting.");
853         }
854         HdmiLogger.debug("Getting CEC setting value '" + name + "'.");
855         return retrieveValue(setting, setting.getDefaultValue().getStringValue());
856     }
857 
858     /**
859      * For a given setting name returns the current value of that setting (int).
860      */
getIntValue(@onNull @ecSettingName String name)861     public int getIntValue(@NonNull @CecSettingName String name) {
862         Setting setting = getSetting(name);
863         if (setting == null) {
864             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
865         }
866         if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
867             throw new IllegalArgumentException("Setting '" + name
868                     + "' is not a int-type setting.");
869         }
870         HdmiLogger.debug("Getting CEC setting value '" + name + "'.");
871         String defaultValue = Integer.toString(setting.getDefaultValue().getIntValue());
872         String value = retrieveValue(setting, defaultValue);
873         return Integer.parseInt(value);
874     }
875 
876     /**
877      * For a given setting name and value sets the current value of that setting (string).
878      */
setStringValue(@onNull @ecSettingName String name, @NonNull String value)879     public void setStringValue(@NonNull @CecSettingName String name, @NonNull String value) {
880         Setting setting = getSetting(name);
881         if (setting == null) {
882             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
883         }
884         if (!setting.getUserConfigurable()) {
885             throw new IllegalArgumentException("Updating CEC setting '" + name + "' prohibited.");
886         }
887         if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
888             throw new IllegalArgumentException("Setting '" + name
889                     + "' is not a string-type setting.");
890         }
891         if (!getAllowedStringValues(name).contains(value)) {
892             throw new IllegalArgumentException("Invalid CEC setting '" + name
893                                                + "' value: '" + value + "'.");
894         }
895         HdmiLogger.debug("Updating CEC setting '" + name + "' to '" + value + "'.");
896         storeValue(setting, value);
897     }
898 
899     /**
900      * For a given setting name and value sets the current value of that setting (int).
901      */
setIntValue(@onNull @ecSettingName String name, int value)902     public void setIntValue(@NonNull @CecSettingName String name, int value) {
903         Setting setting = getSetting(name);
904         if (setting == null) {
905             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
906         }
907         if (!setting.getUserConfigurable()) {
908             throw new IllegalArgumentException("Updating CEC setting '" + name + "' prohibited.");
909         }
910         if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
911             throw new IllegalArgumentException("Setting '" + name
912                     + "' is not a int-type setting.");
913         }
914         if (!getAllowedIntValues(name).contains(value)) {
915             throw new IllegalArgumentException("Invalid CEC setting '" + name
916                                                + "' value: '" + value + "'.");
917         }
918         HdmiLogger.debug("Updating CEC setting '" + name + "' to '" + value + "'.");
919         storeValue(setting, Integer.toString(value));
920     }
921 }
922