• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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
17 
18 import android.accounts.AccountManager
19 import android.app.tvsettings.TvSettingsEnums
20 import android.bluetooth.BluetoothAdapter
21 import android.bluetooth.BluetoothDevice
22 import android.content.BroadcastReceiver
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.content.pm.ApplicationInfo
27 import android.content.pm.PackageManager
28 import android.content.pm.ProviderInfo
29 import android.content.pm.ResolveInfo
30 import android.content.res.Resources
31 import android.graphics.drawable.Drawable
32 import android.icu.text.MessageFormat
33 import android.net.Uri
34 import android.os.Bundle
35 import android.service.settings.suggestions.Suggestion
36 import android.telephony.CellSignalStrength
37 import android.text.TextUtils
38 import android.util.Log
39 import android.util.TypedValue
40 import android.view.ContextThemeWrapper
41 import androidx.annotation.Keep
42 import androidx.annotation.VisibleForTesting
43 import androidx.preference.Preference
44 import androidx.preference.PreferenceCategory
45 import com.android.settingslib.core.AbstractPreferenceController
46 import com.android.settingslib.suggestions.SuggestionControllerMixinCompat
47 import com.android.tv.settings.HotwordSwitchController.HotwordStateListener
48 import com.android.tv.settings.accounts.AccountsFragment
49 import com.android.tv.settings.accounts.AccountsUtil
50 import com.android.tv.settings.connectivity.ActiveNetworkProvider
51 import com.android.tv.settings.connectivity.ConnectivityListener
52 import com.android.tv.settings.connectivity.ConnectivityListenerLite
53 import com.android.tv.settings.customization.CustomizationConstants
54 import com.android.tv.settings.customization.Partner
55 import com.android.tv.settings.customization.PartnerPreferencesMerger
56 import com.android.tv.settings.overlay.FlavorUtils
57 import com.android.tv.settings.suggestions.SuggestionPreference
58 import com.android.tv.settings.system.SecurityFragment
59 import com.android.tv.settings.util.InstrumentationUtils
60 import com.android.tv.settings.util.SliceUtils
61 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment
62 import com.android.tv.twopanelsettings.slices.SlicePreference
63 import com.android.tv.twopanelsettings.slices.SliceShard
64 import com.android.tv.twopanelsettings.slices.compat.Slice
65 import java.util.Locale
66 import java.util.Optional
67 
68 /**
69  * The fragment where all good things begin. Evil is handled elsewhere.
70  */
71 @Keep
72 open class MainFragment : PreferenceControllerFragment(),
73     SuggestionControllerMixinCompat.SuggestionControllerHost,
74     SuggestionPreference.Callback,
75     HotwordStateListener,
76     SliceShard.Callbacks {
77     @VisibleForTesting
78     var mConnectivityListenerOptional: Optional<ConnectivityListener>? = null
79 
80     @VisibleForTesting
81     var mBtAdapter: BluetoothAdapter? = null
82 
83     @VisibleForTesting
84     var mHasBtAccessories: Boolean = false
85 
86     @VisibleForTesting
87     var mHasAccounts: Boolean = false
88 
89     private var mSuggestionQuickSettingPrefsContainer: SuggestionQuickSettingPrefsContainer? = null
90 
91     private val mBCMReceiver: BroadcastReceiver = object : BroadcastReceiver() {
92         override fun onReceive(context: Context, intent: Intent) {
93             updateAccessoryPref()
94         }
95     }
96 
97     private var mConnectivityListenerLite: ConnectivityListenerLite? = null
98 
99     private var mSliceShard : SliceShard? = null
100 
101     override fun getPreferenceScreenResId(): Int {
102         return when (FlavorUtils.getFlavor(context)) {
103             FlavorUtils.FLAVOR_CLASSIC, FlavorUtils.FLAVOR_TWO_PANEL -> R.xml.main_prefs
104             FlavorUtils.FLAVOR_X -> R.xml.main_prefs_x
105             FlavorUtils.FLAVOR_VENDOR -> R.xml.main_prefs_vendor
106             else -> R.xml.main_prefs
107         }
108     }
109 
110     override fun onAttach(context: Context) {
111         mSuggestionQuickSettingPrefsContainer = SuggestionQuickSettingPrefsContainer(this)
112         super.onAttach(context)
113     }
114 
115     override fun onCreate(savedInstanceState: Bundle?) {
116         mSuggestionQuickSettingPrefsContainer!!.onCreate()
117         if (isWifiScanOptimisationEnabled) {
118             mConnectivityListenerLite = ConnectivityListenerLite(
119                 requireActivity().applicationContext,
120                 { activeNetworkProvider: ActiveNetworkProvider ->
121                     this.updateConnectivityType(
122                         activeNetworkProvider
123                     )
124                 },
125                 lifecycle
126             )
127             mConnectivityListenerOptional = Optional.empty()
128         } else {
129             mConnectivityListenerOptional = Optional.of(
130                 ConnectivityListener(
131                     requireActivity().applicationContext,
132                     { this.updateConnectivity() },
133                     settingsLifecycle
134                 )
135             )
136         }
137         mBtAdapter = BluetoothAdapter.getDefaultAdapter()
138         super.onCreate(savedInstanceState)
139         // This is to record the initial start of Settings root in two panel settings case, as the
140         // MainFragment is the left-most pane and will not be slided in from preview pane. For
141         // classic settings case, the event will be recorded in onResume() as this is an instance
142         // of SettingsPreferenceFragment.
143         if (callbackFragment is TwoPanelSettingsFragment) {
144             InstrumentationUtils.logPageFocused(pageId, true)
145         }
146     }
147 
148     private val isWifiScanOptimisationEnabled: Boolean
149         get() = context!!.resources.getBoolean(R.bool.wifi_scan_optimisation_enabled)
150 
151     private val shouldHideChannelsAndInputs: Boolean
152         get() = context!!.resources.getBoolean(R.bool.config_hide_channels_and_inputs)
153 
154     private fun updateConnectivityType(activeNetworkProvider: ActiveNetworkProvider) {
155         val networkPref = findPreference<Preference>(KEY_NETWORK)
156             ?: return
157 
158         if (activeNetworkProvider.isTypeCellular) {
159             networkPref.setIcon(R.drawable.ic_cell_signal_4_white)
160         } else if (activeNetworkProvider.isTypeEthernet) {
161             networkPref.setIcon(R.drawable.ic_ethernet_white)
162             networkPref.setSummary(R.string.connectivity_summary_ethernet_connected)
163         } else if (activeNetworkProvider.isTypeWifi) {
164             networkPref.setIcon(R.drawable.ic_wifi_signal_4_white)
165             networkPref.summary = activeNetworkProvider.ssid
166         } else {
167             if (activeNetworkProvider.isWifiEnabled) {
168                 networkPref.setIcon(R.drawable.ic_wifi_not_connected)
169                 networkPref.setSummary(R.string.connectivity_summary_no_network_connected)
170             } else {
171                 networkPref.setIcon(R.drawable.ic_wifi_signal_off_white)
172                 networkPref.setSummary(R.string.connectivity_summary_wifi_disabled)
173             }
174         }
175     }
176 
177     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
178         val sliceUri = getString(R.string.main_fragment_slice_uri)
179 
180         if (!SliceUtils.isSliceProviderValid(requireContext(), sliceUri)) {
181             setPreferencesFromResource(preferenceScreenResId, null)
182             configurePreferences()
183         } else {
184             setPreferencesFromResource(R.xml.settings_loading, null)
185 
186             val themeTypedValue = TypedValue()
187             requireContext().theme.resolveAttribute(
188                 com.android.tv.twopanelsettings.R.attr.preferenceTheme,
189                 themeTypedValue,
190                 true
191             )
192             val prefContext = ContextThemeWrapper(activity, themeTypedValue.resourceId)
193             mSliceShard = SliceShard(
194                 this, sliceUri, this,
195                 getString(R.string.settings_app_name), prefContext, true
196             )
197         }
198     }
199 
200     override fun onSlice(slice: Slice?) {
201         if (slice == null) {
202             setPreferencesFromResource(preferenceScreenResId, null)
203         }
204         configurePreferences()
205     }
206 
207     private fun configurePreferences() {
208         if (Partner.getInstance(context).isCustomizationPackageProvided) {
209             PartnerPreferencesMerger.mergePreferences(
210                 context,
211                 preferenceScreen,
212                 CustomizationConstants.MAIN_SCREEN
213             )
214         }
215         if (isRestricted) {
216             val appPref = findPreference<Preference>(KEY_APPLICATIONS)
217             if (appPref != null) {
218                 appPref.isVisible = false
219             }
220             val accountsPref = findPreference<Preference>(
221                 KEY_ACCOUNTS_AND_SIGN_IN
222             )
223             if (accountsPref != null) {
224                 accountsPref.isVisible = false
225             }
226         }
227         if (!supportBluetooth()) {
228             val accessoryPreference = findPreference<Preference>(KEY_ACCESSORIES)
229             if (accessoryPreference != null) {
230                 accessoryPreference.isVisible = false
231             }
232         }
233         if (FlavorUtils.isTwoPanel(context)) {
234             val displaySoundPref = findPreference<Preference>(
235                 KEY_DISPLAY_AND_SOUND
236             )
237             if (displaySoundPref != null) {
238                 displaySoundPref.isVisible = true
239             }
240             val privacyPref = findPreference<Preference>(KEY_PRIVACY)
241             if (privacyPref != null) {
242                 privacyPref.isVisible = true
243             }
244         }
245         mSuggestionQuickSettingPrefsContainer!!.onCreatePreferences()
246         updateSoundSettings()
247         SliceUtils.maybeUseSlice(
248             findPreference(KEY_DISPLAY_AND_SOUND),
249             findPreference(KEY_DISPLAY_AND_SOUND_SLICE)
250         )
251         mSuggestionQuickSettingPrefsContainer!!.showOrHideQuickSettings()
252         updateAccountPref()
253         updateAccessoryPref()
254         updateBasicModeSuggestion()
255         updateMonitorPref()
256 
257         val sliceInputsPreference = findPreference<SlicePreference>(
258             KEY_CHANNELS_AND_INPUTS_SLICE
259         )
260         val channelsAndInputsPreference = findPreference<Preference>(KEY_CHANNELS_AND_INPUTS)
261         if (shouldHideChannelsAndInputs) {
262             if (sliceInputsPreference != null) {
263                 sliceInputsPreference.setVisible(false)
264             }
265             if (channelsAndInputsPreference != null) {
266                 channelsAndInputsPreference.setVisible(false)
267             }
268         } else {
269             if (sliceInputsPreference != null
270                 && !SliceUtils.isSliceProviderValid(
271                     requireContext(), sliceInputsPreference.uri
272                 )
273             ) {
274                 sliceInputsPreference.uri = getString(R.string.channels_and_inputs_fallback_slice_uri)
275             }
276             SliceUtils.maybeUseSlice(findPreference(KEY_CHANNELS_AND_INPUTS), sliceInputsPreference)
277         }
278 
279         SliceUtils.maybeUseSlice(
280             findPreference(KEY_HELP_AND_FEEDBACK),
281             findPreference(KEY_HELP_AND_FEEDBACK_SLICE)
282         )
283     }
284 
285     @VisibleForTesting
286     fun updateConnectivity() {
287         if (!mConnectivityListenerOptional!!.isPresent) {
288             return
289         }
290         val networkPref = findPreference<Preference>(KEY_NETWORK)
291             ?: return
292 
293         if (mConnectivityListenerOptional!!.get().isCellConnected) {
294             val signal = mConnectivityListenerOptional!!.get().cellSignalStrength
295             when (signal) {
296                 CellSignalStrength.SIGNAL_STRENGTH_GREAT -> networkPref.setIcon(
297                     R.drawable.ic_cell_signal_4_white
298                 )
299 
300                 CellSignalStrength.SIGNAL_STRENGTH_GOOD -> networkPref.setIcon(R.drawable.ic_cell_signal_3_white)
301                 CellSignalStrength.SIGNAL_STRENGTH_MODERATE -> networkPref.setIcon(
302                     R.drawable.ic_cell_signal_2_white
303                 )
304 
305                 CellSignalStrength.SIGNAL_STRENGTH_POOR -> networkPref.setIcon(R.drawable.ic_cell_signal_1_white)
306                 CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN -> networkPref.setIcon(
307                     R.drawable.ic_cell_signal_0_white
308                 )
309 
310                 else -> networkPref.setIcon(R.drawable.ic_cell_signal_0_white)
311             }
312         } else if (mConnectivityListenerOptional!!.get().isEthernetConnected) {
313             networkPref.setIcon(R.drawable.ic_ethernet_white)
314             networkPref.setSummary(R.string.connectivity_summary_ethernet_connected)
315         } else if (mConnectivityListenerOptional!!.get().isWifiEnabledOrEnabling) {
316             if (mConnectivityListenerOptional!!.get().isWifiConnected) {
317                 val signal = mConnectivityListenerOptional!!.get().getWifiSignalStrength(5)
318                 when (signal) {
319                     4 -> networkPref.setIcon(R.drawable.ic_wifi_signal_4_white)
320                     3 -> networkPref.setIcon(R.drawable.ic_wifi_signal_3_white)
321                     2 -> networkPref.setIcon(R.drawable.ic_wifi_signal_2_white)
322                     1 -> networkPref.setIcon(R.drawable.ic_wifi_signal_1_white)
323                     0 -> networkPref.setIcon(R.drawable.ic_wifi_signal_0_white)
324                     else -> networkPref.setIcon(R.drawable.ic_wifi_signal_0_white)
325                 }
326                 networkPref.summary = mConnectivityListenerOptional!!.get().ssid
327             } else {
328                 networkPref.setIcon(R.drawable.ic_wifi_not_connected)
329                 networkPref.setSummary(R.string.connectivity_summary_no_network_connected)
330             }
331         } else {
332             networkPref.setIcon(R.drawable.ic_wifi_signal_off_white)
333             networkPref.setSummary(R.string.connectivity_summary_wifi_disabled)
334         }
335     }
336 
337     @VisibleForTesting
338     fun updateSoundSettings() {
339         val soundPref = findPreference<Preference>(KEY_SOUND)
340         if (soundPref != null) {
341             val soundIntent = Intent(ACTION_SOUND)
342             val info = systemIntentIsHandled(
343                 context, soundIntent
344             )
345             soundPref.isVisible = info != null
346             if (info?.activityInfo != null) {
347                 val pkgName = info.activityInfo.packageName
348                 val icon = getDrawableResource(pkgName, "sound_icon")
349                 if (icon != null) {
350                     soundPref.icon = icon
351                 }
352                 val title = getStringResource(pkgName, "sound_pref_title")
353                 if (!TextUtils.isEmpty(title)) {
354                     soundPref.title = title
355                 }
356                 val summary = getStringResource(pkgName, "sound_pref_summary")
357                 if (!TextUtils.isEmpty(summary)) {
358                     soundPref.summary = summary
359                 }
360             }
361         }
362     }
363 
364     /**
365      * Extracts a string resource from a given package.
366      *
367      * @param pkgName  the package name
368      * @param resource name, e.g. "my_string_name"
369      */
370     private fun getStringResource(pkgName: String, resourceName: String): String? {
371         try {
372             val targetContext = context!!.createPackageContext(pkgName, 0)
373             val resId = targetContext.resources.getIdentifier(
374                 "$pkgName:string/$resourceName", null, null
375             )
376             if (resId != 0) {
377                 return targetContext.resources.getString(resId)
378             }
379         } catch (e: Resources.NotFoundException) {
380             Log.w(
381                 TAG,
382                 "Unable to get string resource $resourceName", e
383             )
384         } catch (e: PackageManager.NameNotFoundException) {
385             Log.w(
386                 TAG,
387                 "Unable to get string resource $resourceName", e
388             )
389         } catch (e: SecurityException) {
390             Log.w(
391                 TAG,
392                 "Unable to get string resource $resourceName", e
393             )
394         }
395         return null
396     }
397 
398     /**
399      * Extracts a drawable resource from a given package.
400      *
401      * @param pkgName  the package name
402      * @param resource name, e.g. "my_icon_name"
403      */
404     private fun getDrawableResource(pkgName: String, resourceName: String): Drawable? {
405         try {
406             val targetContext = context!!.createPackageContext(pkgName, 0)
407             val resId = targetContext.resources.getIdentifier(
408                 "$pkgName:drawable/$resourceName", null, null
409             )
410             if (resId != 0) {
411                 return targetContext.resources.getDrawable(resId)
412             }
413         } catch (e: Resources.NotFoundException) {
414             Log.w(
415                 TAG,
416                 "Unable to get drawable resource $resourceName", e
417             )
418         } catch (e: PackageManager.NameNotFoundException) {
419             Log.w(
420                 TAG,
421                 "Unable to get drawable resource $resourceName", e
422             )
423         } catch (e: SecurityException) {
424             Log.w(
425                 TAG,
426                 "Unable to get drawable resource $resourceName", e
427             )
428         }
429         return null
430     }
431 
432     private val isRestricted: Boolean
433         get() = SecurityFragment.isRestrictedProfileInEffect(context)
434 
435     @VisibleForTesting
436     fun updateAccessoryPref() {
437         val connectedDevicesSlicePreference =
438             findPreference<Preference>(KEY_CONNECTED_DEVICES_SLICE) as SlicePreference?
439         val accessoryPreference = findPreference<Preference>(KEY_ACCESSORIES)
440         val connectedDevicesPreference = findPreference<Preference>(
441             KEY_CONNECTED_DEVICES
442         )
443         if (connectedDevicesSlicePreference != null && FlavorUtils.isTwoPanel(
444                 context
445             )
446             && SliceUtils.isSliceProviderValid(
447                 context, connectedDevicesSlicePreference.uri
448             )
449         ) {
450             connectedDevicesSlicePreference.isVisible = true
451             connectedDevicesPreference!!.isVisible = false
452             accessoryPreference!!.isVisible = false
453             val pkgInfo = getProviderInfo(
454                 context!!,
455                 Uri.parse(connectedDevicesSlicePreference.uri).authority!!
456             )
457             if (pkgInfo != null) {
458                 updateConnectedDevicePref(pkgInfo.packageName, connectedDevicesSlicePreference)
459             }
460             return
461         }
462 
463         if (connectedDevicesSlicePreference != null) {
464             connectedDevicesSlicePreference.isVisible = false
465         }
466 
467         if (connectedDevicesPreference != null) {
468             val intent = Intent(ACTION_CONNECTED_DEVICES)
469             val info = systemIntentIsHandled(
470                 context, intent
471             )
472             connectedDevicesPreference.isVisible = info != null
473             accessoryPreference!!.isVisible = info == null
474             if (info != null) {
475                 updateConnectedDevicePref(
476                     info.activityInfo.packageName, connectedDevicesPreference
477                 )
478                 return
479             }
480         }
481         if (mBtAdapter == null || accessoryPreference == null) {
482             return
483         }
484 
485         val bondedDevices = mBtAdapter!!.bondedDevices
486         mHasBtAccessories = bondedDevices!!.size != 0
487     }
488 
489     @VisibleForTesting
490     fun updateAccountPref() {
491         val accountsPref = findPreference<Preference>(KEY_ACCOUNTS_AND_SIGN_IN)
492         val accountsSlicePref =
493             findPreference<Preference>(KEY_ACCOUNTS_AND_SIGN_IN_SLICE) as SlicePreference?
494         val accountsBasicMode = findPreference<Preference>(
495             KEY_ACCOUNTS_AND_SIGN_IN_BASIC_MODE
496         )
497         val intent = Intent(ACTION_ACCOUNTS)
498 
499         when (AccountsUtil.getAccountsFragmentToLaunch(context)) {
500             AccountsUtil.ACCOUNTS_FRAGMENT_RESTRICTED -> {
501                 // Use the bundled AccountsFragment if restriction active
502                 if (accountsBasicMode != null) {
503                     accountsBasicMode.isVisible = false
504                 }
505                 if (accountsSlicePref != null) {
506                     accountsSlicePref.isVisible = false
507                 }
508                 if (accountsPref != null) {
509                     accountsPref.isVisible = true
510                 }
511                 return
512             }
513 
514             AccountsUtil.ACCOUNTS_BASIC_MODE_FRAGMENT -> {
515                 if (accountsBasicMode != null) {
516                     accountsBasicMode.isVisible = true
517                 }
518                 if (accountsPref != null) {
519                     accountsPref.isVisible = false
520                 }
521                 if (accountsSlicePref != null) {
522                     accountsSlicePref.isVisible = false
523                 }
524                 return
525             }
526 
527             AccountsUtil.ACCOUNTS_SYSTEM_INTENT -> {
528                 if (accountsPref != null) {
529                     accountsPref.isVisible = true
530                     accountsPref.fragment = null
531                     accountsPref.intent = intent
532                 }
533                 if (accountsSlicePref != null) {
534                     accountsSlicePref.isVisible = false
535                 }
536                 if (accountsBasicMode != null) {
537                     accountsBasicMode.isVisible = false
538                 }
539                 return
540             }
541 
542             AccountsUtil.ACCOUNTS_SLICE_FRAGMENT -> {
543                 // If a slice is available, use it to display the accounts settings, otherwise
544                 // fall back to use AccountsFragment.
545                 if (accountsPref != null) {
546                     accountsPref.isVisible = false
547                 }
548                 if (accountsSlicePref != null) {
549                     accountsSlicePref.isVisible = true
550                 }
551                 if (accountsBasicMode != null) {
552                     accountsBasicMode.isVisible = false
553                 }
554                 return
555             }
556 
557             AccountsUtil.ACCOUNTS_FRAGMENT_DEFAULT -> {
558                 if (accountsPref != null) {
559                     accountsPref.isVisible = true
560                     updateAccountPrefInfo()
561                 }
562                 if (accountsSlicePref != null) {
563                     accountsSlicePref.isVisible = false
564                 }
565                 if (accountsBasicMode != null) {
566                     accountsBasicMode.isVisible = false
567                 }
568             }
569 
570             else -> {
571                 if (accountsPref != null) {
572                     accountsPref.isVisible = true
573                     updateAccountPrefInfo()
574                 }
575                 if (accountsSlicePref != null) {
576                     accountsSlicePref.isVisible = false
577                 }
578                 if (accountsBasicMode != null) {
579                     accountsBasicMode.isVisible = false
580                 }
581             }
582         }
583     }
584 
585     @VisibleForTesting
586     fun updateAccountPrefInfo() {
587         val accountsPref = findPreference<Preference>(KEY_ACCOUNTS_AND_SIGN_IN)
588         if (accountsPref != null && accountsPref.isVisible) {
589             val am = AccountManager.get(context)
590             val accounts = am.accounts
591             if (accounts.size == 0) {
592                 mHasAccounts = false
593                 accountsPref.setIcon(R.drawable.ic_add_an_account)
594                 accountsPref.setSummary(R.string.accounts_category_summary_no_account)
595                 AccountsFragment.setUpAddAccountPrefIntent(accountsPref, context)
596             } else {
597                 mHasAccounts = true
598                 accountsPref.setIcon(R.drawable.ic_accounts_and_sign_in)
599                 if (accounts.size == 1) {
600                     accountsPref.summary = accounts[0].name
601                 } else {
602                     val msgFormat = MessageFormat(
603                         context!!.resources.getString(
604                             R.string.accounts_category_summary
605                         ),
606                         Locale.getDefault()
607                     )
608                     val arguments: MutableMap<String, Any> = HashMap()
609                     arguments["count"] = accounts.size
610                     accountsPref.summary = msgFormat.format(arguments)
611                 }
612             }
613         }
614     }
615 
616     @VisibleForTesting
617     fun updateBasicModeSuggestion() {
618         val basicModeSuggestion = findPreference<PreferenceCategory>(
619             KEY_BASIC_MODE_SUGGESTION
620         )
621             ?: return
622         if (FlavorUtils.getFeatureFactory(context)
623                 .basicModeFeatureProvider.isBasicMode(context!!)
624         ) {
625             basicModeSuggestion.isVisible = true
626         } else {
627             basicModeSuggestion.isVisible = false
628         }
629     }
630 
631     fun updateMonitorPref() {
632         val monitorSlicePref = findPreference<SlicePreference>(
633             KEY_MONITOR_SLICE
634         )
635             ?: return
636         if (SliceUtils.isSliceProviderValid(context, monitorSlicePref.uri)) {
637             monitorSlicePref.setVisible(true);
638         }
639     }
640 
641     override fun onStart() {
642         super.onStart()
643         updateAccountPref()
644         updateAccessoryPref()
645         val btChangeFilter = IntentFilter()
646         btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
647         btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
648         btChangeFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
649         context!!.registerReceiver(mBCMReceiver, btChangeFilter)
650         if (mConnectivityListenerLite != null) {
651             mConnectivityListenerLite!!.setListener { activeNetworkProvider: ActiveNetworkProvider ->
652                 this.updateConnectivityType(
653                     activeNetworkProvider
654                 )
655             }
656         }
657         mConnectivityListenerOptional!!.ifPresent { connectivityListener: ConnectivityListener -> connectivityListener.setListener { this.updateConnectivity() } }
658     }
659 
660     override fun onResume() {
661         super.onResume()
662         if (isWifiScanOptimisationEnabled) {
663             mConnectivityListenerLite!!.handleConnectivityChange()
664         } else {
665             updateConnectivity()
666         }
667     }
668 
669     override fun onStop() {
670         context!!.unregisterReceiver(mBCMReceiver)
671         if (mConnectivityListenerLite != null) {
672             mConnectivityListenerLite!!.setListener(null)
673         }
674         mConnectivityListenerOptional!!.ifPresent { connectivityListener: ConnectivityListener ->
675             connectivityListener.setListener(
676                 null
677             )
678         }
679         super.onStop()
680     }
681 
682     override fun onPreferenceTreeClick(preference: Preference): Boolean {
683         if ((preference.key == KEY_ACCOUNTS_AND_SIGN_IN && !mHasAccounts
684                     && !AccountsUtil.isAdminRestricted(context))
685             || (preference.key == KEY_ACCESSORIES && !mHasBtAccessories)
686             || (preference.key == KEY_DISPLAY_AND_SOUND
687                     && preference.intent != null)
688             || (preference.key == KEY_CHANNELS_AND_INPUTS
689                     && preference.intent != null)
690         ) {
691             context!!.startActivity(preference.intent)
692             return true
693         } else if (preference.key == KEY_BASIC_MODE_EXIT
694             && FlavorUtils.getFeatureFactory(context)
695                 .basicModeFeatureProvider.isBasicMode(context!!)
696         ) {
697             if (activity != null) {
698                 FlavorUtils.getFeatureFactory(context)
699                     .basicModeFeatureProvider.startBasicModeExitActivity(activity)
700             }
701             return true
702         } else {
703             return super.onPreferenceTreeClick(preference)
704         }
705     }
706 
707     override fun onDestroy() {
708         mSuggestionQuickSettingPrefsContainer!!.onDestroy()
709         super.onDestroy()
710     }
711 
712     override fun onCreatePreferenceControllers(context: Context): List<AbstractPreferenceController> {
713         return mSuggestionQuickSettingPrefsContainer!!.onCreatePreferenceControllers(context)
714     }
715 
716     override fun onSuggestionClosed(preference: Preference) {
717         mSuggestionQuickSettingPrefsContainer!!.onSuggestionClosed(preference)
718     }
719 
720     override fun onSuggestionReady(data: List<Suggestion>) {
721         mSuggestionQuickSettingPrefsContainer!!.onSuggestionReady(data)
722     }
723 
724     override fun onHotwordStateChanged() {
725         mSuggestionQuickSettingPrefsContainer!!.onHotwordStateChanged()
726     }
727 
728     override fun onHotwordEnable() {
729         mSuggestionQuickSettingPrefsContainer!!.onHotwordEnable()
730     }
731 
732     override fun onHotwordDisable() {
733         mSuggestionQuickSettingPrefsContainer!!.onHotwordDisable()
734     }
735 
736     private fun supportBluetooth(): Boolean {
737         return activity.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
738     }
739 
740     private fun updateConnectedDevicePref(pkgName: String, pref: Preference) {
741         val icon = getDrawableResource(pkgName, "connected_devices_pref_icon")
742         if (icon != null) {
743             pref.icon = icon
744         }
745         val title =
746             if ((pref is SlicePreference))
747                 getStringResource(pkgName, "connected_devices_slice_pref_title")
748             else
749                 getStringResource(pkgName, "connected_devices_pref_title")
750         if (!TextUtils.isEmpty(title)) {
751             pref.title = title
752         }
753         val summary = getStringResource(pkgName, "connected_devices_pref_summary")
754         if (!TextUtils.isEmpty(summary)) {
755             pref.summary = summary
756         }
757         pref.onPreferenceClickListener =
758             Preference.OnPreferenceClickListener { preference: Preference? ->
759                 InstrumentationUtils.logEntrySelected(
760                     TvSettingsEnums.CONNECTED_CLASSIC
761                 )
762                 false
763             }
764     }
765 
766     override fun getPageId(): Int {
767         return TvSettingsEnums.TV_SETTINGS_ROOT
768     }
769 
770     override fun setSubtitle(subtitle: CharSequence?) {
771     }
772 
773     override fun setIcon(icon: Drawable?) {
774     }
775 
776     companion object {
777         private const val TAG = "MainFragment"
778         private const val KEY_BASIC_MODE_SUGGESTION = "basic_mode_suggestion"
779         private const val KEY_BASIC_MODE_EXIT = "basic_mode_exit"
780 
781         @VisibleForTesting
782         const val KEY_ACCOUNTS_AND_SIGN_IN: String = "accounts_and_sign_in"
783 
784         @VisibleForTesting
785         const val KEY_ACCOUNTS_AND_SIGN_IN_SLICE: String = "accounts_and_sign_in_slice"
786 
787         @VisibleForTesting
788         const val KEY_ACCOUNTS_AND_SIGN_IN_BASIC_MODE: String = "accounts_and_sign_in_basic_mode"
789         private const val KEY_APPLICATIONS = "applications"
790 
791         @VisibleForTesting
792         const val KEY_ACCESSORIES: String = "remotes_and_accessories"
793 
794         @VisibleForTesting
795         const val KEY_CONNECTED_DEVICES: String = "connected_devices"
796         private const val KEY_CONNECTED_DEVICES_SLICE = "connected_devices_slice"
797 
798         @VisibleForTesting
799         const val KEY_NETWORK: String = "network"
800 
801         @VisibleForTesting
802         const val KEY_SOUND: String = "sound"
803         const val ACTION_SOUND: String = "com.android.tv.settings.SOUND"
804 
805         @VisibleForTesting
806         const val ACTION_CONNECTED_DEVICES: String = "com.android.tv.settings.CONNECTED_DEVICES"
807 
808         @VisibleForTesting
809         const val KEY_PRIVACY: String = "privacy"
810 
811         @VisibleForTesting
812         const val KEY_DISPLAY_AND_SOUND: String = "display_and_sound"
813         private const val KEY_DISPLAY_AND_SOUND_SLICE = "display_and_sound_slice"
814         private const val KEY_CHANNELS_AND_INPUTS = "channels_and_inputs"
815         private const val KEY_CHANNELS_AND_INPUTS_SLICE = "channels_and_inputs_slice"
816 
817         private const val KEY_HELP_AND_FEEDBACK = "help_and_feedback"
818         private const val KEY_HELP_AND_FEEDBACK_SLICE = "help_and_feedback_slice"
819 
820         private const val KEY_MONITOR_SLICE = "monitor_slice"
821 
822         private const val ACTION_ACCOUNTS = "com.android.tv.settings.ACCOUNTS"
823 
824         fun newInstance(): MainFragment {
825             return MainFragment()
826         }
827 
828         /**
829          * Returns the ResolveInfo for the system activity that matches given intent filter or null if
830          * no such activity exists.
831          *
832          * @param context Context of the caller
833          * @param intent  The intent matching the desired system app
834          * @return ResolveInfo of the matching activity or null if no match exists
835          */
836         @JvmStatic
837         fun systemIntentIsHandled(context: Context?, intent: Intent?): ResolveInfo? {
838             if (intent == null || context == null) {
839                 return null
840             }
841 
842             val pm = context.packageManager
843             for (info in pm.queryIntentActivities(intent, 0)) {
844                 if (info.activityInfo != null
845                     && ((info.activityInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM)
846                             == ApplicationInfo.FLAG_SYSTEM)
847                 ) {
848                     return info
849                 }
850             }
851             return null
852         }
853 
854         private fun getProviderInfo(context: Context, authority: String): ProviderInfo? {
855             return context.packageManager.resolveContentProvider(authority, 0)
856         }
857     }
858 }
859