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