Home | History | Annotate | Download | only in settings
      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 
    100     public static MainFragment newInstance() {
    101         return new MainFragment();
    102     }
    103 
    104     @Override
    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
    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
    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
    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
    182     public void onStop() {
    183         super.onStop();
    184         mAuthenticatorHelper.stopListeningToAccountUpdates();
    185         LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mBCMReceiver);
    186     }
    187 
    188     private void hideIfIntentUnhandled(Preference preference) {
    189         if (preference == null) {
    190             return;
    191         }
    192         preference.setVisible(systemIntentIsHandled(preference.getIntent()) != null);
    193     }
    194 
    195     private boolean isRestricted() {
    196         return SecurityFragment.isRestrictedProfileInEffect(getContext());
    197     }
    198 
    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 
    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 
    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 
    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 
    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 
    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 
    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