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