• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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