• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.accounts.AuthenticatorDescription;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.content.res.Resources;
33 import android.graphics.drawable.Drawable;
34 import android.media.tv.TvInputInfo;
35 import android.media.tv.TvInputManager;
36 import android.os.Bundle;
37 import android.os.UserHandle;
38 import android.support.v17.preference.LeanbackPreferenceFragment;
39 import android.support.v7.preference.Preference;
40 import android.support.v7.preference.PreferenceGroup;
41 import android.telephony.SignalStrength;
42 import android.text.TextUtils;
43 import android.util.ArraySet;
44 import android.util.Log;
45 
46 import com.android.settingslib.accounts.AuthenticatorHelper;
47 import com.android.tv.settings.accessories.AccessoryUtils;
48 import com.android.tv.settings.accessories.BluetoothAccessoryFragment;
49 import com.android.tv.settings.accounts.AccountSyncFragment;
50 import com.android.tv.settings.accounts.AddAccountWithTypeActivity;
51 import com.android.tv.settings.connectivity.ConnectivityListener;
52 import com.android.tv.settings.device.sound.SoundFragment;
53 import com.android.tv.settings.system.SecurityFragment;
54 
55 import java.util.ArrayList;
56 import java.util.Set;
57 
58 public class MainFragment extends LeanbackPreferenceFragment {
59     private static final String TAG = "MainFragment";
60 
61     private static final String KEY_DEVELOPER = "developer";
62     private static final String KEY_LOCATION = "location";
63     private static final String KEY_SECURITY = "security";
64     private static final String KEY_USAGE = "usageAndDiag";
65     private static final String KEY_ADD_ACCOUNT = "add_account";
66     private static final String KEY_ACCESSORIES = "accessories";
67     private static final String KEY_PERSONAL = "personal";
68     private static final String KEY_ADD_ACCESSORY = "add_accessory";
69     private static final String KEY_NETWORK = "network";
70     private static final String KEY_INPUTS = "inputs";
71     private static final String KEY_SOUNDS = "sound_effects";
72     private static final String KEY_GOOGLE_SETTINGS = "google_settings";
73     private static final String KEY_HOME_SETTINGS = "home";
74     private static final String KEY_CAST_SETTINGS = "cast";
75     private static final String KEY_SPEECH_SETTINGS = "speech";
76     private static final String KEY_SEARCH_SETTINGS = "search";
77     private static final String KEY_ACCOUNTS_CATEGORY = "accounts";
78 
79     private AuthenticatorHelper mAuthenticatorHelper;
80     private BluetoothAdapter mBtAdapter;
81     private ConnectivityListener mConnectivityListener;
82 
83     private boolean mInputSettingNeeded;
84 
85     private Preference mDeveloperPref;
86     private PreferenceGroup mAccessoriesGroup;
87     private PreferenceGroup mAccountsGroup;
88     private Preference mAddAccessory;
89     private Preference mNetworkPref;
90     private Preference mSoundsPref;
91 
92     private final BroadcastReceiver mBCMReceiver = new BroadcastReceiver() {
93         @Override
94         public void onReceive(Context context, Intent intent) {
95             updateAccessories();
96         }
97     };
98 
newInstance()99     public static MainFragment newInstance() {
100         return new MainFragment();
101     }
102 
103     @Override
onCreate(Bundle savedInstanceState)104     public void onCreate(Bundle savedInstanceState) {
105         mAuthenticatorHelper = new AuthenticatorHelper(getContext(),
106                 new UserHandle(UserHandle.myUserId()), userHandle -> updateAccounts());
107         mBtAdapter = BluetoothAdapter.getDefaultAdapter();
108         mConnectivityListener = new ConnectivityListener(getContext(), this::updateWifi);
109 
110         final TvInputManager manager = (TvInputManager) getContext().getSystemService(
111                 Context.TV_INPUT_SERVICE);
112         if (manager != null) {
113             for (final TvInputInfo input : manager.getTvInputList()) {
114                 if (input.isPassthroughInput()) {
115                     mInputSettingNeeded = true;
116                 }
117             }
118         }
119         super.onCreate(savedInstanceState);
120     }
121 
122     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)123     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
124         if (isRestricted()) {
125             setPreferencesFromResource(R.xml.restricted_prefs, null);
126         } else {
127             setPreferencesFromResource(R.xml.main_prefs, null);
128         }
129         mDeveloperPref = findPreference(KEY_DEVELOPER);
130         mAccessoriesGroup = (PreferenceGroup) findPreference(KEY_ACCESSORIES);
131         mAddAccessory = findPreference(KEY_ADD_ACCESSORY);
132         mNetworkPref = findPreference(KEY_NETWORK);
133         mSoundsPref = findPreference(KEY_SOUNDS);
134         mAccountsGroup = (PreferenceGroup) findPreference(KEY_ACCOUNTS_CATEGORY);
135 
136         final Preference inputPref = findPreference(KEY_INPUTS);
137         if (inputPref != null) {
138             inputPref.setVisible(mInputSettingNeeded);
139         }
140     }
141 
142     @Override
onStart()143     public void onStart() {
144         super.onStart();
145         mAuthenticatorHelper.listenToAccountUpdates();
146 
147         IntentFilter btChangeFilter = new IntentFilter();
148         btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
149         btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
150         btChangeFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
151         getContext().registerReceiver(mBCMReceiver, btChangeFilter);
152         mConnectivityListener.start();
153     }
154 
155     @Override
onResume()156     public void onResume() {
157         super.onResume();
158 
159         updateAccounts();
160         updateAccessories();
161         updateDeveloperOptions();
162         updateSounds();
163         updateGoogleSettings();
164 
165         hideIfIntentUnhandled(findPreference(KEY_HOME_SETTINGS));
166         hideIfIntentUnhandled(findPreference(KEY_CAST_SETTINGS));
167         hideIfIntentUnhandled(findPreference(KEY_USAGE));
168         hideIfIntentUnhandled(findPreference(KEY_SPEECH_SETTINGS));
169         hideIfIntentUnhandled(findPreference(KEY_SEARCH_SETTINGS));
170     }
171 
172     @Override
onStop()173     public void onStop() {
174         super.onStop();
175         mAuthenticatorHelper.stopListeningToAccountUpdates();
176         getContext().unregisterReceiver(mBCMReceiver);
177         mConnectivityListener.stop();
178     }
179 
180     @Override
onDestroy()181     public void onDestroy() {
182         super.onDestroy();
183         if (mConnectivityListener != null) {
184             mConnectivityListener.destroy();
185         }
186     }
187 
hideIfIntentUnhandled(Preference preference)188     private void hideIfIntentUnhandled(Preference preference) {
189         if (preference == null) {
190             return;
191         }
192         preference.setVisible(systemIntentIsHandled(getContext(), preference.getIntent()) != null);
193     }
194 
isRestricted()195     private boolean isRestricted() {
196         return SecurityFragment.isRestrictedProfileInEffect(getContext());
197     }
198 
updateAccounts()199     private void updateAccounts() {
200         if (mAccountsGroup == null) {
201             return;
202         }
203 
204         final Set<String> touchedAccounts = new ArraySet<>(mAccountsGroup.getPreferenceCount());
205 
206         final AccountManager am = AccountManager.get(getContext());
207         final AuthenticatorDescription[] authTypes = am.getAuthenticatorTypes();
208         final ArrayList<String> allowableAccountTypes = new ArrayList<>(authTypes.length);
209         final Context themedContext = getPreferenceManager().getContext();
210 
211         for (AuthenticatorDescription authDesc : authTypes) {
212             final Context targetContext;
213             try {
214                 targetContext = getContext().createPackageContext(authDesc.packageName, 0);
215             } catch (PackageManager.NameNotFoundException e) {
216                 Log.e(TAG, "Authenticator description with bad package name", e);
217                 continue;
218             } catch (SecurityException e) {
219                 Log.e(TAG, "Security exception loading package resources", e);
220                 continue;
221             }
222 
223             // Main title text comes from the authenticator description (e.g. "Google").
224             String authTitle = null;
225             try {
226                 authTitle = targetContext.getString(authDesc.labelId);
227                 if (TextUtils.isEmpty(authTitle)) {
228                     authTitle = null;  // Handled later when we add the row.
229                 }
230             } catch (Resources.NotFoundException e) {
231                 Log.e(TAG, "Authenticator description with bad label id", e);
232             }
233 
234             // There exist some authenticators which aren't intended to be user-facing.
235             // If the authenticator doesn't have a title or an icon, don't present it to
236             // the user as an option.
237             if (authTitle != null || authDesc.iconId != 0) {
238                 allowableAccountTypes.add(authDesc.type);
239             }
240 
241             Account[] accounts = am.getAccountsByType(authDesc.type);
242             if (accounts == null || accounts.length == 0) {
243                 continue;  // No point in continuing; there aren't any accounts to show.
244             }
245 
246             // Icon URI to be displayed for each account is based on the type of authenticator.
247             Drawable authImage = null;
248             try {
249                 authImage = targetContext.getDrawable(authDesc.iconId);
250             } catch (Resources.NotFoundException e) {
251                 Log.e(TAG, "Authenticator has bad resources", e);
252             }
253 
254             // Display an entry for each installed account we have.
255             for (final Account account : accounts) {
256                 final String key = "account_pref:" + account.type + ":" + account.name;
257                 Preference preference = findPreference(key);
258                 if (preference == null) {
259                     preference = new Preference(themedContext);
260                 }
261                 preference.setTitle(authTitle != null ? authTitle : account.name);
262                 preference.setIcon(authImage);
263                 preference.setSummary(authTitle != null ? account.name : null);
264                 preference.setFragment(AccountSyncFragment.class.getName());
265                 AccountSyncFragment.prepareArgs(preference.getExtras(), account);
266 
267                 touchedAccounts.add(key);
268                 preference.setKey(key);
269 
270                 mAccountsGroup.addPreference(preference);
271             }
272         }
273 
274         for (int i = 0; i < mAccountsGroup.getPreferenceCount();) {
275             final Preference preference = mAccountsGroup.getPreference(i);
276             final String key = preference.getKey();
277             if (touchedAccounts.contains(key) || TextUtils.equals(KEY_ADD_ACCOUNT, key)) {
278                 i++;
279             } else {
280                 mAccountsGroup.removePreference(preference);
281             }
282         }
283 
284         // Never allow restricted profile to add accounts.
285         final Preference addAccountPref = findPreference(KEY_ADD_ACCOUNT);
286         if (addAccountPref != null) {
287             addAccountPref.setOrder(Integer.MAX_VALUE);
288             if (isRestricted()) {
289                 addAccountPref.setVisible(false);
290             } else {
291                 Intent i = new Intent().setComponent(new ComponentName("com.android.tv.settings",
292                         "com.android.tv.settings.accounts.AddAccountWithTypeActivity"));
293                 i.putExtra(AddAccountWithTypeActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
294                         allowableAccountTypes.toArray(new String[allowableAccountTypes.size()]));
295 
296                 // If there are available account types, show the "add account" button.
297                 addAccountPref.setVisible(!allowableAccountTypes.isEmpty());
298                 addAccountPref.setIntent(i);
299             }
300         }
301     }
302 
updateAccessories()303     private void updateAccessories() {
304         if (mAccessoriesGroup == null) {
305             return;
306         }
307 
308         if (mBtAdapter == null) {
309             mAccessoriesGroup.setVisible(false);
310             mAccessoriesGroup.removeAll();
311             return;
312         }
313 
314         final Set<BluetoothDevice> bondedDevices = mBtAdapter.getBondedDevices();
315         if (bondedDevices == null) {
316             mAccessoriesGroup.setVisible(false);
317             mAccessoriesGroup.removeAll();
318             return;
319         }
320 
321         final Context themedContext = getPreferenceManager().getContext();
322 
323         final Set<String> touchedKeys = new ArraySet<>(bondedDevices.size() + 1);
324         if (mAddAccessory != null) {
325             touchedKeys.add(mAddAccessory.getKey());
326         }
327 
328         for (final BluetoothDevice device : bondedDevices) {
329             final String deviceAddress = device.getAddress();
330             if (TextUtils.isEmpty(deviceAddress)) {
331                 Log.w(TAG, "Skipping mysteriously empty bluetooth device");
332                 continue;
333             }
334 
335             final String desc = device.isConnected() ? getString(R.string.accessory_connected) :
336                     null;
337             final String key = "BluetoothDevice:" + deviceAddress;
338             touchedKeys.add(key);
339             Preference preference = mAccessoriesGroup.findPreference(key);
340             if (preference == null) {
341                 preference = new Preference(themedContext);
342                 preference.setKey(key);
343             }
344             final String deviceName = device.getAliasName();
345             preference.setTitle(deviceName);
346             preference.setSummary(desc);
347             final int deviceImgId = AccessoryUtils.getImageIdForDevice(device);
348             preference.setIcon(deviceImgId);
349             preference.setFragment(BluetoothAccessoryFragment.class.getName());
350             BluetoothAccessoryFragment.prepareArgs(
351                     preference.getExtras(),
352                     deviceAddress,
353                     deviceName,
354                     deviceImgId);
355             mAccessoriesGroup.addPreference(preference);
356         }
357 
358         for (int i = 0; i < mAccessoriesGroup.getPreferenceCount();) {
359             final Preference preference = mAccessoriesGroup.getPreference(i);
360             if (touchedKeys.contains(preference.getKey())) {
361                 i++;
362             } else {
363                 mAccessoriesGroup.removePreference(preference);
364             }
365         }
366     }
367 
updateDeveloperOptions()368     private void updateDeveloperOptions() {
369         if (mDeveloperPref == null) {
370             return;
371         }
372 
373         final boolean developerEnabled = PreferenceUtils.isDeveloperEnabled(getContext());
374         mDeveloperPref.setVisible(developerEnabled);
375     }
376 
updateSounds()377     private void updateSounds() {
378         if (mSoundsPref == null) {
379             return;
380         }
381 
382         mSoundsPref.setIcon(SoundFragment.getSoundEffectsEnabled(getContext().getContentResolver())
383                 ? R.drawable.ic_volume_up : R.drawable.ic_volume_off);
384     }
385 
updateWifi()386     private void updateWifi() {
387         if (mNetworkPref == null) {
388             return;
389         }
390 
391         mNetworkPref.setTitle(mConnectivityListener.isEthernetAvailable()
392                 ? R.string.connectivity_network : R.string.connectivity_wifi);
393 
394         if (mConnectivityListener.isCellConnected()) {
395             final int signal = mConnectivityListener.getCellSignalStrength();
396             switch (signal) {
397                 case SignalStrength.SIGNAL_STRENGTH_GREAT:
398                     mNetworkPref.setIcon(R.drawable.ic_cell_signal_4_white);
399                     break;
400                 case SignalStrength.SIGNAL_STRENGTH_GOOD:
401                     mNetworkPref.setIcon(R.drawable.ic_cell_signal_3_white);
402                     break;
403                 case SignalStrength.SIGNAL_STRENGTH_MODERATE:
404                     mNetworkPref.setIcon(R.drawable.ic_cell_signal_2_white);
405                     break;
406                 case SignalStrength.SIGNAL_STRENGTH_POOR:
407                     mNetworkPref.setIcon(R.drawable.ic_cell_signal_1_white);
408                     break;
409                 case SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN:
410                 default:
411                     mNetworkPref.setIcon(R.drawable.ic_cell_signal_0_white);
412                     break;
413             }
414         } else if (mConnectivityListener.isEthernetConnected()) {
415             mNetworkPref.setIcon(R.drawable.ic_ethernet_white);
416         } else if (mConnectivityListener.isWifiConnected()) {
417             final int signal = mConnectivityListener.getWifiSignalStrength(5);
418             switch (signal) {
419                 case 4:
420                     mNetworkPref.setIcon(R.drawable.ic_wifi_signal_4_white);
421                     break;
422                 case 3:
423                     mNetworkPref.setIcon(R.drawable.ic_wifi_signal_3_white);
424                     break;
425                 case 2:
426                     mNetworkPref.setIcon(R.drawable.ic_wifi_signal_2_white);
427                     break;
428                 case 1:
429                     mNetworkPref.setIcon(R.drawable.ic_wifi_signal_1_white);
430                     break;
431                 case 0:
432                 default:
433                     mNetworkPref.setIcon(R.drawable.ic_wifi_signal_0_white);
434                     break;
435             }
436         } else {
437             // TODO: get a not connected icon
438             mNetworkPref.setIcon(R.drawable.ic_wifi_signal_4_white);
439         }
440     }
441 
updateGoogleSettings()442     private void updateGoogleSettings() {
443         final Preference googleSettingsPref = findPreference(KEY_GOOGLE_SETTINGS);
444         if (googleSettingsPref != null) {
445             final ResolveInfo info = systemIntentIsHandled(getContext(),
446                     googleSettingsPref.getIntent());
447             googleSettingsPref.setVisible(info != null);
448             if (info != null && info.activityInfo != null) {
449                 googleSettingsPref.setIcon(
450                     info.activityInfo.loadIcon(getContext().getPackageManager()));
451                 googleSettingsPref.setTitle(
452                     info.activityInfo.loadLabel(getContext().getPackageManager()));
453             }
454 
455             final Preference speechPref = findPreference(KEY_SPEECH_SETTINGS);
456             if (speechPref != null) {
457                 speechPref.setVisible(info == null);
458             }
459             final Preference searchPref = findPreference(KEY_SEARCH_SETTINGS);
460             if (searchPref != null) {
461                 searchPref.setVisible(info == null);
462             }
463         }
464     }
465 
466     /**
467      * Returns the ResolveInfo for the system activity that matches given intent filter or null if
468      * no such activity exists.
469      * @param context Context of the caller
470      * @param intent The intent matching the desired system app
471      * @return ResolveInfo of the matching activity or null if no match exists
472      */
systemIntentIsHandled(Context context, Intent intent)473     public static ResolveInfo systemIntentIsHandled(Context context, Intent intent) {
474         if (intent == null) {
475             return null;
476         }
477 
478         final PackageManager pm = context.getPackageManager();
479 
480         for (ResolveInfo info : pm.queryIntentActivities(intent, 0)) {
481             if (info.activityInfo != null
482                     && (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
483                     == ApplicationInfo.FLAG_SYSTEM) {
484                 return info;
485             }
486         }
487         return null;
488     }
489 }
490