• 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.v4.content.LocalBroadcastManager;
40 import android.support.v7.preference.Preference;
41 import android.support.v7.preference.PreferenceGroup;
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.accessories.BluetoothConnectionsManager;
50 import com.android.tv.settings.accounts.AccountSyncFragment;
51 import com.android.tv.settings.accounts.AddAccountWithTypeActivity;
52 import com.android.tv.settings.connectivity.ConnectivityListener;
53 import com.android.tv.settings.device.sound.SoundFragment;
54 import com.android.tv.settings.system.SecurityFragment;
55 
56 import java.util.ArrayList;
57 import java.util.Set;
58 
59 public class MainFragment extends LeanbackPreferenceFragment {
60     private static final String TAG = "MainFragment";
61 
62     private static final String KEY_DEVELOPER = "developer";
63     private static final String KEY_LOCATION = "location";
64     private static final String KEY_SECURITY = "security";
65     private static final String KEY_USAGE = "usageAndDiag";
66     private static final String KEY_ADD_ACCOUNT = "add_account";
67     private static final String KEY_ACCESSORIES = "accessories";
68     private static final String KEY_PERSONAL = "personal";
69     private static final String KEY_ADD_ACCESSORY = "add_accessory";
70     private static final String KEY_NETWORK = "network";
71     private static final String KEY_INPUTS = "inputs";
72     private static final String KEY_SOUNDS = "sound_effects";
73     private static final String KEY_GOOGLE_SETTINGS = "googleSettings";
74     private static final String KEY_HOME_SETTINGS = "home";
75     private static final String KEY_CAST_SETTINGS = "cast";
76     private static final String KEY_SPEECH_SETTINGS = "speech";
77     private static final String KEY_SEARCH_SETTINGS = "search";
78     private static final String KEY_ACCOUNTS_CATEGORY = "accounts";
79 
80     private AuthenticatorHelper mAuthenticatorHelper;
81     private BluetoothAdapter mBtAdapter;
82     private ConnectivityListener mConnectivityListener;
83 
84     private boolean mInputSettingNeeded;
85 
86     private Preference mDeveloperPref;
87     private PreferenceGroup mAccessoriesGroup;
88     private PreferenceGroup mAccountsGroup;
89     private Preference mAddAccessory;
90     private Preference mNetworkPref;
91     private Preference mSoundsPref;
92 
93     private final BroadcastReceiver mBCMReceiver = new BroadcastReceiver() {
94         @Override
95         public void onReceive(Context context, Intent intent) {
96             updateAccessories();
97         }
98     };
99 
newInstance()100     public static MainFragment newInstance() {
101         return new MainFragment();
102     }
103 
104     @Override
onCreate(Bundle savedInstanceState)105     public void onCreate(Bundle savedInstanceState) {
106         mAuthenticatorHelper = new AuthenticatorHelper(getContext(),
107                 new UserHandle(UserHandle.myUserId()),
108                 new AuthenticatorHelper.OnAccountsUpdateListener() {
109                     @Override
110                     public void onAccountsUpdate(UserHandle userHandle) {
111                         updateAccounts();
112                     }
113                 });
114         mBtAdapter = BluetoothAdapter.getDefaultAdapter();
115         mConnectivityListener = new ConnectivityListener(getContext(),
116                 new ConnectivityListener.Listener() {
117                     @Override
118                     public void onConnectivityChange() {
119                         updateWifi();
120                     }
121                 });
122 
123         final TvInputManager manager = (TvInputManager) getContext().getSystemService(
124                 Context.TV_INPUT_SERVICE);
125         if (manager != null) {
126             for (final TvInputInfo input : manager.getTvInputList()) {
127                 if (input.isPassthroughInput()) {
128                     mInputSettingNeeded = true;
129                 }
130             }
131         }
132         super.onCreate(savedInstanceState);
133     }
134 
135     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)136     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
137         if (isRestricted()) {
138             setPreferencesFromResource(R.xml.restricted_prefs, null);
139         } else {
140             setPreferencesFromResource(R.xml.main_prefs, null);
141         }
142         mDeveloperPref = findPreference(KEY_DEVELOPER);
143         mAccessoriesGroup = (PreferenceGroup) findPreference(KEY_ACCESSORIES);
144         mAddAccessory = findPreference(KEY_ADD_ACCESSORY);
145         mNetworkPref = findPreference(KEY_NETWORK);
146         mSoundsPref = findPreference(KEY_SOUNDS);
147         mAccountsGroup = (PreferenceGroup) findPreference(KEY_ACCOUNTS_CATEGORY);
148 
149         final Preference inputPref = findPreference(KEY_INPUTS);
150         if (inputPref != null) {
151             inputPref.setVisible(mInputSettingNeeded);
152         }
153     }
154 
155     @Override
onStart()156     public void onStart() {
157         super.onStart();
158         mAuthenticatorHelper.listenToAccountUpdates();
159         final IntentFilter filter =
160                 new IntentFilter(BluetoothConnectionsManager.ACTION_BLUETOOTH_UPDATE);
161         LocalBroadcastManager.getInstance(getContext()).registerReceiver(mBCMReceiver, filter);
162     }
163 
164     @Override
onResume()165     public void onResume() {
166         super.onResume();
167 
168         updateAccounts();
169         updateAccessories();
170         updateDeveloperOptions();
171         updateSounds();
172         updateGoogleSettings();
173 
174         hideIfIntentUnhandled(findPreference(KEY_HOME_SETTINGS));
175         hideIfIntentUnhandled(findPreference(KEY_CAST_SETTINGS));
176         hideIfIntentUnhandled(findPreference(KEY_USAGE));
177         hideIfIntentUnhandled(findPreference(KEY_SPEECH_SETTINGS));
178         hideIfIntentUnhandled(findPreference(KEY_SEARCH_SETTINGS));
179     }
180 
181     @Override
onStop()182     public void onStop() {
183         super.onStop();
184         mAuthenticatorHelper.stopListeningToAccountUpdates();
185         LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mBCMReceiver);
186     }
187 
hideIfIntentUnhandled(Preference preference)188     private void hideIfIntentUnhandled(Preference preference) {
189         if (preference == null) {
190             return;
191         }
192         preference.setVisible(systemIntentIsHandled(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         final Set<String> connectedBluetoothAddresses =
316                 BluetoothConnectionsManager.getConnectedSet(getContext());
317         final Context themedContext = getPreferenceManager().getContext();
318 
319         final Set<String> touchedKeys = new ArraySet<>(bondedDevices.size() + 1);
320         if (mAddAccessory != null) {
321             touchedKeys.add(mAddAccessory.getKey());
322         }
323 
324         for (final BluetoothDevice device : bondedDevices) {
325             final String desc = connectedBluetoothAddresses.contains(device.getAddress())
326                     ? getString(R.string.accessory_connected)
327                     : null;
328 
329             final String key = "BluetoothDevice:" + device.getAddress();
330             touchedKeys.add(key);
331             Preference preference = mAccessoriesGroup.findPreference(key);
332             if (preference == null) {
333                 preference = new Preference(themedContext);
334                 preference.setKey(key);
335             }
336             preference.setTitle(device.getName());
337             preference.setSummary(desc);
338             final int deviceImgId = AccessoryUtils.getImageIdForDevice(device);
339             preference.setIcon(deviceImgId);
340             preference.setFragment(BluetoothAccessoryFragment.class.getName());
341             BluetoothAccessoryFragment.prepareArgs(
342                     preference.getExtras(),
343                     device.getAddress(),
344                     device.getName(),
345                     deviceImgId);
346             mAccessoriesGroup.addPreference(preference);
347         }
348 
349         for (int i = 0; i < mAccessoriesGroup.getPreferenceCount();) {
350             final Preference preference = mAccessoriesGroup.getPreference(i);
351             if (touchedKeys.contains(preference.getKey())) {
352                 i++;
353             } else {
354                 mAccessoriesGroup.removePreference(preference);
355             }
356         }
357     }
358 
updateDeveloperOptions()359     private void updateDeveloperOptions() {
360         if (mDeveloperPref == null) {
361             return;
362         }
363 
364         final boolean developerEnabled = PreferenceUtils.isDeveloperEnabled(getContext());
365         mDeveloperPref.setVisible(developerEnabled);
366     }
367 
updateSounds()368     private void updateSounds() {
369         if (mSoundsPref == null) {
370             return;
371         }
372 
373         mSoundsPref.setIcon(SoundFragment.getSoundEffectsEnabled(getContext().getContentResolver())
374                 ? R.drawable.ic_volume_up : R.drawable.ic_volume_off);
375     }
376 
updateWifi()377     private void updateWifi() {
378         if (mNetworkPref == null) {
379             return;
380         }
381 
382         mNetworkPref.setTitle(mConnectivityListener.isEthernetAvailable()
383                 ? R.string.connectivity_network : R.string.connectivity_wifi);
384 
385         // TODO: signal strength
386     }
387 
updateGoogleSettings()388     public void updateGoogleSettings() {
389         final Preference googleSettingsPref = findPreference(KEY_GOOGLE_SETTINGS);
390         if (googleSettingsPref != null) {
391             final ResolveInfo info = systemIntentIsHandled(googleSettingsPref.getIntent());
392             googleSettingsPref.setVisible(info != null);
393             if (info != null) {
394                 try {
395                     final Context targetContext = getContext()
396                             .createPackageContext(info.resolvePackageName != null ?
397                                     info.resolvePackageName : info.activityInfo.packageName, 0);
398                     googleSettingsPref.setIcon(targetContext.getDrawable(info.iconResourceId));
399                 } catch (Resources.NotFoundException | PackageManager.NameNotFoundException
400                         | SecurityException e) {
401                     Log.e(TAG, "Google settings icon not found", e);
402                 }
403             }
404         }
405     }
406 
systemIntentIsHandled(Intent intent)407     private ResolveInfo systemIntentIsHandled(Intent intent) {
408         if (intent == null) {
409             return null;
410         }
411 
412         final PackageManager pm = getContext().getPackageManager();
413 
414         for (ResolveInfo info : pm.queryIntentActivities(intent, 0)) {
415             if (info.activityInfo != null && info.activityInfo.enabled &&
416                     (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) ==
417                             ApplicationInfo.FLAG_SYSTEM) {
418                 return info;
419             }
420         }
421         return null;
422     }
423 }
424