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.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 
     99     public static MainFragment newInstance() {
    100         return new MainFragment();
    101     }
    102 
    103     @Override
    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
    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
    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
    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
    173     public void onStop() {
    174         super.onStop();
    175         mAuthenticatorHelper.stopListeningToAccountUpdates();
    176         getContext().unregisterReceiver(mBCMReceiver);
    177         mConnectivityListener.stop();
    178     }
    179 
    180     @Override
    181     public void onDestroy() {
    182         super.onDestroy();
    183         if (mConnectivityListener != null) {
    184             mConnectivityListener.destroy();
    185         }
    186     }
    187 
    188     private void hideIfIntentUnhandled(Preference preference) {
    189         if (preference == null) {
    190             return;
    191         }
    192         preference.setVisible(systemIntentIsHandled(getContext(), 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         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 
    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 
    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 
    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 
    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      */
    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