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