2 * Copyright (C) 2007 Google Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.settings;
19 import static android.content.Intent.EXTRA_USER;
21 import android.annotation.Nullable;
22 import android.app.ActivityManager;
23 import android.app.ActivityManagerNative;
24 import android.app.AlertDialog;
25 import android.app.Dialog;
26 import android.app.Fragment;
27 import android.app.IActivityManager;
28 import android.content.ContentResolver;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.content.pm.ResolveInfo;
37 import android.content.pm.Signature;
38 import android.content.pm.UserInfo;
39 import android.content.res.Resources;
40 import android.content.res.Resources.NotFoundException;
41 import android.database.Cursor;
42 import android.graphics.Bitmap;
43 import android.graphics.BitmapFactory;
44 import android.graphics.drawable.Drawable;
45 import android.net.ConnectivityManager;
46 import android.net.LinkProperties;
47 import android.net.Uri;
48 import android.os.BatteryManager;
49 import android.os.Bundle;
50 import android.os.IBinder;
51 import android.os.RemoteException;
52 import android.os.UserHandle;
53 import android.os.UserManager;
54 import android.preference.Preference;
55 import android.preference.PreferenceFrameLayout;
56 import android.preference.PreferenceGroup;
57 import android.provider.ContactsContract.CommonDataKinds;
58 import android.provider.ContactsContract.Contacts;
59 import android.provider.ContactsContract.Data;
60 import android.provider.ContactsContract.Profile;
61 import android.provider.ContactsContract.RawContacts;
62 import android.service.persistentdata.PersistentDataBlockManager;
63 import android.telephony.TelephonyManager;
64 import android.text.BidiFormatter;
65 import android.text.TextDirectionHeuristics;
66 import android.text.TextUtils;
67 import android.util.Log;
68 import android.view.View;
69 import android.view.ViewGroup;
70 import android.widget.ListView;
71 import android.widget.TabWidget;
73 import com.android.internal.util.ImageUtils;
74 import com.android.internal.util.UserIcons;
75 import com.android.settings.UserSpinnerAdapter.UserDetails;
76 import com.android.settings.dashboard.DashboardCategory;
77 import com.android.settings.dashboard.DashboardTile;
78 import com.android.settings.drawable.CircleFramedDrawable;
80 import java.io.IOException;
81 import java.io.InputStream;
82 import java.net.InetAddress;
83 import java.text.NumberFormat;
84 import java.util.ArrayList;
85 import java.util.Iterator;
86 import java.util.List;
87 import java.util.Locale;
89 public final class Utils {
90 private static final String TAG = "Settings";
93 * Set the preference's title to the matching activity's label.
95 public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1;
98 * The opacity level of a disabled icon.
100 public static final float DISABLED_ALPHA = 0.4f;
103 * Color spectrum to use to indicate badness. 0 is completely transparent (no data),
104 * 1 is most bad (red), the last value is least bad (green).
106 public static final int[] BADNESS_COLORS = new int[] {
107 0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00,
108 0xfffabf2c, 0xff679e37, 0xff0a7f42
112 * Name of the meta-data item that should be set in the AndroidManifest.xml
113 * to specify the icon that should be displayed for the preference.
115 private static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
118 * Name of the meta-data item that should be set in the AndroidManifest.xml
119 * to specify the title that should be displayed for the preference.
121 private static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
124 * Name of the meta-data item that should be set in the AndroidManifest.xml
125 * to specify the summary text that should be displayed for the preference.
127 private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
129 private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
131 private static final int SECONDS_PER_MINUTE = 60;
132 private static final int SECONDS_PER_HOUR = 60 * 60;
133 private static final int SECONDS_PER_DAY = 24 * 60 * 60;
136 * Finds a matching activity for a preference's intent. If a matching
137 * activity is not found, it will remove the preference.
139 * @param context The context.
140 * @param parentPreferenceGroup The preference group that contains the
141 * preference whose intent is being resolved.
142 * @param preferenceKey The key of the preference whose intent is being
144 * @param flags 0 or one or more of
145 * {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY}
147 * @return Whether an activity was found. If false, the preference was
150 public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
151 PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
153 Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
154 if (preference == null) {
158 Intent intent = preference.getIntent();
159 if (intent != null) {
160 // Find the activity that is in the system image
161 PackageManager pm = context.getPackageManager();
162 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
163 int listSize = list.size();
164 for (int i = 0; i < listSize; i++) {
165 ResolveInfo resolveInfo = list.get(i);
166 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
169 // Replace the intent with this specific activity
170 preference.setIntent(new Intent().setClassName(
171 resolveInfo.activityInfo.packageName,
172 resolveInfo.activityInfo.name));
174 if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) {
175 // Set the preference title to the activity's label
176 preference.setTitle(resolveInfo.loadLabel(pm));
184 // Did not find a matching activity, so remove the preference
185 parentPreferenceGroup.removePreference(preference);
190 public static boolean updateTileToSpecificActivityFromMetaDataOrRemove(Context context,
191 DashboardTile tile) {
193 Intent intent = tile.intent;
194 if (intent != null) {
195 // Find the activity that is in the system image
196 PackageManager pm = context.getPackageManager();
197 List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
198 int listSize = list.size();
199 for (int i = 0; i < listSize; i++) {
200 ResolveInfo resolveInfo = list.get(i);
201 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
203 Drawable icon = null;
205 String summary = null;
207 // Get the activity's meta-data
209 Resources res = pm.getResourcesForApplication(
210 resolveInfo.activityInfo.packageName);
211 Bundle metaData = resolveInfo.activityInfo.metaData;
213 if (res != null && metaData != null) {
214 icon = res.getDrawable(metaData.getInt(META_DATA_PREFERENCE_ICON));
215 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
216 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
218 } catch (NameNotFoundException e) {
220 } catch (NotFoundException e) {
224 // Set the preference title to the activity's label if no
225 // meta-data is found
226 if (TextUtils.isEmpty(title)) {
227 title = resolveInfo.loadLabel(pm).toString();
230 // Set icon, title and summary for the preference
234 tile.summary = summary;
235 // Replace the intent with this specific activity
236 tile.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
237 resolveInfo.activityInfo.name);
248 * Returns true if Monkey is running.
250 public static boolean isMonkeyRunning() {
251 return ActivityManager.isUserAMonkey();
255 * Returns whether the device is voice-capable (meaning, it is also a phone).
257 public static boolean isVoiceCapable(Context context) {
258 TelephonyManager telephony =
259 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
260 return telephony != null && telephony.isVoiceCapable();
263 public static boolean isWifiOnly(Context context) {
264 ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
265 Context.CONNECTIVITY_SERVICE);
266 return (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
270 * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses.
271 * @param context the application context
272 * @return the formatted and newline-separated IP addresses, or null if none.
274 public static String getWifiIpAddresses(Context context) {
275 ConnectivityManager cm = (ConnectivityManager)
276 context.getSystemService(Context.CONNECTIVITY_SERVICE);
277 LinkProperties prop = cm.getLinkProperties(ConnectivityManager.TYPE_WIFI);
278 return formatIpAddresses(prop);
282 * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
284 * @param context the application context
285 * @return the formatted and newline-separated IP addresses, or null if none.
287 public static String getDefaultIpAddresses(ConnectivityManager cm) {
288 LinkProperties prop = cm.getActiveLinkProperties();
289 return formatIpAddresses(prop);
292 private static String formatIpAddresses(LinkProperties prop) {
293 if (prop == null) return null;
294 Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
295 // If there are no entries, return null
296 if (!iter.hasNext()) return null;
297 // Concatenate all available addresses, comma separated
298 String addresses = "";
299 while (iter.hasNext()) {
300 addresses += iter.next().getHostAddress();
301 if (iter.hasNext()) addresses += "\n";
306 public static Locale createLocaleFromString(String localeStr) {
307 // TODO: is there a better way to actually construct a locale that will match?
308 // The main problem is, on top of Java specs, locale.toString() and
309 // new Locale(locale.toString()).toString() do not return equal() strings in
310 // many cases, because the constructor takes the only string as the language
311 // code. So : new Locale("en", "US").toString() => "en_US"
312 // And : new Locale("en_US").toString() => "en_us"
313 if (null == localeStr)
314 return Locale.getDefault();
315 String[] brokenDownLocale = localeStr.split("_", 3);
316 // split may not return a 0-length array.
317 if (1 == brokenDownLocale.length) {
318 return new Locale(brokenDownLocale[0]);
319 } else if (2 == brokenDownLocale.length) {
320 return new Locale(brokenDownLocale[0], brokenDownLocale[1]);
322 return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]);
326 /** Formats the ratio of amount/total as a percentage. */
327 public static String formatPercentage(long amount, long total) {
328 return formatPercentage(((double) amount) / total);
331 /** Formats an integer from 0..100 as a percentage. */
332 public static String formatPercentage(int percentage) {
333 return formatPercentage(((double) percentage) / 100.0);
336 /** Formats a double from 0.0..1.0 as a percentage. */
337 private static String formatPercentage(double percentage) {
338 BidiFormatter bf = BidiFormatter.getInstance();
339 return bf.unicodeWrap(NumberFormat.getPercentInstance().format(percentage));
342 public static boolean isBatteryPresent(Intent batteryChangedIntent) {
343 return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
346 public static String getBatteryPercentage(Intent batteryChangedIntent) {
347 return formatPercentage(getBatteryLevel(batteryChangedIntent));
350 public static int getBatteryLevel(Intent batteryChangedIntent) {
351 int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
352 int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
353 return (level * 100) / scale;
356 public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
357 final Intent intent = batteryChangedIntent;
359 int plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
360 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
361 BatteryManager.BATTERY_STATUS_UNKNOWN);
363 if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
365 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
366 resId = R.string.battery_info_status_charging_ac;
367 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
368 resId = R.string.battery_info_status_charging_usb;
369 } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
370 resId = R.string.battery_info_status_charging_wireless;
372 resId = R.string.battery_info_status_charging;
374 statusString = res.getString(resId);
375 } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
376 statusString = res.getString(R.string.battery_info_status_discharging);
377 } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
378 statusString = res.getString(R.string.battery_info_status_not_charging);
379 } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
380 statusString = res.getString(R.string.battery_info_status_full);
382 statusString = res.getString(R.string.battery_info_status_unknown);
388 public static void forcePrepareCustomPreferencesList(
389 ViewGroup parent, View child, ListView list, boolean ignoreSidePadding) {
390 list.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
391 list.setClipToPadding(false);
392 prepareCustomPreferencesList(parent, child, list, ignoreSidePadding);
396 * Prepare a custom preferences layout, moving padding to {@link ListView}
397 * when outside scrollbars are requested. Usually used to display
398 * {@link ListView} and {@link TabWidget} with correct padding.
400 public static void prepareCustomPreferencesList(
401 ViewGroup parent, View child, View list, boolean ignoreSidePadding) {
402 final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY;
404 final Resources res = list.getResources();
405 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin);
406 final int paddingBottom = res.getDimensionPixelSize(
407 com.android.internal.R.dimen.preference_fragment_padding_bottom);
409 if (parent instanceof PreferenceFrameLayout) {
410 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true;
412 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide;
413 list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom);
415 list.setPaddingRelative(paddingSide, 0, paddingSide, paddingBottom);
420 public static void forceCustomPadding(View view, boolean additive) {
421 final Resources res = view.getResources();
422 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin);
424 final int paddingStart = paddingSide + (additive ? view.getPaddingStart() : 0);
425 final int paddingEnd = paddingSide + (additive ? view.getPaddingEnd() : 0);
426 final int paddingBottom = res.getDimensionPixelSize(
427 com.android.internal.R.dimen.preference_fragment_padding_bottom);
429 view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom);
433 * Return string resource that best describes combination of tethering
434 * options available on this device.
436 public static int getTetheringLabel(ConnectivityManager cm) {
437 String[] usbRegexs = cm.getTetherableUsbRegexs();
438 String[] wifiRegexs = cm.getTetherableWifiRegexs();
439 String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
441 boolean usbAvailable = usbRegexs.length != 0;
442 boolean wifiAvailable = wifiRegexs.length != 0;
443 boolean bluetoothAvailable = bluetoothRegexs.length != 0;
445 if (wifiAvailable && usbAvailable && bluetoothAvailable) {
446 return R.string.tether_settings_title_all;
447 } else if (wifiAvailable && usbAvailable) {
448 return R.string.tether_settings_title_all;
449 } else if (wifiAvailable && bluetoothAvailable) {
450 return R.string.tether_settings_title_all;
451 } else if (wifiAvailable) {
452 return R.string.tether_settings_title_wifi;
453 } else if (usbAvailable && bluetoothAvailable) {
454 return R.string.tether_settings_title_usb_bluetooth;
455 } else if (usbAvailable) {
456 return R.string.tether_settings_title_usb;
458 return R.string.tether_settings_title_bluetooth;
462 /* Used by UserSettings as well. Call this on a non-ui thread. */
463 public static boolean copyMeProfilePhoto(Context context, UserInfo user) {
464 Uri contactUri = Profile.CONTENT_URI;
466 InputStream avatarDataStream = Contacts.openContactPhotoInputStream(
467 context.getContentResolver(),
469 // If there's no profile photo, assign a default avatar
470 if (avatarDataStream == null) {
473 int userId = user != null ? user.id : UserHandle.myUserId();
474 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
475 Bitmap icon = BitmapFactory.decodeStream(avatarDataStream);
476 um.setUserIcon(userId, icon);
478 avatarDataStream.close();
479 } catch (IOException ioe) { }
483 public static String getMeProfileName(Context context, boolean full) {
485 return getProfileDisplayName(context);
487 return getShorterNameIfPossible(context);
491 private static String getShorterNameIfPossible(Context context) {
492 final String given = getLocalProfileGivenName(context);
493 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context);
496 private static String getLocalProfileGivenName(Context context) {
497 final ContentResolver cr = context.getContentResolver();
499 // Find the raw contact ID for the local ME profile raw contact.
500 final long localRowProfileId;
501 final Cursor localRawProfile = cr.query(
502 Profile.CONTENT_RAW_CONTACTS_URI,
503 new String[] {RawContacts._ID},
504 RawContacts.ACCOUNT_TYPE + " IS NULL AND " +
505 RawContacts.ACCOUNT_NAME + " IS NULL",
507 if (localRawProfile == null) return null;
510 if (!localRawProfile.moveToFirst()) {
513 localRowProfileId = localRawProfile.getLong(0);
515 localRawProfile.close();
518 // Find the structured name for the raw contact.
519 final Cursor structuredName = cr.query(
520 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(),
521 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME,
522 CommonDataKinds.StructuredName.FAMILY_NAME},
523 Data.RAW_CONTACT_ID + "=" + localRowProfileId,
525 if (structuredName == null) return null;
528 if (!structuredName.moveToFirst()) {
531 String partialName = structuredName.getString(0);
532 if (TextUtils.isEmpty(partialName)) {
533 partialName = structuredName.getString(1);
537 structuredName.close();
541 private static final String getProfileDisplayName(Context context) {
542 final ContentResolver cr = context.getContentResolver();
543 final Cursor profile = cr.query(Profile.CONTENT_URI,
544 new String[] {Profile.DISPLAY_NAME}, null, null, null);
545 if (profile == null) return null;
548 if (!profile.moveToFirst()) {
551 return profile.getString(0);
557 /** Not global warming, it's global change warning. */
558 public static Dialog buildGlobalChangeWarningDialog(final Context context, int titleResId,
559 final Runnable positiveAction) {
560 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
561 builder.setTitle(titleResId);
562 builder.setMessage(R.string.global_change_warning);
563 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
565 public void onClick(DialogInterface dialog, int which) {
566 positiveAction.run();
569 builder.setNegativeButton(android.R.string.cancel, null);
571 return builder.create();
574 public static boolean hasMultipleUsers(Context context) {
575 return ((UserManager) context.getSystemService(Context.USER_SERVICE))
576 .getUsers().size() > 1;
580 * Start a new instance of the activity, showing only the given fragment.
581 * When launched in this mode, the given preference fragment will be instantiated and fill the
584 * @param context The context.
585 * @param fragmentName The name of the fragment to display.
586 * @param args Optional arguments to supply to the fragment.
587 * @param resultTo Option fragment that should receive the result of the activity launch.
588 * @param resultRequestCode If resultTo is non-null, this is the request code in which
589 * to report the result.
590 * @param titleResId resource id for the String to display for the title of this set
592 * @param title String to display for the title of this set of preferences.
594 public static void startWithFragment(Context context, String fragmentName, Bundle args,
595 Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title) {
596 startWithFragment(context, fragmentName, args, resultTo, resultRequestCode,
597 titleResId, title, false /* not a shortcut */);
600 public static void startWithFragment(Context context, String fragmentName, Bundle args,
601 Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title,
602 boolean isShortcut) {
603 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResId,
605 if (resultTo == null) {
606 context.startActivity(intent);
608 resultTo.startActivityForResult(intent, resultRequestCode);
612 public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args,
613 int titleResId, CharSequence title, boolean isShortcut, UserHandle userHandle) {
614 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResId,
616 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
617 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
618 context.startActivityAsUser(intent, userHandle);
622 * Build an Intent to launch a new activity showing the selected fragment.
623 * The implementation constructs an Intent that re-launches the current activity with the
624 * appropriate arguments to display the fragment.
627 * @param context The Context.
628 * @param fragmentName The name of the fragment to display.
629 * @param args Optional arguments to supply to the fragment.
630 * @param titleResId Optional title resource id to show for this item.
631 * @param title Optional title to show for this item.
632 * @param isShortcut tell if this is a Launcher Shortcut or not
633 * @return Returns an Intent that can be launched to display the given
636 public static Intent onBuildStartFragmentIntent(Context context, String fragmentName,
637 Bundle args, int titleResId, CharSequence title, boolean isShortcut) {
638 Intent intent = new Intent(Intent.ACTION_MAIN);
639 intent.setClass(context, SubSettings.class);
640 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName);
641 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
642 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId);
643 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
644 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut);
649 * Returns the managed profile of the current user or null if none found.
651 public static UserHandle getManagedProfile(UserManager userManager) {
652 List<UserHandle> userProfiles = userManager.getUserProfiles();
653 final int count = userProfiles.size();
654 for (int i = 0; i < count; i++) {
655 final UserHandle profile = userProfiles.get(i);
656 if (profile.getIdentifier() == userManager.getUserHandle()) {
659 final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier());
660 if (userInfo.isManagedProfile()) {
668 * Returns true if the current profile is a managed one.
670 public static boolean isManagedProfile(UserManager userManager) {
671 UserInfo currentUser = userManager.getUserInfo(userManager.getUserHandle());
672 return currentUser.isManagedProfile();
676 * Creates a {@link UserSpinnerAdapter} if there is more than one profile on the device.
678 * <p> The adapter can be used to populate a spinner that switches between the Settings
679 * app on the different profiles.
681 * @return a {@link UserSpinnerAdapter} or null if there is only one profile.
683 public static UserSpinnerAdapter createUserSpinnerAdapter(UserManager userManager,
685 List<UserHandle> userProfiles = userManager.getUserProfiles();
686 if (userProfiles.size() < 2) {
690 UserHandle myUserHandle = new UserHandle(UserHandle.myUserId());
691 // The first option should be the current profile
692 userProfiles.remove(myUserHandle);
693 userProfiles.add(0, myUserHandle);
695 ArrayList<UserDetails> userDetails = new ArrayList<UserDetails>(userProfiles.size());
696 final int count = userProfiles.size();
697 for (int i = 0; i < count; i++) {
698 userDetails.add(new UserDetails(userProfiles.get(i), userManager, context));
700 return new UserSpinnerAdapter(context, userDetails);
704 * Returns the target user for a Settings activity.
706 * The target user can be either the current user, the user that launched this activity or
707 * the user contained as an extra in the arguments or intent extras.
709 * Note: This is secure in the sense that it only returns a target user different to the current
710 * one if the app launching this activity is the Settings app itself, running in the same user
711 * or in one that is in the same profile group, or if the user id is provided by the system.
713 public static UserHandle getSecureTargetUser(IBinder activityToken,
714 UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) {
715 UserHandle currentUser = new UserHandle(UserHandle.myUserId());
716 IActivityManager am = ActivityManagerNative.getDefault();
718 String launchedFromPackage = am.getLaunchedFromPackage(activityToken);
719 boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage);
721 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
722 am.getLaunchedFromUid(activityToken)));
723 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
725 if (isProfileOf(um, launchedFromUser)) {
726 return launchedFromUser;
729 UserHandle extrasUser = intentExtras != null
730 ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
731 if (extrasUser != null && !extrasUser.equals(currentUser)) {
733 if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) {
737 UserHandle argumentsUser = arguments != null
738 ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
739 if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
741 if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) {
742 return argumentsUser;
745 } catch (RemoteException e) {
747 Log.v(TAG, "Could not talk to activity manager.", e);
753 * Returns the target user for a Settings activity.
755 * The target user can be either the current user, the user that launched this activity or
756 * the user contained as an extra in the arguments or intent extras.
758 * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if
761 * @see #getInsecureTargetUser(IBinder, Bundle, Bundle)
763 public static UserHandle getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments,
764 @Nullable Bundle intentExtras) {
765 UserHandle currentUser = new UserHandle(UserHandle.myUserId());
766 IActivityManager am = ActivityManagerNative.getDefault();
768 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
769 am.getLaunchedFromUid(activityToken)));
770 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
771 return launchedFromUser;
773 UserHandle extrasUser = intentExtras != null
774 ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
775 if (extrasUser != null && !extrasUser.equals(currentUser)) {
778 UserHandle argumentsUser = arguments != null
779 ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
780 if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
781 return argumentsUser;
783 } catch (RemoteException e) {
785 Log.v(TAG, "Could not talk to activity manager.", e);
792 * Returns true if the user provided is in the same profiles group as the current user.
794 private static boolean isProfileOf(UserManager um, UserHandle otherUser) {
795 if (um == null || otherUser == null) return false;
796 return (UserHandle.myUserId() == otherUser.getIdentifier())
797 || um.getUserProfiles().contains(otherUser);
801 * Creates a dialog to confirm with the user if it's ok to remove the user
802 * and delete all the data.
804 * @param context a Context object
805 * @param removingUserId The userId of the user to remove
806 * @param onConfirmListener Callback object for positive action
807 * @return the created Dialog
809 public static Dialog createRemoveConfirmationDialog(Context context, int removingUserId,
810 DialogInterface.OnClickListener onConfirmListener) {
811 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
812 UserInfo userInfo = um.getUserInfo(removingUserId);
815 if (UserHandle.myUserId() == removingUserId) {
816 titleResId = R.string.user_confirm_remove_self_title;
817 messageResId = R.string.user_confirm_remove_self_message;
818 } else if (userInfo.isRestricted()) {
819 titleResId = R.string.user_profile_confirm_remove_title;
820 messageResId = R.string.user_profile_confirm_remove_message;
821 } else if (userInfo.isManagedProfile()) {
822 titleResId = R.string.work_profile_confirm_remove_title;
823 messageResId = R.string.work_profile_confirm_remove_message;
825 titleResId = R.string.user_confirm_remove_title;
826 messageResId = R.string.user_confirm_remove_message;
828 Dialog dlg = new AlertDialog.Builder(context)
829 .setTitle(titleResId)
830 .setMessage(messageResId)
831 .setPositiveButton(R.string.user_delete_button,
833 .setNegativeButton(android.R.string.cancel, null)
839 * Returns whether or not this device is able to be OEM unlocked.
841 static boolean isOemUnlockEnabled(Context context) {
842 PersistentDataBlockManager manager =(PersistentDataBlockManager)
843 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
844 return manager.getOemUnlockEnabled();
848 * Allows enabling or disabling OEM unlock on this device. OEM unlocked
849 * devices allow users to flash other OSes to them.
851 static void setOemUnlockEnabled(Context context, boolean enabled) {
852 PersistentDataBlockManager manager =(PersistentDataBlockManager)
853 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
854 manager.setOemUnlockEnabled(enabled);
858 * Returns a circular icon for a user.
860 public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
861 if (user.iconPath != null) {
862 Bitmap icon = um.getUserIcon(user.id);
864 return CircleFramedDrawable.getInstance(context, icon);
867 return UserIcons.getDefaultUserIcon(user.id, /* light= */ false);
871 * Return whether or not the user should have a SIM Cards option in Settings.
872 * TODO: Change back to returning true if count is greater than one after testing.
873 * TODO: See bug 16533525.
875 public static boolean showSimCardTile(Context context) {
876 final TelephonyManager tm =
877 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
879 return tm.getSimCount() > 0;
883 * Determine whether a package is a "system package", in which case certain things (like
884 * disabling notifications or disabling the package altogether) should be disallowed.
886 public static boolean isSystemPackage(PackageManager pm, PackageInfo pkg) {
887 if (sSystemSignature == null) {
888 sSystemSignature = new Signature[]{ getSystemSignature(pm) };
890 return sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg));
893 private static Signature[] sSystemSignature;
895 private static Signature getFirstSignature(PackageInfo pkg) {
896 if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
897 return pkg.signatures[0];
902 private static Signature getSystemSignature(PackageManager pm) {
904 final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
905 return getFirstSignature(sys);
906 } catch (NameNotFoundException e) {
912 * Returns elapsed time for the given millis, in the following format:
914 * @param context the application context
915 * @param millis the elapsed time in milli seconds
916 * @param withSeconds include seconds?
917 * @return the formatted elapsed time
919 public static String formatElapsedTime(Context context, double millis, boolean withSeconds) {
920 StringBuilder sb = new StringBuilder();
921 int seconds = (int) Math.floor(millis / 1000);
927 int days = 0, hours = 0, minutes = 0;
928 if (seconds >= SECONDS_PER_DAY) {
929 days = seconds / SECONDS_PER_DAY;
930 seconds -= days * SECONDS_PER_DAY;
932 if (seconds >= SECONDS_PER_HOUR) {
933 hours = seconds / SECONDS_PER_HOUR;
934 seconds -= hours * SECONDS_PER_HOUR;
936 if (seconds >= SECONDS_PER_MINUTE) {
937 minutes = seconds / SECONDS_PER_MINUTE;
938 seconds -= minutes * SECONDS_PER_MINUTE;
942 sb.append(context.getString(R.string.battery_history_days,
943 days, hours, minutes, seconds));
944 } else if (hours > 0) {
945 sb.append(context.getString(R.string.battery_history_hours,
946 hours, minutes, seconds));
947 } else if (minutes > 0) {
948 sb.append(context.getString(R.string.battery_history_minutes, minutes, seconds));
950 sb.append(context.getString(R.string.battery_history_seconds, seconds));
954 sb.append(context.getString(R.string.battery_history_days_no_seconds,
955 days, hours, minutes));
956 } else if (hours > 0) {
957 sb.append(context.getString(R.string.battery_history_hours_no_seconds,
960 sb.append(context.getString(R.string.battery_history_minutes_no_seconds, minutes));
963 return sb.toString();