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