• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 
17 package com.android.settings;
18 
19 import android.app.Activity;
20 import android.app.ActivityManager;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.UserInfo;
32 import android.content.res.Resources;
33 import android.content.res.Resources.NotFoundException;
34 import android.database.Cursor;
35 import android.graphics.Bitmap;
36 import android.graphics.BitmapFactory;
37 import android.graphics.drawable.Drawable;
38 import android.net.ConnectivityManager;
39 import android.net.LinkProperties;
40 import android.net.Uri;
41 import android.os.BatteryManager;
42 import android.os.Bundle;
43 import android.os.ParcelFileDescriptor;
44 import android.os.UserHandle;
45 import android.os.UserManager;
46 import android.preference.Preference;
47 import android.preference.PreferenceActivity.Header;
48 import android.preference.PreferenceFrameLayout;
49 import android.preference.PreferenceGroup;
50 import android.provider.ContactsContract.CommonDataKinds;
51 import android.provider.ContactsContract.CommonDataKinds.Phone;
52 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
53 import android.provider.ContactsContract.Contacts;
54 import android.provider.ContactsContract.Data;
55 import android.provider.ContactsContract.Profile;
56 import android.provider.ContactsContract.RawContacts;
57 import android.telephony.TelephonyManager;
58 import android.text.TextUtils;
59 import android.util.Log;
60 import android.view.View;
61 import android.view.ViewGroup;
62 import android.widget.ListView;
63 import android.widget.TabWidget;
64 
65 import com.android.settings.users.ProfileUpdateReceiver;
66 
67 import java.io.FileOutputStream;
68 import java.io.IOException;
69 import java.io.InputStream;
70 import java.net.InetAddress;
71 import java.util.Iterator;
72 import java.util.List;
73 import java.util.Locale;
74 
75 public class Utils {
76 
77     /**
78      * Set the preference's title to the matching activity's label.
79      */
80     public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1;
81 
82     /**
83      * Name of the meta-data item that should be set in the AndroidManifest.xml
84      * to specify the icon that should be displayed for the preference.
85      */
86     private static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
87 
88     /**
89      * Name of the meta-data item that should be set in the AndroidManifest.xml
90      * to specify the title that should be displayed for the preference.
91      */
92     private static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
93 
94     /**
95      * Name of the meta-data item that should be set in the AndroidManifest.xml
96      * to specify the summary text that should be displayed for the preference.
97      */
98     private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
99 
100     /**
101      * Finds a matching activity for a preference's intent. If a matching
102      * activity is not found, it will remove the preference.
103      *
104      * @param context The context.
105      * @param parentPreferenceGroup The preference group that contains the
106      *            preference whose intent is being resolved.
107      * @param preferenceKey The key of the preference whose intent is being
108      *            resolved.
109      * @param flags 0 or one or more of
110      *            {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY}
111      *            .
112      * @return Whether an activity was found. If false, the preference was
113      *         removed.
114      */
updatePreferenceToSpecificActivityOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags)115     public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
116             PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
117 
118         Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
119         if (preference == null) {
120             return false;
121         }
122 
123         Intent intent = preference.getIntent();
124         if (intent != null) {
125             // Find the activity that is in the system image
126             PackageManager pm = context.getPackageManager();
127             List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
128             int listSize = list.size();
129             for (int i = 0; i < listSize; i++) {
130                 ResolveInfo resolveInfo = list.get(i);
131                 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
132                         != 0) {
133 
134                     // Replace the intent with this specific activity
135                     preference.setIntent(new Intent().setClassName(
136                             resolveInfo.activityInfo.packageName,
137                             resolveInfo.activityInfo.name));
138 
139                     if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) {
140                         // Set the preference title to the activity's label
141                         preference.setTitle(resolveInfo.loadLabel(pm));
142                     }
143 
144                     return true;
145                 }
146             }
147         }
148 
149         // Did not find a matching activity, so remove the preference
150         parentPreferenceGroup.removePreference(preference);
151 
152         return false;
153     }
154 
155     /**
156      * Finds a matching activity for a preference's intent. If a matching
157      * activity is not found, it will remove the preference. The icon, title and
158      * summary of the preference will also be updated with the values retrieved
159      * from the activity's meta-data elements. If no meta-data elements are
160      * specified then the preference title will be set to match the label of the
161      * activity, an icon and summary text will not be displayed.
162      *
163      * @param context The context.
164      * @param parentPreferenceGroup The preference group that contains the
165      *            preference whose intent is being resolved.
166      * @param preferenceKey The key of the preference whose intent is being
167      *            resolved.
168      *
169      * @return Whether an activity was found. If false, the preference was
170      *         removed.
171      *
172      * @see {@link #META_DATA_PREFERENCE_ICON}
173      *      {@link #META_DATA_PREFERENCE_TITLE}
174      *      {@link #META_DATA_PREFERENCE_SUMMARY}
175      */
updatePreferenceToSpecificActivityFromMetaDataOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey)176     public static boolean updatePreferenceToSpecificActivityFromMetaDataOrRemove(Context context,
177             PreferenceGroup parentPreferenceGroup, String preferenceKey) {
178 
179         IconPreferenceScreen preference = (IconPreferenceScreen)parentPreferenceGroup
180                 .findPreference(preferenceKey);
181         if (preference == null) {
182             return false;
183         }
184 
185         Intent intent = preference.getIntent();
186         if (intent != null) {
187             // Find the activity that is in the system image
188             PackageManager pm = context.getPackageManager();
189             List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
190             int listSize = list.size();
191             for (int i = 0; i < listSize; i++) {
192                 ResolveInfo resolveInfo = list.get(i);
193                 if ((resolveInfo.activityInfo.applicationInfo.flags
194                         & ApplicationInfo.FLAG_SYSTEM) != 0) {
195                     Drawable icon = null;
196                     String title = null;
197                     String summary = null;
198 
199                     // Get the activity's meta-data
200                     try {
201                         Resources res = pm
202                                 .getResourcesForApplication(resolveInfo.activityInfo.packageName);
203                         Bundle metaData = resolveInfo.activityInfo.metaData;
204 
205                         if (res != null && metaData != null) {
206                             icon = res.getDrawable(metaData.getInt(META_DATA_PREFERENCE_ICON));
207                             title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
208                             summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
209                         }
210                     } catch (NameNotFoundException e) {
211                         // Ignore
212                     } catch (NotFoundException e) {
213                         // Ignore
214                     }
215 
216                     // Set the preference title to the activity's label if no
217                     // meta-data is found
218                     if (TextUtils.isEmpty(title)) {
219                         title = resolveInfo.loadLabel(pm).toString();
220                     }
221 
222                     // Set icon, title and summary for the preference
223                     preference.setIcon(icon);
224                     preference.setTitle(title);
225                     preference.setSummary(summary);
226 
227                     // Replace the intent with this specific activity
228                     preference.setIntent(new Intent().setClassName(
229                             resolveInfo.activityInfo.packageName,
230                             resolveInfo.activityInfo.name));
231 
232                    return true;
233                 }
234             }
235         }
236 
237         // Did not find a matching activity, so remove the preference
238         parentPreferenceGroup.removePreference(preference);
239 
240         return false;
241     }
242 
updateHeaderToSpecificActivityFromMetaDataOrRemove(Context context, List<Header> target, Header header)243     public static boolean updateHeaderToSpecificActivityFromMetaDataOrRemove(Context context,
244             List<Header> target, Header header) {
245 
246         Intent intent = header.intent;
247         if (intent != null) {
248             // Find the activity that is in the system image
249             PackageManager pm = context.getPackageManager();
250             List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
251             int listSize = list.size();
252             for (int i = 0; i < listSize; i++) {
253                 ResolveInfo resolveInfo = list.get(i);
254                 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
255                         != 0) {
256                     Drawable icon = null;
257                     String title = null;
258                     String summary = null;
259 
260                     // Get the activity's meta-data
261                     try {
262                         Resources res = pm.getResourcesForApplication(
263                                 resolveInfo.activityInfo.packageName);
264                         Bundle metaData = resolveInfo.activityInfo.metaData;
265 
266                         if (res != null && metaData != null) {
267                             icon = res.getDrawable(metaData.getInt(META_DATA_PREFERENCE_ICON));
268                             title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
269                             summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
270                         }
271                     } catch (NameNotFoundException e) {
272                         // Ignore
273                     } catch (NotFoundException e) {
274                         // Ignore
275                     }
276 
277                     // Set the preference title to the activity's label if no
278                     // meta-data is found
279                     if (TextUtils.isEmpty(title)) {
280                         title = resolveInfo.loadLabel(pm).toString();
281                     }
282 
283                     // Set icon, title and summary for the preference
284                     // TODO:
285                     //header.icon = icon;
286                     header.title = title;
287                     header.summary = summary;
288                     // Replace the intent with this specific activity
289                     header.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
290                             resolveInfo.activityInfo.name);
291 
292                     return true;
293                 }
294             }
295         }
296 
297         // Did not find a matching activity, so remove the preference
298         target.remove(header);
299 
300         return false;
301     }
302 
303     /**
304      * Returns true if Monkey is running.
305      */
isMonkeyRunning()306     public static boolean isMonkeyRunning() {
307         return ActivityManager.isUserAMonkey();
308     }
309 
310     /**
311      * Returns whether the device is voice-capable (meaning, it is also a phone).
312      */
isVoiceCapable(Context context)313     public static boolean isVoiceCapable(Context context) {
314         TelephonyManager telephony =
315                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
316         return telephony != null && telephony.isVoiceCapable();
317     }
318 
isWifiOnly(Context context)319     public static boolean isWifiOnly(Context context) {
320         ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
321                 Context.CONNECTIVITY_SERVICE);
322         return (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
323     }
324 
325     /**
326      * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses.
327      * @param context the application context
328      * @return the formatted and comma-separated IP addresses, or null if none.
329      */
getWifiIpAddresses(Context context)330     public static String getWifiIpAddresses(Context context) {
331         ConnectivityManager cm = (ConnectivityManager)
332                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
333         LinkProperties prop = cm.getLinkProperties(ConnectivityManager.TYPE_WIFI);
334         return formatIpAddresses(prop);
335     }
336 
337     /**
338      * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
339      * addresses.
340      * @param context the application context
341      * @return the formatted and comma-separated IP addresses, or null if none.
342      */
getDefaultIpAddresses(Context context)343     public static String getDefaultIpAddresses(Context context) {
344         ConnectivityManager cm = (ConnectivityManager)
345                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
346         LinkProperties prop = cm.getActiveLinkProperties();
347         return formatIpAddresses(prop);
348     }
349 
formatIpAddresses(LinkProperties prop)350     private static String formatIpAddresses(LinkProperties prop) {
351         if (prop == null) return null;
352         Iterator<InetAddress> iter = prop.getAddresses().iterator();
353         // If there are no entries, return null
354         if (!iter.hasNext()) return null;
355         // Concatenate all available addresses, comma separated
356         String addresses = "";
357         while (iter.hasNext()) {
358             addresses += iter.next().getHostAddress();
359             if (iter.hasNext()) addresses += ", ";
360         }
361         return addresses;
362     }
363 
createLocaleFromString(String localeStr)364     public static Locale createLocaleFromString(String localeStr) {
365         // TODO: is there a better way to actually construct a locale that will match?
366         // The main problem is, on top of Java specs, locale.toString() and
367         // new Locale(locale.toString()).toString() do not return equal() strings in
368         // many cases, because the constructor takes the only string as the language
369         // code. So : new Locale("en", "US").toString() => "en_US"
370         // And : new Locale("en_US").toString() => "en_us"
371         if (null == localeStr)
372             return Locale.getDefault();
373         String[] brokenDownLocale = localeStr.split("_", 3);
374         // split may not return a 0-length array.
375         if (1 == brokenDownLocale.length) {
376             return new Locale(brokenDownLocale[0]);
377         } else if (2 == brokenDownLocale.length) {
378             return new Locale(brokenDownLocale[0], brokenDownLocale[1]);
379         } else {
380             return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]);
381         }
382     }
383 
getBatteryPercentage(Intent batteryChangedIntent)384     public static String getBatteryPercentage(Intent batteryChangedIntent) {
385         int level = batteryChangedIntent.getIntExtra("level", 0);
386         int scale = batteryChangedIntent.getIntExtra("scale", 100);
387         return String.valueOf(level * 100 / scale) + "%";
388     }
389 
getBatteryStatus(Resources res, Intent batteryChangedIntent)390     public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
391         final Intent intent = batteryChangedIntent;
392 
393         int plugType = intent.getIntExtra("plugged", 0);
394         int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
395         String statusString;
396         if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
397             statusString = res.getString(R.string.battery_info_status_charging);
398             if (plugType > 0) {
399                 int resId;
400                 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
401                     resId = R.string.battery_info_status_charging_ac;
402                 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
403                     resId = R.string.battery_info_status_charging_usb;
404                 } else {
405                     resId = R.string.battery_info_status_charging_wireless;
406                 }
407                 statusString = statusString + " " + res.getString(resId);
408             }
409         } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
410             statusString = res.getString(R.string.battery_info_status_discharging);
411         } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
412             statusString = res.getString(R.string.battery_info_status_not_charging);
413         } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
414             statusString = res.getString(R.string.battery_info_status_full);
415         } else {
416             statusString = res.getString(R.string.battery_info_status_unknown);
417         }
418 
419         return statusString;
420     }
421 
forcePrepareCustomPreferencesList( ViewGroup parent, View child, ListView list, boolean ignoreSidePadding)422     public static void forcePrepareCustomPreferencesList(
423             ViewGroup parent, View child, ListView list, boolean ignoreSidePadding) {
424         list.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
425         list.setClipToPadding(false);
426         prepareCustomPreferencesList(parent, child, list, ignoreSidePadding);
427     }
428 
429     /**
430      * Prepare a custom preferences layout, moving padding to {@link ListView}
431      * when outside scrollbars are requested. Usually used to display
432      * {@link ListView} and {@link TabWidget} with correct padding.
433      */
prepareCustomPreferencesList( ViewGroup parent, View child, View list, boolean ignoreSidePadding)434     public static void prepareCustomPreferencesList(
435             ViewGroup parent, View child, View list, boolean ignoreSidePadding) {
436         final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY;
437         if (movePadding && parent instanceof PreferenceFrameLayout) {
438             ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true;
439 
440             final Resources res = list.getResources();
441             final int paddingSide = res.getDimensionPixelSize(
442                     com.android.internal.R.dimen.preference_fragment_padding_side);
443             final int paddingBottom = res.getDimensionPixelSize(
444                     com.android.internal.R.dimen.preference_fragment_padding_bottom);
445 
446             final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide;
447             list.setPadding(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom);
448         }
449     }
450 
451     /**
452      * Return string resource that best describes combination of tethering
453      * options available on this device.
454      */
getTetheringLabel(ConnectivityManager cm)455     public static int getTetheringLabel(ConnectivityManager cm) {
456         String[] usbRegexs = cm.getTetherableUsbRegexs();
457         String[] wifiRegexs = cm.getTetherableWifiRegexs();
458         String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
459 
460         boolean usbAvailable = usbRegexs.length != 0;
461         boolean wifiAvailable = wifiRegexs.length != 0;
462         boolean bluetoothAvailable = bluetoothRegexs.length != 0;
463 
464         if (wifiAvailable && usbAvailable && bluetoothAvailable) {
465             return R.string.tether_settings_title_all;
466         } else if (wifiAvailable && usbAvailable) {
467             return R.string.tether_settings_title_all;
468         } else if (wifiAvailable && bluetoothAvailable) {
469             return R.string.tether_settings_title_all;
470         } else if (wifiAvailable) {
471             return R.string.tether_settings_title_wifi;
472         } else if (usbAvailable && bluetoothAvailable) {
473             return R.string.tether_settings_title_usb_bluetooth;
474         } else if (usbAvailable) {
475             return R.string.tether_settings_title_usb;
476         } else {
477             return R.string.tether_settings_title_bluetooth;
478         }
479     }
480 
481     /* Used by UserSettings as well. Call this on a non-ui thread. */
copyMeProfilePhoto(Context context, UserInfo user)482     public static boolean copyMeProfilePhoto(Context context, UserInfo user) {
483         Uri contactUri = Profile.CONTENT_URI;
484 
485         InputStream avatarDataStream = Contacts.openContactPhotoInputStream(
486                     context.getContentResolver(),
487                     contactUri, true);
488         // If there's no profile photo, assign a default avatar
489         if (avatarDataStream == null) {
490             return false;
491         }
492         int userId = user != null ? user.id : UserHandle.myUserId();
493         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
494         Bitmap icon = BitmapFactory.decodeStream(avatarDataStream);
495         um.setUserIcon(userId, icon);
496         try {
497             avatarDataStream.close();
498         } catch (IOException ioe) { }
499         return true;
500     }
501 
getMeProfileName(Context context, boolean full)502     public static String getMeProfileName(Context context, boolean full) {
503         if (full) {
504             return getProfileDisplayName(context);
505         } else {
506             return getShorterNameIfPossible(context);
507         }
508     }
509 
getShorterNameIfPossible(Context context)510     private static String getShorterNameIfPossible(Context context) {
511         final String given = getLocalProfileGivenName(context);
512         return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context);
513     }
514 
getLocalProfileGivenName(Context context)515     private static String getLocalProfileGivenName(Context context) {
516         final ContentResolver cr = context.getContentResolver();
517 
518         // Find the raw contact ID for the local ME profile raw contact.
519         final long localRowProfileId;
520         final Cursor localRawProfile = cr.query(
521                 Profile.CONTENT_RAW_CONTACTS_URI,
522                 new String[] {RawContacts._ID},
523                 RawContacts.ACCOUNT_TYPE + " IS NULL AND " +
524                         RawContacts.ACCOUNT_NAME + " IS NULL",
525                 null, null);
526         if (localRawProfile == null) return null;
527 
528         try {
529             if (!localRawProfile.moveToFirst()) {
530                 return null;
531             }
532             localRowProfileId = localRawProfile.getLong(0);
533         } finally {
534             localRawProfile.close();
535         }
536 
537         // Find the structured name for the raw contact.
538         final Cursor structuredName = cr.query(
539                 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(),
540                 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME,
541                     CommonDataKinds.StructuredName.FAMILY_NAME},
542                 Data.RAW_CONTACT_ID + "=" + localRowProfileId,
543                 null, null);
544         if (structuredName == null) return null;
545 
546         try {
547             if (!structuredName.moveToFirst()) {
548                 return null;
549             }
550             String partialName = structuredName.getString(0);
551             if (TextUtils.isEmpty(partialName)) {
552                 partialName = structuredName.getString(1);
553             }
554             return partialName;
555         } finally {
556             structuredName.close();
557         }
558     }
559 
getProfileDisplayName(Context context)560     private static final String getProfileDisplayName(Context context) {
561         final ContentResolver cr = context.getContentResolver();
562         final Cursor profile = cr.query(Profile.CONTENT_URI,
563                 new String[] {Profile.DISPLAY_NAME}, null, null, null);
564         if (profile == null) return null;
565 
566         try {
567             if (!profile.moveToFirst()) {
568                 return null;
569             }
570             return profile.getString(0);
571         } finally {
572             profile.close();
573         }
574     }
575 
576     /** Not global warming, it's global change warning. */
buildGlobalChangeWarningDialog(final Context context, int titleResId, final Runnable positiveAction)577     public static Dialog buildGlobalChangeWarningDialog(final Context context, int titleResId,
578             final Runnable positiveAction) {
579         final AlertDialog.Builder builder = new AlertDialog.Builder(context);
580         builder.setTitle(titleResId);
581         builder.setMessage(R.string.global_change_warning);
582         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
583             @Override
584             public void onClick(DialogInterface dialog, int which) {
585                 positiveAction.run();
586             }
587         });
588         builder.setNegativeButton(android.R.string.cancel, null);
589 
590         return builder.create();
591     }
592 
hasMultipleUsers(Context context)593     public static boolean hasMultipleUsers(Context context) {
594         return ((UserManager) context.getSystemService(Context.USER_SERVICE))
595                 .getUsers().size() > 1;
596     }
597 }
598