1 /* 2 * Copyright (C) 2015 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 package com.android.tv.settings.device.displaysound 17 18 import android.app.tvsettings.TvSettingsEnums 19 import android.content.ContentResolver 20 import android.content.Context 21 import android.hardware.display.DisplayManager 22 import android.hardware.hdmi.HdmiControlManager 23 import android.media.AudioManager 24 import android.os.Bundle 25 import android.provider.Settings 26 import android.text.TextUtils 27 import android.view.Display 28 import android.view.LayoutInflater 29 import android.view.View 30 import android.view.ViewGroup 31 import androidx.annotation.Keep 32 import androidx.annotation.VisibleForTesting 33 import androidx.lifecycle.Lifecycle 34 import androidx.lifecycle.lifecycleScope 35 import androidx.lifecycle.repeatOnLifecycle 36 import androidx.preference.Preference 37 import androidx.preference.SwitchPreference 38 import androidx.preference.TwoStatePreference 39 import com.android.tv.settings.R 40 import com.android.tv.settings.SettingsPreferenceFragment 41 import com.android.tv.settings.device.displaysound.PreferredDynamicRangeInfo.MatchContentDynamicRangeInfoFragment 42 import com.android.tv.settings.overlay.FlavorUtils 43 import com.android.tv.settings.util.InstrumentationUtils 44 import com.android.tv.settings.util.ResolutionSelectionUtils 45 import com.android.tv.settings.util.SliceUtils 46 import com.android.tv.settings.util.SliceUtilsKt 47 import com.android.tv.twopanelsettings.slices.SlicePreference 48 import kotlinx.coroutines.launch 49 50 /** 51 * The "Display & sound" screen in TV Settings. 52 */ 53 @Keep 54 class DisplaySoundFragment : SettingsPreferenceFragment(), DisplayManager.DisplayListener { 55 lateinit var mAudioManager: AudioManager 56 lateinit var mHdmiControlManager: HdmiControlManager 57 lateinit var mDisplayManager: DisplayManager 58 private var mCurrentMode: Display.Mode? = null 59 onAttachnull60 override fun onAttach(context: Context) { 61 mAudioManager = context.getSystemService(AudioManager::class.java) as AudioManager 62 mHdmiControlManager = 63 context.getSystemService(HdmiControlManager::class.java) as HdmiControlManager 64 super.onAttach(context) 65 } 66 67 private val preferenceScreenResId: Int 68 get() = when (FlavorUtils.getFlavor(context)) { 69 FlavorUtils.FLAVOR_CLASSIC, FlavorUtils.FLAVOR_TWO_PANEL -> R.xml.display_sound 70 FlavorUtils.FLAVOR_X, FlavorUtils.FLAVOR_VENDOR -> R.xml.display_sound_x 71 else -> R.xml.display_sound 72 } 73 onCreatePreferencesnull74 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 75 setPreferencesFromResource(preferenceScreenResId, null) 76 } 77 onCreateViewnull78 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 79 savedInstanceState: Bundle?): View { 80 findPreference<TwoStatePreference>(KEY_SOUND_EFFECTS)?.isChecked = soundEffectsEnabled 81 updateCecPreference() 82 mDisplayManager = displayManager 83 val display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY) 84 if (display.systemPreferredDisplayMode != null) { 85 mDisplayManager.registerDisplayListener(this, null) 86 mCurrentMode = mDisplayManager.globalUserPreferredDisplayMode 87 updateResolutionTitleDescription(ResolutionSelectionUtils.modeToString( 88 mCurrentMode, context)) 89 } else { 90 removePreference(findPreference(KEY_RESOLUTION_TITLE)) 91 } 92 val dynamicRangePreference = findPreference<SwitchPreference>(KEY_DYNAMIC_RANGE) 93 if (mDisplayManager.supportedHdrOutputTypes.isEmpty()) { 94 removePreference(dynamicRangePreference) 95 } else if (FlavorUtils.getFlavor(context) != FlavorUtils.FLAVOR_CLASSIC) { 96 createInfoFragments() 97 } 98 viewLifecycleOwner.lifecycleScope.launch { 99 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { 100 updateDefaultAudioOutputSettings() 101 } 102 } 103 return checkNotNull(super.onCreateView(inflater, container, savedInstanceState)) 104 } 105 onDestroynull106 override fun onDestroy() { 107 super.onDestroy() 108 mDisplayManager.unregisterDisplayListener(this) 109 } 110 onResumenull111 override fun onResume() { 112 super.onResume() 113 // Update the subtitle of CEC setting when navigating back to this page. 114 updateCecPreference() 115 findPreference<SwitchPreference>(KEY_DYNAMIC_RANGE)?.isChecked = 116 DisplaySoundUtils.getMatchContentDynamicRangeStatus(mDisplayManager) 117 } 118 onPreferenceTreeClicknull119 override fun onPreferenceTreeClick(preference: Preference): Boolean { 120 if (TextUtils.equals(preference.key, KEY_SOUND_EFFECTS)) { 121 val soundPref = preference as TwoStatePreference 122 InstrumentationUtils 123 .logToggleInteracted( 124 TvSettingsEnums.DISPLAY_SOUND_SYSTEM_SOUNDS, soundPref.isChecked) 125 soundEffectsEnabled = soundPref.isChecked 126 } else if (TextUtils.equals(preference.key, KEY_DYNAMIC_RANGE)) { 127 val dynamicPref = preference as SwitchPreference 128 DisplaySoundUtils 129 .setMatchContentDynamicRangeStatus(context, mDisplayManager, dynamicPref.isChecked) 130 } 131 return super.onPreferenceTreeClick(preference) 132 } 133 134 private var soundEffectsEnabled: Boolean 135 get() = getSoundEffectsEnabled(requireActivity().contentResolver) 136 private set(enabled) { 137 if (enabled) { 138 mAudioManager.loadSoundEffects() 139 } else { 140 mAudioManager.unloadSoundEffects() 141 } 142 Settings.System.putInt(requireActivity().contentResolver, 143 Settings.System.SOUND_EFFECTS_ENABLED, if (enabled) 1 else 0) 144 } 145 updateCecPreferencenull146 private fun updateCecPreference() { 147 findPreference<Preference>(KEY_CEC)?.apply{ 148 if (this is SlicePreference && SliceUtils.isSliceProviderValid( 149 context, this.uri)) { 150 val cecEnabled = (mHdmiControlManager.getHdmiCecEnabled() 151 == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) 152 setSummary(if (cecEnabled) R.string.enabled else R.string.disabled) 153 isVisible = true 154 } else { 155 isVisible = false 156 } 157 } 158 } 159 updateDefaultAudioOutputSettingsnull160 private suspend fun updateDefaultAudioOutputSettings() { 161 findPreference<SlicePreference>(KEY_DEFAULT_AUDIO_OUTPUT_SETTINGS_SLICE)?.apply { 162 isVisible = SliceUtils.isSliceProviderValid(context, 163 this.uri) 164 && SliceUtilsKt.isSettingsSliceEnabled(context, 165 this.uri, null) 166 } 167 } 168 getPageIdnull169 override fun getPageId(): Int { 170 return TvSettingsEnums.DISPLAY_SOUND 171 } 172 onDisplayAddednull173 override fun onDisplayAdded(displayId: Int) {} onDisplayRemovednull174 override fun onDisplayRemoved(displayId: Int) {} onDisplayChangednull175 override fun onDisplayChanged(displayId: Int) { 176 val newMode = mDisplayManager.globalUserPreferredDisplayMode 177 if (mCurrentMode != newMode) { 178 updateResolutionTitleDescription( 179 ResolutionSelectionUtils.modeToString(newMode, context)) 180 mCurrentMode = newMode 181 } 182 } 183 184 @get:VisibleForTesting 185 val displayManager: DisplayManager 186 get() = requireContext().getSystemService(DisplayManager::class.java) as DisplayManager 187 updateResolutionTitleDescriptionnull188 private fun updateResolutionTitleDescription(summary: String) { 189 findPreference<Preference>(KEY_RESOLUTION_TITLE)?.summary = summary 190 } 191 removePreferencenull192 private fun removePreference(preference: Preference?) { 193 if (preference != null) { 194 preferenceScreen.removePreference(preference) 195 } 196 } 197 createInfoFragmentsnull198 private fun createInfoFragments() { 199 findPreference<Preference>(KEY_DYNAMIC_RANGE)?.fragment = 200 MatchContentDynamicRangeInfoFragment::class.java.name 201 } 202 203 companion object { 204 const val KEY_SOUND_EFFECTS = "sound_effects" 205 private const val KEY_CEC = "cec" 206 private const val KEY_DEFAULT_AUDIO_OUTPUT_SETTINGS_SLICE = "default_audio_output_settings" 207 private const val KEY_RESOLUTION_TITLE = "resolution_selection" 208 private const val KEY_DYNAMIC_RANGE = "match_content_dynamic_range" newInstancenull209 fun newInstance(): DisplaySoundFragment { 210 return DisplaySoundFragment() 211 } 212 getSoundEffectsEnablednull213 fun getSoundEffectsEnabled(contentResolver: ContentResolver?): Boolean { 214 return (Settings.System.getInt(contentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 1) 215 != 0) 216 } 217 } 218 } 219