1 /* 2 * Copyright (C) 2018 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 17 18 import android.app.tvsettings.TvSettingsEnums 19 import android.content.Context 20 import android.content.Intent 21 import android.content.pm.PackageManager 22 import android.content.res.Resources 23 import android.media.AudioManager 24 import android.media.tv.TvInputManager 25 import android.os.Bundle 26 import android.os.UserHandle 27 import android.provider.Settings 28 import android.text.TextUtils 29 import android.util.Log 30 import android.view.LayoutInflater 31 import android.view.View 32 import android.view.ViewGroup 33 import androidx.annotation.Keep 34 import androidx.annotation.VisibleForTesting 35 import androidx.leanback.preference.LeanbackSettingsFragmentCompat 36 import androidx.lifecycle.Lifecycle 37 import androidx.lifecycle.lifecycleScope 38 import androidx.lifecycle.repeatOnLifecycle 39 import androidx.preference.Preference 40 import androidx.preference.TwoStatePreference 41 import com.android.settingslib.development.DevelopmentSettingsEnabler 42 import com.android.tv.settings.LongClickPreference 43 import com.android.tv.settings.MainFragment 44 import com.android.tv.settings.R 45 import com.android.tv.settings.SettingsPreferenceFragment 46 import com.android.tv.settings.about.RebootConfirmFragment 47 import com.android.tv.settings.autofill.AutofillHelper 48 import com.android.tv.settings.customization.CustomizationConstants 49 import com.android.tv.settings.customization.Partner 50 import com.android.tv.settings.customization.PartnerPreferencesMerger 51 import com.android.tv.settings.device.eco.PowerAndEnergyFragment 52 import com.android.tv.settings.inputmethod.InputMethodHelper 53 import com.android.tv.settings.overlay.FlavorUtils 54 import com.android.tv.settings.privacy.PrivacyToggle 55 import com.android.tv.settings.privacy.SensorFragment 56 import com.android.tv.settings.system.SecurityFragment 57 import com.android.tv.settings.util.InstrumentationUtils 58 import com.android.tv.settings.util.SliceUtils 59 import com.android.tv.settings.util.SliceUtilsKt 60 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment 61 import com.android.tv.twopanelsettings.slices.SlicePreference 62 import kotlinx.coroutines.launch 63 64 /** 65 * The "Device Preferences" screen in TV settings. 66 */ 67 @Keep 68 class DevicePrefFragment : SettingsPreferenceFragment(), LongClickPreference.OnLongClickListener { 69 private var mSoundsSwitchPref: TwoStatePreference? = null 70 private var mInputSettingNeeded = false 71 private var mAudioManager: AudioManager? = null 72 private val preferenceScreenResId: Int 73 get() = if (isRestricted) { 74 R.xml.device_restricted 75 } else when (FlavorUtils.getFlavor(context)) { 76 FlavorUtils.FLAVOR_CLASSIC -> R.xml.device 77 FlavorUtils.FLAVOR_TWO_PANEL -> R.xml.device_two_panel 78 FlavorUtils.FLAVOR_X -> R.xml.device_x 79 FlavorUtils.FLAVOR_VENDOR -> R.xml.device_vendor 80 else -> R.xml.device 81 } 82 onCreatenull83 override fun onCreate(savedInstanceState: Bundle?) { 84 val manager = requireContext().getSystemService( 85 Context.TV_INPUT_SERVICE) as TvInputManager 86 for (input in manager.tvInputList) { 87 if (input.isPassthroughInput) { 88 mInputSettingNeeded = true 89 } 90 } 91 mAudioManager = requireContext().getSystemService(AudioManager::class.java) as AudioManager 92 super.onCreate(savedInstanceState) 93 } 94 onCreatePreferencesnull95 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 96 setPreferencesFromResource(preferenceScreenResId, null) 97 if (Partner.getInstance(context).isCustomizationPackageProvided) { 98 PartnerPreferencesMerger.mergePreferences( 99 context, 100 preferenceScreen, 101 CustomizationConstants.DEVICE_SCREEN 102 ) 103 } 104 mSoundsSwitchPref = findPreference(KEY_SOUNDS_SWITCH) 105 } onCreateViewnull106 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 107 savedInstanceState: Bundle?): View { 108 mSoundsSwitchPref?.isChecked = soundEffectsEnabled 109 findPreference<Preference>(KEY_INPUTS)?.isVisible = mInputSettingNeeded 110 findPreference<LongClickPreference>(KEY_REBOOT)?.setLongClickListener(this) 111 PrivacyToggle.MIC_TOGGLE.preparePreferenceWithSensorFragment(context, 112 findPreference(KEY_MIC), SensorFragment.TOGGLE_EXTRA) 113 PrivacyToggle.CAMERA_TOGGLE.preparePreferenceWithSensorFragment(context, 114 findPreference(KEY_CAMERA), SensorFragment.TOGGLE_EXTRA) 115 updateDeveloperOptions() 116 updateSounds() 117 updateGoogleSettings() 118 updateCastSettings() 119 updateFastpairSettings() 120 updateKeyboardAutofillSettings() 121 updateAmbientSettings() 122 updatePowerAndEnergySettings() 123 updateSystemTvSettings() 124 hideIfIntentUnhandled(findPreference(KEY_HOME_SETTINGS)) 125 hideIfIntentUnhandled(findPreference(KEY_CAST_SETTINGS)) 126 hideIfIntentUnhandled(findPreference(KEY_USAGE)) 127 viewLifecycleOwner.lifecycleScope.launch { 128 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { 129 updateInternalSettings() 130 updateAssistantBroadcastSlice() 131 } 132 } 133 return checkNotNull(super.onCreateView(inflater, container, savedInstanceState)) 134 } 135 onPreferenceTreeClicknull136 override fun onPreferenceTreeClick(preference: Preference): Boolean { 137 when (preference.key) { 138 KEY_HOME_SETTINGS -> InstrumentationUtils.logEntrySelected(TvSettingsEnums.PREFERENCES_HOME_SCREEN) 139 KEY_GOOGLE_SETTINGS -> InstrumentationUtils.logEntrySelected(TvSettingsEnums.PREFERENCES_ASSISTANT) 140 KEY_CAST_SETTINGS -> InstrumentationUtils.logEntrySelected(TvSettingsEnums.PREFERENCES_CHROMECAST_SHELL) 141 KEY_REBOOT -> InstrumentationUtils.logEntrySelected(TvSettingsEnums.SYSTEM_REBOOT) 142 KEY_SOUNDS_SWITCH -> 143 mSoundsSwitchPref?.let { 144 InstrumentationUtils.logToggleInteracted(TvSettingsEnums.DISPLAY_SOUND_SYSTEM_SOUNDS, 145 it.isChecked) 146 soundEffectsEnabled = it.isChecked 147 } 148 } 149 return super.onPreferenceTreeClick(preference) 150 } 151 onPreferenceLongClicknull152 override fun onPreferenceLongClick(preference: Preference): Boolean { 153 if (TextUtils.equals(preference.key, KEY_REBOOT)) { 154 InstrumentationUtils.logEntrySelected(TvSettingsEnums.SYSTEM_REBOOT) 155 val fragment = callbackFragment 156 if (fragment is LeanbackSettingsFragmentCompat) { 157 fragment.startImmersiveFragment( 158 RebootConfirmFragment.newInstance(true /* safeMode */)) 159 return true 160 } else if (fragment is TwoPanelSettingsFragment) { 161 fragment.startImmersiveFragment( 162 RebootConfirmFragment.newInstance(true /* safeMode */)) 163 return true 164 } 165 } 166 return false 167 } 168 169 private var soundEffectsEnabled: Boolean 170 get() = Settings.System.getInt(requireActivity().contentResolver, 171 Settings.System.SOUND_EFFECTS_ENABLED, 1) != 0 172 private set(enabled) { 173 if (enabled) { 174 mAudioManager?.loadSoundEffects() 175 } else { 176 mAudioManager?.unloadSoundEffects() 177 } 178 Settings.System.putInt(requireActivity().contentResolver, 179 Settings.System.SOUND_EFFECTS_ENABLED, if (enabled) 1 else 0) 180 } 181 hideIfIntentUnhandlednull182 private fun hideIfIntentUnhandled(preference: Preference?) { 183 if (preference == null || !preference.isVisible) { 184 return 185 } 186 preference.isVisible = MainFragment.systemIntentIsHandled(context, preference.intent) != null 187 } 188 189 private val isRestricted: Boolean 190 get() = SecurityFragment.isRestrictedProfileInEffect(context) 191 192 @VisibleForTesting updateDeveloperOptionsnull193 fun updateDeveloperOptions() { 194 findPreference<Preference>(KEY_DEVELOPER)?.isVisible = 195 DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context) 196 } 197 updateSoundsnull198 private fun updateSounds() { 199 findPreference<Preference>(KEY_SOUNDS)?.isVisible = 200 MainFragment 201 .systemIntentIsHandled(context, Intent(MainFragment.ACTION_SOUND)) == null 202 203 } 204 updateGoogleSettingsnull205 private fun updateGoogleSettings() { 206 findPreference<Preference>(KEY_GOOGLE_SETTINGS)?.apply { 207 val info = MainFragment.systemIntentIsHandled(context, 208 this.intent) 209 isVisible = info != null 210 211 info?.let { 212 icon = it.activityInfo.loadIcon(requireContext().packageManager) 213 title = it.activityInfo.loadLabel(requireContext().packageManager) 214 } 215 } 216 } 217 218 @VisibleForTesting updateCastSettingsnull219 fun updateCastSettings() { 220 findPreference<Preference>(KEY_CAST_SETTINGS)?.apply { 221 val info = MainFragment.systemIntentIsHandled( 222 requireContext(), this.intent) 223 if (info != null) { 224 try { 225 val targetContext = requireContext() 226 .createPackageContext(if (info.resolvePackageName != null) info.resolvePackageName else info.activityInfo.packageName, 0) 227 this.icon = targetContext.getDrawable(info.getIconResource()) 228 } catch (e: Resources.NotFoundException) { 229 Log.e(TAG, "Cast settings icon not found", e) 230 } catch (e: PackageManager.NameNotFoundException) { 231 Log.e(TAG, "Cast settings icon not found", e) 232 } catch (e: SecurityException) { 233 Log.e(TAG, "Cast settings icon not found", e) 234 } 235 title = info.activityInfo.loadLabel(requireContext().packageManager) 236 } 237 } 238 239 findPreference<SlicePreference>(KEY_CAST_SETTINGS_SLICE)?.apply { 240 isVisible = SliceUtils.isSliceProviderValid(requireContext(), this.uri) 241 && !FlavorUtils.getFeatureFactory(requireContext()).getBasicModeFeatureProvider() 242 .isBasicMode(requireContext()) 243 } 244 } 245 updateInternalSettingsnull246 private suspend fun updateInternalSettings() { 247 findPreference<SlicePreference>(KEY_OVERLAY_INTERNAL_SETTINGS_SLICE)?.apply { 248 isVisible = SliceUtils.isSliceProviderValid(context, this.uri) 249 && SliceUtilsKt.isSettingsSliceEnabled(requireContext(), this.uri, null) 250 } 251 } 252 updateAssistantBroadcastSlicenull253 private suspend fun updateAssistantBroadcastSlice() { 254 findPreference<Preference>(KEY_ASSISTANT_BROADCAST)?.apply { 255 isVisible = SliceUtilsKt.isSettingsSliceEnabled( 256 requireContext(), 257 (this as SlicePreference).uri, 258 RES_TOP_LEVEL_ASSISTANT_SLICE_URI) 259 } 260 } 261 262 @VisibleForTesting updateFastpairSettingsnull263 fun updateFastpairSettings() { 264 findPreference<SlicePreference>(KEY_FASTPAIR_SETTINGS_SLICE)?.apply { 265 isVisible = SliceUtils.isSliceProviderValid(context, this.uri) 266 } 267 } 268 269 @VisibleForTesting updateKeyboardAutofillSettingsnull270 fun updateKeyboardAutofillSettings() { 271 val keyboardPref = findPreference<Preference>(KEY_KEYBOARD) 272 val candidates = AutofillHelper.getAutofillCandidates(requireContext(), 273 requireContext().packageManager, UserHandle.myUserId()) 274 275 // Switch title depends on whether there is autofill 276 if (candidates.isEmpty()) { 277 keyboardPref?.setTitle(R.string.system_keyboard) 278 } else { 279 keyboardPref?.setTitle(R.string.system_keyboard_autofill) 280 } 281 var summary: CharSequence = "" 282 // append current keyboard to summary 283 val defaultImId = InputMethodHelper.getDefaultInputMethodId(context) 284 if (!TextUtils.isEmpty(defaultImId)) { 285 val info = InputMethodHelper.findInputMethod(defaultImId, 286 InputMethodHelper.getEnabledSystemInputMethodList(context)) 287 if (info != null) { 288 summary = info.loadLabel(requireContext().packageManager) 289 } 290 } 291 // append current autofill to summary 292 val appInfo = AutofillHelper.getCurrentAutofill(requireContext(), candidates) 293 if (appInfo != null) { 294 val autofillInfo = appInfo.loadLabel() 295 if (summary.length > 0) { 296 requireContext().getString(R.string.string_concat, summary, autofillInfo) 297 } else { 298 summary = autofillInfo 299 } 300 } 301 keyboardPref?.summary = summary 302 } 303 updateAmbientSettingsnull304 private fun updateAmbientSettings() { 305 findPreference<SlicePreference>(KEY_AMBIENT_SETTINGS)?.apply { 306 isVisible = SliceUtils.isSliceProviderValid(context, this.uri) 307 } 308 } 309 updatePowerAndEnergySettingsnull310 private fun updatePowerAndEnergySettings() { 311 val energySaverPref = findPreference<Preference>(KEY_ENERGY_SAVER) 312 val powerAndEnergyPref = findPreference<Preference>(KEY_POWER_AND_ENERGY) 313 if (energySaverPref == null || powerAndEnergyPref == null) { 314 return 315 } 316 val showPowerAndEnergy = !PowerAndEnergyFragment.hasOnlyEnergySaverPreference(context) 317 powerAndEnergyPref.isVisible = showPowerAndEnergy 318 energySaverPref.isVisible = !showPowerAndEnergy 319 } 320 updateSystemTvSettingsnull321 private fun updateSystemTvSettings() { 322 findPreference<SlicePreference>(KEY_SYSTEM_TV_SLICE)?.apply { 323 isVisible = SliceUtils.isSliceProviderValid(context, this.uri) 324 } 325 } 326 getPageIdnull327 override fun getPageId(): Int { 328 return TvSettingsEnums.SYSTEM 329 } 330 331 companion object { 332 @JvmField 333 @VisibleForTesting 334 val KEY_DEVELOPER = "developer" 335 336 @JvmField 337 @VisibleForTesting 338 val KEY_CAST_SETTINGS = "cast" 339 private const val KEY_CAST_SETTINGS_SLICE = "cast_settings" 340 341 @JvmField 342 @VisibleForTesting 343 val KEY_KEYBOARD = "keyboard" 344 private const val TAG = "DeviceFragment" 345 private const val KEY_USAGE = "usageAndDiag" 346 private const val KEY_INPUTS = "inputs" 347 private const val KEY_SOUNDS = "sound_effects" 348 private const val KEY_SOUNDS_SWITCH = "sound_effects_switch" 349 private const val KEY_GOOGLE_SETTINGS = "google_settings" 350 private const val KEY_HOME_SETTINGS = "home" 351 private const val KEY_REBOOT = "reboot" 352 private const val KEY_MIC = "microphone" 353 private const val KEY_CAMERA = "camera" 354 private const val KEY_FASTPAIR_SETTINGS_SLICE = "fastpair_slice" 355 private const val KEY_OVERLAY_INTERNAL_SETTINGS_SLICE = "overlay_internal" 356 private const val KEY_ASSISTANT_BROADCAST = "assistant_broadcast" 357 private const val KEY_AMBIENT_SETTINGS = "ambient_settings" 358 private const val KEY_ENERGY_SAVER = "energysaver" 359 private const val KEY_POWER_AND_ENERGY = "power_and_energy" 360 private const val RES_TOP_LEVEL_ASSISTANT_SLICE_URI = "top_level_assistant_slice_uri" 361 private const val KEY_SYSTEM_TV_SLICE = "menu_system_tv" 362 } 363 } 364