1 /* 2 * Copyright (C) 2014 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.ComponentName; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.Intent.ShortcutIconResource; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.content.pm.ResolveInfo; 33 import android.content.res.Resources; 34 import android.content.res.Resources.NotFoundException; 35 import android.content.res.TypedArray; 36 import android.content.res.XmlResourceParser; 37 import android.media.tv.TvInputInfo; 38 import android.media.tv.TvInputManager; 39 import android.net.Uri; 40 import android.preference.PreferenceActivity; 41 import android.support.v17.leanback.widget.ArrayObjectAdapter; 42 import android.support.v17.leanback.widget.HeaderItem; 43 import android.text.TextUtils; 44 import android.util.AttributeSet; 45 import android.util.Log; 46 import android.util.TypedValue; 47 import android.util.Xml; 48 49 import com.android.internal.util.XmlUtils; 50 import com.android.tv.settings.accessories.AccessoryUtils; 51 import com.android.tv.settings.accessories.BluetoothAccessoryActivity; 52 import com.android.tv.settings.accessories.BluetoothConnectionsManager; 53 import com.android.tv.settings.accounts.AccountSettingsActivity; 54 import com.android.tv.settings.accounts.AddAccountWithTypeActivity; 55 import com.android.tv.settings.accounts.AuthenticatorHelper; 56 import com.android.tv.settings.connectivity.ConnectivityStatusIconUriGetter; 57 import com.android.tv.settings.connectivity.ConnectivityStatusTextGetter; 58 import com.android.tv.settings.connectivity.WifiNetworksActivity; 59 import com.android.tv.settings.device.sound.SoundActivity; 60 import com.android.tv.settings.users.RestrictedProfileDialogFragment; 61 import com.android.tv.settings.util.UriUtils; 62 63 import org.xmlpull.v1.XmlPullParser; 64 import org.xmlpull.v1.XmlPullParserException; 65 66 import java.io.IOException; 67 import java.util.ArrayList; 68 import java.util.Set; 69 70 /** 71 * Gets the list of browse headers and browse items. 72 */ 73 public class BrowseInfo extends BrowseInfoBase { 74 75 private static final String TAG = "TvSettings.BrowseInfo"; 76 private static final boolean DEBUG = false; 77 78 private static final String ACCOUNT_TYPE_GOOGLE = "com.google"; 79 80 private static final String ETHERNET_PREFERENCE_KEY = "ethernet"; 81 82 interface XmlReaderListener { handleRequestedNode(Context context, XmlResourceParser parser, AttributeSet attrs)83 void handleRequestedNode(Context context, XmlResourceParser parser, AttributeSet attrs) 84 throws org.xmlpull.v1.XmlPullParserException, IOException; 85 } 86 87 static class SoundActivityImageUriGetter implements MenuItem.UriGetter { 88 89 private final Context mContext; 90 SoundActivityImageUriGetter(Context context)91 SoundActivityImageUriGetter(Context context) { 92 mContext = context; 93 } 94 95 @Override getUri()96 public String getUri() { 97 return UriUtils.getAndroidResourceUri(mContext.getResources(), 98 SoundActivity.getIconResource(mContext.getContentResolver())); 99 } 100 } 101 102 static class XmlReader { 103 104 private final Context mContext; 105 private final int mXmlResource; 106 private final String mRootNodeName; 107 private final String mNodeNameRequested; 108 private final XmlReaderListener mListener; 109 XmlReader(Context context, int xmlResource, String rootNodeName, String nodeNameRequested, XmlReaderListener listener)110 XmlReader(Context context, int xmlResource, String rootNodeName, String nodeNameRequested, 111 XmlReaderListener listener) { 112 mContext = context; 113 mXmlResource = xmlResource; 114 mRootNodeName = rootNodeName; 115 mNodeNameRequested = nodeNameRequested; 116 mListener = listener; 117 } 118 read()119 void read() { 120 XmlResourceParser parser = null; 121 try { 122 parser = mContext.getResources().getXml(mXmlResource); 123 AttributeSet attrs = Xml.asAttributeSet(parser); 124 125 int type; 126 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 127 && type != XmlPullParser.START_TAG) { 128 // Parse next until start tag is found 129 } 130 131 String nodeName = parser.getName(); 132 if (!mRootNodeName.equals(nodeName)) { 133 throw new RuntimeException("XML document must start with <" + mRootNodeName 134 + "> tag; found" + nodeName + " at " + parser.getPositionDescription()); 135 } 136 137 final int outerDepth = parser.getDepth(); 138 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 139 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 140 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 141 continue; 142 } 143 144 nodeName = parser.getName(); 145 if (mNodeNameRequested.equals(nodeName)) { 146 mListener.handleRequestedNode(mContext, parser, attrs); 147 } else { 148 XmlUtils.skipCurrentTag(parser); 149 } 150 } 151 152 } catch (XmlPullParserException|IOException e) { 153 throw new RuntimeException("Error parsing headers", e); 154 } finally { 155 if (parser != null) 156 parser.close(); 157 } 158 } 159 } 160 161 private static final String PREF_KEY_ADD_ACCOUNT = "add_account"; 162 private static final String PREF_KEY_WIFI = "network"; 163 private static final String PREF_KEY_DEVELOPER = "developer"; 164 private static final String PREF_KEY_INPUTS = "inputs"; 165 private static final String PREF_KEY_HOME = "home"; 166 private static final String PREF_KEY_CAST = "cast"; 167 private static final String PREF_KEY_GOOGLESETTINGS = "googleSettings"; 168 private static final String PREF_KEY_USAGE_AND_DIAG = "usageAndDiag"; 169 170 private final Context mContext; 171 private final AuthenticatorHelper mAuthenticatorHelper; 172 private int mNextItemId; 173 private int mAccountHeaderId; 174 private final BluetoothAdapter mBtAdapter; 175 private final Object mGuard = new Object(); 176 private MenuItem mWifiItem = null; 177 private MenuItem mSoundsItem = null; 178 private MenuItem mDeveloperOptionItem = null; 179 private ArrayObjectAdapter mWifiRow = null; 180 private ArrayObjectAdapter mSoundsRow = null; 181 private ArrayObjectAdapter mDeveloperRow = null; 182 183 private final PreferenceUtils mPreferenceUtils; 184 private boolean mDeveloperEnabled; 185 private final boolean mInputSettingNeeded; 186 BrowseInfo(Context context)187 BrowseInfo(Context context) { 188 mContext = context; 189 mAuthenticatorHelper = new AuthenticatorHelper(); 190 mAuthenticatorHelper.updateAuthDescriptions(context); 191 mAuthenticatorHelper.onAccountsUpdated(context, null); 192 mBtAdapter = BluetoothAdapter.getDefaultAdapter(); 193 mNextItemId = 0; 194 mPreferenceUtils = new PreferenceUtils(context); 195 mDeveloperEnabled = mPreferenceUtils.isDeveloperEnabled(); 196 mInputSettingNeeded = isInputSettingNeeded(); 197 } 198 init()199 void init() { 200 synchronized (mGuard) { 201 mHeaderItems.clear(); 202 mRows.clear(); 203 int settingsXml = isRestricted() ? R.xml.restricted_main : R.xml.main; 204 new XmlReader(mContext, settingsXml, "preference-headers", "header", 205 new HeaderXmlReaderListener()).read(); 206 updateAccessories(R.id.accessories); 207 } 208 } 209 checkForDeveloperOptionUpdate()210 void checkForDeveloperOptionUpdate() { 211 final boolean developerEnabled = mPreferenceUtils.isDeveloperEnabled(); 212 if (developerEnabled != mDeveloperEnabled) { 213 mDeveloperEnabled = developerEnabled; 214 if (mDeveloperOptionItem != null) { 215 mDeveloperRow.add (mDeveloperOptionItem); 216 mDeveloperOptionItem = null; 217 } 218 } 219 } 220 221 private class HeaderXmlReaderListener implements XmlReaderListener { 222 @Override handleRequestedNode(Context context, XmlResourceParser parser, AttributeSet attrs)223 public void handleRequestedNode(Context context, XmlResourceParser parser, 224 AttributeSet attrs) 225 throws XmlPullParserException, IOException { 226 TypedArray sa = mContext.getResources().obtainAttributes(attrs, 227 com.android.internal.R.styleable.PreferenceHeader); 228 final int headerId = sa.getResourceId( 229 com.android.internal.R.styleable.PreferenceHeader_id, 230 (int) PreferenceActivity.HEADER_ID_UNDEFINED); 231 String title = getStringFromTypedArray(sa, 232 com.android.internal.R.styleable.PreferenceHeader_title); 233 sa.recycle(); 234 sa = context.getResources().obtainAttributes(attrs, R.styleable.CanvasSettings); 235 int preferenceRes = sa.getResourceId(R.styleable.CanvasSettings_preference, 0); 236 sa.recycle(); 237 mHeaderItems.add(new HeaderItem(headerId, title)); 238 final ArrayObjectAdapter currentRow = new ArrayObjectAdapter(); 239 mRows.put(headerId, currentRow); 240 if (headerId != R.id.accessories) { 241 new XmlReader(context, preferenceRes, "PreferenceScreen", "Preference", 242 new PreferenceXmlReaderListener(headerId, currentRow)).read(); 243 } 244 } 245 } 246 isRestricted()247 private boolean isRestricted() { 248 return RestrictedProfileDialogFragment.isRestrictedProfileInEffect(mContext); 249 } 250 251 private class PreferenceXmlReaderListener implements XmlReaderListener { 252 253 private final int mHeaderId; 254 private final ArrayObjectAdapter mRow; 255 PreferenceXmlReaderListener(int headerId, ArrayObjectAdapter row)256 PreferenceXmlReaderListener(int headerId, ArrayObjectAdapter row) { 257 mHeaderId = headerId; 258 mRow = row; 259 } 260 261 @Override handleRequestedNode(Context context, XmlResourceParser parser, AttributeSet attrs)262 public void handleRequestedNode(Context context, XmlResourceParser parser, 263 AttributeSet attrs) throws XmlPullParserException, IOException { 264 TypedArray sa = context.getResources().obtainAttributes(attrs, 265 com.android.internal.R.styleable.Preference); 266 267 String key = getStringFromTypedArray(sa, 268 com.android.internal.R.styleable.Preference_key); 269 String title = getStringFromTypedArray(sa, 270 com.android.internal.R.styleable.Preference_title); 271 int iconRes = sa.getResourceId(com.android.internal.R.styleable.Preference_icon, 272 R.drawable.settings_default_icon); 273 sa.recycle(); 274 275 if (PREF_KEY_ADD_ACCOUNT.equals(key)) { 276 mAccountHeaderId = mHeaderId; 277 addAccounts(mRow); 278 } else if (PREF_KEY_HOME.equals(key)) { 279 // Only show home screen setting if there's a system app to handle the intent. 280 Intent recIntent = getIntent(parser, attrs); 281 if (systemIntentIsHandled(recIntent) != null) { 282 mRow.add(new MenuItem.Builder() 283 .id(mNextItemId++) 284 .title(title) 285 .imageResourceId(mContext, iconRes) 286 .intent(recIntent) 287 .build()); 288 } 289 } else if (PREF_KEY_CAST.equals(key)) { 290 Intent i = getIntent(parser, attrs); 291 if (systemIntentIsHandled(i) != null) { 292 mRow.add(new MenuItem.Builder() 293 .id(mNextItemId++) 294 .title(title) 295 .imageResourceId(mContext, iconRes) 296 .intent(i) 297 .build()); 298 } 299 } else if (PREF_KEY_GOOGLESETTINGS.equals(key)) { 300 Intent i = getIntent(parser, attrs); 301 final ResolveInfo info = systemIntentIsHandled(i); 302 if (info != null) { 303 try { 304 final PackageManager packageManager = context.getPackageManager(); 305 final String packageName = info.resolvePackageName != null ? 306 info.resolvePackageName : info.activityInfo.packageName; 307 final Resources targetResources = 308 packageManager.getResourcesForApplication(packageName); 309 final String targetTitle = info.loadLabel(packageManager).toString(); 310 mRow.add(new MenuItem.Builder() 311 .id(mNextItemId++) 312 .title(targetTitle) 313 .imageResourceId(targetResources, info.iconResourceId) 314 .intent(i) 315 .build()); 316 } catch (Exception e) { 317 Log.e(TAG, "Error adding google settings", e); 318 } 319 } 320 } else if (PREF_KEY_USAGE_AND_DIAG.equals(key)) { 321 Intent i = getIntent(parser, attrs); 322 if (systemIntentIsHandled(i) != null) { 323 mRow.add(new MenuItem.Builder() 324 .id(mNextItemId++) 325 .title(title) 326 .imageResourceId(mContext, iconRes) 327 .intent(i) 328 .build()); 329 } 330 } else if (!PREF_KEY_INPUTS.equals(key) || mInputSettingNeeded) { 331 MenuItem.TextGetter descriptionGetter = getDescriptionTextGetterFromKey(key); 332 MenuItem.UriGetter uriGetter = getIconUriGetterFromKey(key); 333 MenuItem.Builder builder = new MenuItem.Builder().id(mNextItemId++).title(title) 334 .descriptionGetter(descriptionGetter) 335 .intent(getIntent(parser, attrs)); 336 if(uriGetter == null) { 337 builder.imageResourceId(mContext, iconRes); 338 } else { 339 builder.imageUriGetter(uriGetter); 340 } 341 final MenuItem item = builder.build(); 342 if (key.equals(PREF_KEY_WIFI)) { 343 mWifiRow = mRow; 344 mWifiItem = item; 345 mRow.add(mWifiItem); 346 } else if (key.equals(SoundActivity.getPreferenceKey())) { 347 mSoundsRow = mRow; 348 mSoundsItem = item; 349 mRow.add(mSoundsItem); 350 } else if (key.equals(PREF_KEY_DEVELOPER) && !mDeveloperEnabled) { 351 mDeveloperRow = mRow; 352 mDeveloperOptionItem = item; 353 } else { 354 mRow.add(item); 355 } 356 } 357 } 358 } 359 rebuildInfo()360 void rebuildInfo() { 361 init(); 362 } 363 updateAccounts()364 void updateAccounts() { 365 synchronized (mGuard) { 366 if (isRestricted()) { 367 // We don't display the accounts in restricted mode 368 return; 369 } 370 ArrayObjectAdapter row = mRows.get(mAccountHeaderId); 371 // Clear any account row cards that are not "Location" or "Security". 372 String dontDelete[] = new String[3]; 373 dontDelete[0] = mContext.getString(R.string.system_location); 374 dontDelete[1] = mContext.getString(R.string.system_security); 375 dontDelete[2] = mContext.getString(R.string.system_diagnostic); 376 int i = 0; 377 while (i < row.size ()) { 378 MenuItem menuItem = (MenuItem) row.get(i); 379 String title = menuItem.getTitle (); 380 boolean deleteItem = true; 381 for (int j = 0; j < dontDelete.length; ++j) { 382 if (title.equals(dontDelete[j])) { 383 deleteItem = false; 384 break; 385 } 386 } 387 if (deleteItem) { 388 row.removeItems(i, 1); 389 } else { 390 ++i; 391 } 392 } 393 // Add accounts to end of row. 394 addAccounts(row); 395 } 396 } 397 updateAccessories()398 void updateAccessories() { 399 synchronized (mGuard) { 400 updateAccessories(R.id.accessories); 401 } 402 } 403 updateWifi(final boolean isEthernetAvailable)404 public void updateWifi(final boolean isEthernetAvailable) { 405 if (mWifiItem != null) { 406 int index = mWifiRow.indexOf(mWifiItem); 407 if (index >= 0) { 408 mWifiItem = new MenuItem.Builder().from(mWifiItem) 409 .title(mContext.getString(isEthernetAvailable 410 ? R.string.connectivity_network : R.string.connectivity_wifi)) 411 .build(); 412 mWifiRow.replace(index, mWifiItem); 413 } 414 } 415 } 416 updateSounds()417 public void updateSounds() { 418 if (mSoundsItem != null) { 419 int index = mSoundsRow.indexOf(mSoundsItem); 420 if (index >= 0) { 421 mSoundsRow.notifyArrayItemRangeChanged(index, 1); 422 } 423 } 424 } 425 isInputSettingNeeded()426 private boolean isInputSettingNeeded() { 427 TvInputManager manager = (TvInputManager) mContext.getSystemService( 428 Context.TV_INPUT_SERVICE); 429 if (manager != null) { 430 for (TvInputInfo input : manager.getTvInputList()) { 431 if (input.isPassthroughInput()) { 432 return true; 433 } 434 } 435 } 436 return false; 437 } 438 updateAccessories(int headerId)439 private void updateAccessories(int headerId) { 440 ArrayObjectAdapter row = mRows.get(headerId); 441 row.clear(); 442 443 addAccessories(row); 444 445 // Add new accessory activity icon 446 ComponentName componentName = new ComponentName("com.android.tv.settings", 447 "com.android.tv.settings.accessories.AddAccessoryActivity"); 448 Intent i = new Intent().setComponent(componentName); 449 row.add(new MenuItem.Builder().id(mNextItemId++) 450 .title(mContext.getString(R.string.accessories_add)) 451 .imageResourceId(mContext, R.drawable.ic_settings_bluetooth) 452 .intent(i).build()); 453 } 454 getIntent(XmlResourceParser parser, AttributeSet attrs)455 private Intent getIntent(XmlResourceParser parser, AttributeSet attrs) 456 throws org.xmlpull.v1.XmlPullParserException, IOException { 457 Intent intent = null; 458 if (parser.next() == XmlPullParser.START_TAG && "intent".equals(parser.getName())) { 459 TypedArray sa = mContext.getResources() 460 .obtainAttributes(attrs, com.android.internal.R.styleable.Intent); 461 String targetClass = getStringFromTypedArray( 462 sa, com.android.internal.R.styleable.Intent_targetClass); 463 String targetPackage = getStringFromTypedArray( 464 sa, com.android.internal.R.styleable.Intent_targetPackage); 465 String action = getStringFromTypedArray( 466 sa, com.android.internal.R.styleable.Intent_action); 467 if (targetClass != null && targetPackage != null) { 468 ComponentName componentName = new ComponentName(targetPackage, targetClass); 469 intent = new Intent(); 470 intent.setComponent(componentName); 471 } else if (action != null) { 472 intent = new Intent(action); 473 if (targetPackage != null) { 474 intent.setPackage(targetPackage); 475 } 476 } 477 478 XmlUtils.skipCurrentTag(parser); 479 } 480 return intent; 481 } 482 getStringFromTypedArray(TypedArray sa, int resourceId)483 private String getStringFromTypedArray(TypedArray sa, int resourceId) { 484 String value = null; 485 TypedValue tv = sa.peekValue(resourceId); 486 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 487 if (tv.resourceId != 0) { 488 value = mContext.getString(tv.resourceId); 489 } else { 490 value = tv.string.toString(); 491 } 492 } 493 return value; 494 } 495 getDescriptionTextGetterFromKey(String key)496 private MenuItem.TextGetter getDescriptionTextGetterFromKey(String key) { 497 if (WifiNetworksActivity.PREFERENCE_KEY.equals(key)) { 498 return ConnectivityStatusTextGetter.createWifiStatusTextGetter(mContext); 499 } 500 501 if (ETHERNET_PREFERENCE_KEY.equals(key)) { 502 return ConnectivityStatusTextGetter.createEthernetStatusTextGetter(mContext); 503 } 504 505 return null; 506 } 507 getIconUriGetterFromKey(String key)508 private MenuItem.UriGetter getIconUriGetterFromKey(String key) { 509 if (SoundActivity.getPreferenceKey().equals(key)) { 510 return new SoundActivityImageUriGetter(mContext); 511 } 512 513 if (WifiNetworksActivity.PREFERENCE_KEY.equals(key)) { 514 return ConnectivityStatusIconUriGetter.createWifiStatusIconUriGetter(mContext); 515 } 516 517 return null; 518 } 519 addAccounts(ArrayObjectAdapter row)520 private void addAccounts(ArrayObjectAdapter row) { 521 AccountManager am = AccountManager.get(mContext); 522 AuthenticatorDescription[] authTypes = am.getAuthenticatorTypes(); 523 ArrayList<String> allowableAccountTypes = new ArrayList<>(authTypes.length); 524 PackageManager pm = mContext.getPackageManager(); 525 526 int googleAccountCount = 0; 527 528 for (AuthenticatorDescription authDesc : authTypes) { 529 final Resources resources; 530 try { 531 resources = pm.getResourcesForApplication(authDesc.packageName); 532 } catch (NameNotFoundException e) { 533 Log.e(TAG, "Authenticator description with bad package name", e); 534 continue; 535 } 536 537 // Main title text comes from the authenticator description (e.g. "Google"). 538 String authTitle = null; 539 try { 540 authTitle = resources.getString(authDesc.labelId); 541 if (TextUtils.isEmpty(authTitle)) { 542 authTitle = null; // Handled later when we add the row. 543 } 544 } catch (NotFoundException e) { 545 if (DEBUG) Log.e(TAG, "Authenticator description with bad label id", e); 546 } 547 548 // There exist some authenticators which aren't intended to be user-facing. 549 // If the authenticator doesn't have a title or an icon, don't present it to 550 // the user as an option. 551 if (authTitle != null || authDesc.iconId != 0) { 552 allowableAccountTypes.add(authDesc.type); 553 } 554 555 Account[] accounts = am.getAccountsByType(authDesc.type); 556 if (accounts == null || accounts.length == 0) { 557 continue; // No point in continuing; there aren't any accounts to show. 558 } 559 560 // Icon URI to be displayed for each account is based on the type of authenticator. 561 String imageUri = null; 562 if (ACCOUNT_TYPE_GOOGLE.equals(authDesc.type)) { 563 googleAccountCount = accounts.length; 564 imageUri = googleAccountIconUri(mContext); 565 } else { 566 try { 567 imageUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + 568 authDesc.packageName + '/' + 569 resources.getResourceTypeName(authDesc.iconId) + '/' + 570 resources.getResourceEntryName(authDesc.iconId)) 571 .toString(); 572 } catch (NotFoundException e) { 573 if (DEBUG) Log.e(TAG, "Authenticator has bad resource ids", e); 574 } 575 } 576 577 // Display an entry for each installed account we have. 578 for (final Account account : accounts) { 579 Intent i = new Intent(mContext, AccountSettingsActivity.class) 580 .putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account.name); 581 row.add(new MenuItem.Builder().id(mNextItemId++) 582 .title(authTitle != null ? authTitle : account.name) 583 .imageUri(imageUri) 584 .description(authTitle != null ? account.name : null) 585 .intent(i) 586 .build()); 587 } 588 } 589 590 // Never allow restricted profile to add accounts. 591 if (!isRestricted()) { 592 593 // If there's already a Google account installed, disallow installing a second one. 594 if (googleAccountCount > 0) { 595 allowableAccountTypes.remove(ACCOUNT_TYPE_GOOGLE); 596 } 597 598 // If there are available account types, add the "add account" button. 599 if (!allowableAccountTypes.isEmpty()) { 600 Intent i = new Intent().setComponent(new ComponentName("com.android.tv.settings", 601 "com.android.tv.settings.accounts.AddAccountWithTypeActivity")); 602 i.putExtra(AddAccountWithTypeActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, 603 allowableAccountTypes.toArray(new String[allowableAccountTypes.size()])); 604 605 row.add(new MenuItem.Builder().id(mNextItemId++) 606 .title(mContext.getString(R.string.add_account)) 607 .imageResourceId(mContext, R.drawable.ic_settings_add) 608 .intent(i).build()); 609 } 610 } 611 } 612 addAccessories(ArrayObjectAdapter row)613 private void addAccessories(ArrayObjectAdapter row) { 614 if (mBtAdapter != null) { 615 Set<BluetoothDevice> bondedDevices = mBtAdapter.getBondedDevices(); 616 if (DEBUG) { 617 Log.d(TAG, "List of Bonded BT Devices:"); 618 } 619 620 Set<String> connectedBluetoothAddresses = 621 BluetoothConnectionsManager.getConnectedSet(mContext); 622 623 for (BluetoothDevice device : bondedDevices) { 624 if (DEBUG) { 625 Log.d(TAG, " Device name: " + device.getName() + " , Class: " + 626 device.getBluetoothClass().getDeviceClass()); 627 } 628 629 int resourceId = AccessoryUtils.getImageIdForDevice(device); 630 Intent i = BluetoothAccessoryActivity.getIntent(mContext, device.getAddress(), 631 device.getName(), resourceId); 632 633 String desc = connectedBluetoothAddresses.contains(device.getAddress()) 634 ? mContext.getString(R.string.accessory_connected) 635 : null; 636 637 row.add(new MenuItem.Builder().id(mNextItemId++).title(device.getName()) 638 .description(desc).imageResourceId(mContext, resourceId) 639 .intent(i).build()); 640 } 641 } 642 } 643 googleAccountIconUri(Context context)644 private static String googleAccountIconUri(Context context) { 645 ShortcutIconResource iconResource = new ShortcutIconResource(); 646 iconResource.packageName = context.getPackageName(); 647 iconResource.resourceName = context.getResources().getResourceName( 648 R.drawable.ic_settings_google_account); 649 return UriUtils.getShortcutIconResourceUri(iconResource).toString(); 650 } 651 systemIntentIsHandled(Intent intent)652 private ResolveInfo systemIntentIsHandled(Intent intent) { 653 if (mContext == null || intent == null) { 654 return null; 655 } 656 657 PackageManager pm = mContext.getPackageManager(); 658 if (pm == null) { 659 return null; 660 } 661 662 for (ResolveInfo info : pm.queryIntentActivities(intent, 0)) { 663 if (info.activityInfo != null && info.activityInfo.enabled && 664 (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 665 ApplicationInfo.FLAG_SYSTEM) { 666 return info; 667 } 668 } 669 return null; 670 } 671 } 672