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