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