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