• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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