• 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 static android.content.Intent.EXTRA_USER;
20 
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;
72 
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;
79 
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;
88 
89 public final class Utils {
90     private static final String TAG = "Settings";
91 
92     /**
93      * Set the preference's title to the matching activity's label.
94      */
95     public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1;
96 
97     /**
98      * The opacity level of a disabled icon.
99      */
100     public static final float DISABLED_ALPHA = 0.4f;
101 
102     /**
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).
105      */
106     public static final int[] BADNESS_COLORS = new int[] {
107             0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00,
108             0xfffabf2c, 0xff679e37, 0xff0a7f42
109     };
110 
111     /**
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.
114      */
115     private static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
116 
117     /**
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.
120      */
121     private static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
122 
123     /**
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.
126      */
127     private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
128 
129     private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
130 
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;
134 
135     /**
136      * Finds a matching activity for a preference's intent. If a matching
137      * activity is not found, it will remove the preference.
138      *
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
143      *            resolved.
144      * @param flags 0 or one or more of
145      *            {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY}
146      *            .
147      * @return Whether an activity was found. If false, the preference was
148      *         removed.
149      */
updatePreferenceToSpecificActivityOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags)150     public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
151             PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
152 
153         Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
154         if (preference == null) {
155             return false;
156         }
157 
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)
167                         != 0) {
168 
169                     // Replace the intent with this specific activity
170                     preference.setIntent(new Intent().setClassName(
171                             resolveInfo.activityInfo.packageName,
172                             resolveInfo.activityInfo.name));
173 
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));
177                     }
178 
179                     return true;
180                 }
181             }
182         }
183 
184         // Did not find a matching activity, so remove the preference
185         parentPreferenceGroup.removePreference(preference);
186 
187         return false;
188     }
189 
updateTileToSpecificActivityFromMetaDataOrRemove(Context context, DashboardTile tile)190     public static boolean updateTileToSpecificActivityFromMetaDataOrRemove(Context context,
191             DashboardTile tile) {
192 
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)
202                         != 0) {
203                     Drawable icon = null;
204                     String title = null;
205                     String summary = null;
206 
207                     // Get the activity's meta-data
208                     try {
209                         Resources res = pm.getResourcesForApplication(
210                                 resolveInfo.activityInfo.packageName);
211                         Bundle metaData = resolveInfo.activityInfo.metaData;
212 
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));
217                         }
218                     } catch (NameNotFoundException e) {
219                         // Ignore
220                     } catch (NotFoundException e) {
221                         // Ignore
222                     }
223 
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();
228                     }
229 
230                     // Set icon, title and summary for the preference
231                     // TODO:
232                     //tile.icon = icon;
233                     tile.title = title;
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);
238 
239                     return true;
240                 }
241             }
242         }
243 
244         return false;
245     }
246 
247     /**
248      * Returns true if Monkey is running.
249      */
isMonkeyRunning()250     public static boolean isMonkeyRunning() {
251         return ActivityManager.isUserAMonkey();
252     }
253 
254     /**
255      * Returns whether the device is voice-capable (meaning, it is also a phone).
256      */
isVoiceCapable(Context context)257     public static boolean isVoiceCapable(Context context) {
258         TelephonyManager telephony =
259                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
260         return telephony != null && telephony.isVoiceCapable();
261     }
262 
isWifiOnly(Context context)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);
267     }
268 
269     /**
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.
273      */
getWifiIpAddresses(Context context)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);
279     }
280 
281     /**
282      * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
283      * addresses.
284      * @param context the application context
285      * @return the formatted and newline-separated IP addresses, or null if none.
286      */
getDefaultIpAddresses(ConnectivityManager cm)287     public static String getDefaultIpAddresses(ConnectivityManager cm) {
288         LinkProperties prop = cm.getActiveLinkProperties();
289         return formatIpAddresses(prop);
290     }
291 
formatIpAddresses(LinkProperties 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";
302         }
303         return addresses;
304     }
305 
createLocaleFromString(String localeStr)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]);
321         } else {
322             return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]);
323         }
324     }
325 
326     /** Formats the ratio of amount/total as a percentage. */
formatPercentage(long amount, long total)327     public static String formatPercentage(long amount, long total) {
328         return formatPercentage(((double) amount) / total);
329     }
330 
331     /** Formats an integer from 0..100 as a percentage. */
formatPercentage(int percentage)332     public static String formatPercentage(int percentage) {
333         return formatPercentage(((double) percentage) / 100.0);
334     }
335 
336     /** Formats a double from 0.0..1.0 as a percentage. */
formatPercentage(double percentage)337     private static String formatPercentage(double percentage) {
338       BidiFormatter bf = BidiFormatter.getInstance();
339       return bf.unicodeWrap(NumberFormat.getPercentInstance().format(percentage));
340     }
341 
isBatteryPresent(Intent batteryChangedIntent)342     public static boolean isBatteryPresent(Intent batteryChangedIntent) {
343         return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
344     }
345 
getBatteryPercentage(Intent batteryChangedIntent)346     public static String getBatteryPercentage(Intent batteryChangedIntent) {
347         return formatPercentage(getBatteryLevel(batteryChangedIntent));
348     }
349 
getBatteryLevel(Intent 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;
354     }
355 
getBatteryStatus(Resources res, Intent batteryChangedIntent)356     public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
357         final Intent intent = batteryChangedIntent;
358 
359         int plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
360         int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
361                 BatteryManager.BATTERY_STATUS_UNKNOWN);
362         String statusString;
363         if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
364             int resId;
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;
371             } else {
372                 resId = R.string.battery_info_status_charging;
373             }
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);
381         } else {
382             statusString = res.getString(R.string.battery_info_status_unknown);
383         }
384 
385         return statusString;
386     }
387 
forcePrepareCustomPreferencesList( ViewGroup parent, View child, ListView list, boolean ignoreSidePadding)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);
393     }
394 
395     /**
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.
399      */
prepareCustomPreferencesList( ViewGroup parent, View child, View list, boolean ignoreSidePadding)400     public static void prepareCustomPreferencesList(
401             ViewGroup parent, View child, View list, boolean ignoreSidePadding) {
402         final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY;
403         if (movePadding) {
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);
408 
409             if (parent instanceof PreferenceFrameLayout) {
410                 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true;
411 
412                 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide;
413                 list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom);
414             } else {
415                 list.setPaddingRelative(paddingSide, 0, paddingSide, paddingBottom);
416             }
417         }
418     }
419 
forceCustomPadding(View view, boolean additive)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);
423 
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);
428 
429         view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom);
430     }
431 
432     /**
433      * Return string resource that best describes combination of tethering
434      * options available on this device.
435      */
getTetheringLabel(ConnectivityManager cm)436     public static int getTetheringLabel(ConnectivityManager cm) {
437         String[] usbRegexs = cm.getTetherableUsbRegexs();
438         String[] wifiRegexs = cm.getTetherableWifiRegexs();
439         String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
440 
441         boolean usbAvailable = usbRegexs.length != 0;
442         boolean wifiAvailable = wifiRegexs.length != 0;
443         boolean bluetoothAvailable = bluetoothRegexs.length != 0;
444 
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;
457         } else {
458             return R.string.tether_settings_title_bluetooth;
459         }
460     }
461 
462     /* Used by UserSettings as well. Call this on a non-ui thread. */
copyMeProfilePhoto(Context context, UserInfo user)463     public static boolean copyMeProfilePhoto(Context context, UserInfo user) {
464         Uri contactUri = Profile.CONTENT_URI;
465 
466         InputStream avatarDataStream = Contacts.openContactPhotoInputStream(
467                     context.getContentResolver(),
468                     contactUri, true);
469         // If there's no profile photo, assign a default avatar
470         if (avatarDataStream == null) {
471             return false;
472         }
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);
477         try {
478             avatarDataStream.close();
479         } catch (IOException ioe) { }
480         return true;
481     }
482 
getMeProfileName(Context context, boolean full)483     public static String getMeProfileName(Context context, boolean full) {
484         if (full) {
485             return getProfileDisplayName(context);
486         } else {
487             return getShorterNameIfPossible(context);
488         }
489     }
490 
getShorterNameIfPossible(Context context)491     private static String getShorterNameIfPossible(Context context) {
492         final String given = getLocalProfileGivenName(context);
493         return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context);
494     }
495 
getLocalProfileGivenName(Context context)496     private static String getLocalProfileGivenName(Context context) {
497         final ContentResolver cr = context.getContentResolver();
498 
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",
506                 null, null);
507         if (localRawProfile == null) return null;
508 
509         try {
510             if (!localRawProfile.moveToFirst()) {
511                 return null;
512             }
513             localRowProfileId = localRawProfile.getLong(0);
514         } finally {
515             localRawProfile.close();
516         }
517 
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,
524                 null, null);
525         if (structuredName == null) return null;
526 
527         try {
528             if (!structuredName.moveToFirst()) {
529                 return null;
530             }
531             String partialName = structuredName.getString(0);
532             if (TextUtils.isEmpty(partialName)) {
533                 partialName = structuredName.getString(1);
534             }
535             return partialName;
536         } finally {
537             structuredName.close();
538         }
539     }
540 
getProfileDisplayName(Context context)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;
546 
547         try {
548             if (!profile.moveToFirst()) {
549                 return null;
550             }
551             return profile.getString(0);
552         } finally {
553             profile.close();
554         }
555     }
556 
557     /** Not global warming, it's global change warning. */
buildGlobalChangeWarningDialog(final Context context, int titleResId, final Runnable positiveAction)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() {
564             @Override
565             public void onClick(DialogInterface dialog, int which) {
566                 positiveAction.run();
567             }
568         });
569         builder.setNegativeButton(android.R.string.cancel, null);
570 
571         return builder.create();
572     }
573 
hasMultipleUsers(Context context)574     public static boolean hasMultipleUsers(Context context) {
575         return ((UserManager) context.getSystemService(Context.USER_SERVICE))
576                 .getUsers().size() > 1;
577     }
578 
579     /**
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
582      * entire activity.
583      *
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
591      *                   of preferences.
592      * @param title String to display for the title of this set of preferences.
593      */
startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title)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 */);
598     }
599 
startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title, boolean isShortcut)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,
604                 title, isShortcut);
605         if (resultTo == null) {
606             context.startActivity(intent);
607         } else {
608             resultTo.startActivityForResult(intent, resultRequestCode);
609         }
610     }
611 
startWithFragmentAsUser(Context context, String fragmentName, Bundle args, int titleResId, CharSequence title, boolean isShortcut, UserHandle userHandle)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,
615                 title, isShortcut);
616         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
617         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
618         context.startActivityAsUser(intent, userHandle);
619     }
620 
621     /**
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.
625      *
626      *
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
634      * fragment.
635      */
onBuildStartFragmentIntent(Context context, String fragmentName, Bundle args, int titleResId, CharSequence title, boolean isShortcut)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);
645         return intent;
646     }
647 
648     /**
649      * Returns the managed profile of the current user or null if none found.
650      */
getManagedProfile(UserManager userManager)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()) {
657                 continue;
658             }
659             final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier());
660             if (userInfo.isManagedProfile()) {
661                 return profile;
662             }
663         }
664         return null;
665     }
666 
667     /**
668      * Returns true if the current profile is a managed one.
669      */
isManagedProfile(UserManager userManager)670     public static boolean isManagedProfile(UserManager userManager) {
671         UserInfo currentUser = userManager.getUserInfo(userManager.getUserHandle());
672         return currentUser.isManagedProfile();
673     }
674 
675     /**
676      * Creates a {@link UserSpinnerAdapter} if there is more than one profile on the device.
677      *
678      * <p> The adapter can be used to populate a spinner that switches between the Settings
679      * app on the different profiles.
680      *
681      * @return a {@link UserSpinnerAdapter} or null if there is only one profile.
682      */
createUserSpinnerAdapter(UserManager userManager, Context context)683     public static UserSpinnerAdapter createUserSpinnerAdapter(UserManager userManager,
684             Context context) {
685         List<UserHandle> userProfiles = userManager.getUserProfiles();
686         if (userProfiles.size() < 2) {
687             return null;
688         }
689 
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);
694 
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));
699         }
700         return new UserSpinnerAdapter(context, userDetails);
701     }
702 
703     /**
704      * Returns the target user for a Settings activity.
705      *
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.
708      *
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.
712      */
getSecureTargetUser(IBinder activityToken, UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras)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();
717         try {
718             String launchedFromPackage = am.getLaunchedFromPackage(activityToken);
719             boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage);
720 
721             UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
722                     am.getLaunchedFromUid(activityToken)));
723             if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
724                 // Check it's secure
725                 if (isProfileOf(um, launchedFromUser)) {
726                     return launchedFromUser;
727                 }
728             }
729             UserHandle extrasUser = intentExtras != null
730                     ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
731             if (extrasUser != null && !extrasUser.equals(currentUser)) {
732                 // Check it's secure
733                 if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) {
734                     return extrasUser;
735                 }
736             }
737             UserHandle argumentsUser = arguments != null
738                     ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
739             if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
740                 // Check it's secure
741                 if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) {
742                     return argumentsUser;
743                 }
744             }
745         } catch (RemoteException e) {
746             // Should not happen
747             Log.v(TAG, "Could not talk to activity manager.", e);
748         }
749         return currentUser;
750    }
751 
752     /**
753      * Returns the target user for a Settings activity.
754      *
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.
757      *
758      * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if
759      * possible.
760      *
761      * @see #getInsecureTargetUser(IBinder, Bundle, Bundle)
762      */
getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments, @Nullable Bundle intentExtras)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();
767        try {
768            UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
769                    am.getLaunchedFromUid(activityToken)));
770            if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
771                return launchedFromUser;
772            }
773            UserHandle extrasUser = intentExtras != null
774                    ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
775            if (extrasUser != null && !extrasUser.equals(currentUser)) {
776                return extrasUser;
777            }
778            UserHandle argumentsUser = arguments != null
779                    ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
780            if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
781                return argumentsUser;
782            }
783        } catch (RemoteException e) {
784            // Should not happen
785            Log.v(TAG, "Could not talk to activity manager.", e);
786            return null;
787        }
788        return currentUser;
789    }
790 
791    /**
792     * Returns true if the user provided is in the same profiles group as the current user.
793     */
isProfileOf(UserManager um, UserHandle otherUser)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);
798    }
799 
800     /**
801      * Creates a dialog to confirm with the user if it's ok to remove the user
802      * and delete all the data.
803      *
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
808      */
createRemoveConfirmationDialog(Context context, int removingUserId, DialogInterface.OnClickListener onConfirmListener)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);
813         int titleResId;
814         int messageResId;
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;
824         } else {
825             titleResId = R.string.user_confirm_remove_title;
826             messageResId = R.string.user_confirm_remove_message;
827         }
828         Dialog dlg = new AlertDialog.Builder(context)
829                 .setTitle(titleResId)
830                 .setMessage(messageResId)
831                 .setPositiveButton(R.string.user_delete_button,
832                         onConfirmListener)
833                 .setNegativeButton(android.R.string.cancel, null)
834                 .create();
835         return dlg;
836     }
837 
838     /**
839      * Returns whether or not this device is able to be OEM unlocked.
840      */
isOemUnlockEnabled(Context context)841     static boolean isOemUnlockEnabled(Context context) {
842         PersistentDataBlockManager manager =(PersistentDataBlockManager)
843                 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
844         return manager.getOemUnlockEnabled();
845     }
846 
847     /**
848      * Allows enabling or disabling OEM unlock on this device. OEM unlocked
849      * devices allow users to flash other OSes to them.
850      */
setOemUnlockEnabled(Context context, boolean enabled)851     static void setOemUnlockEnabled(Context context, boolean enabled) {
852         PersistentDataBlockManager manager =(PersistentDataBlockManager)
853                 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
854         manager.setOemUnlockEnabled(enabled);
855     }
856 
857     /**
858      * Returns a circular icon for a user.
859      */
getUserIcon(Context context, UserManager um, UserInfo user)860     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
861         if (user.iconPath != null) {
862             Bitmap icon = um.getUserIcon(user.id);
863             if (icon != null) {
864                 return CircleFramedDrawable.getInstance(context, icon);
865             }
866         }
867         return UserIcons.getDefaultUserIcon(user.id, /* light= */ false);
868     }
869 
870     /**
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.
874      */
showSimCardTile(Context context)875     public static boolean showSimCardTile(Context context) {
876         final TelephonyManager tm =
877                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
878 
879         // TODO: Uncomment to re-enable SimSettings.
880         // return tm.getSimCount() > 0;
881         return false;
882     }
883 
884     /**
885      * Determine whether a package is a "system package", in which case certain things (like
886      * disabling notifications or disabling the package altogether) should be disallowed.
887      */
isSystemPackage(PackageManager pm, PackageInfo pkg)888     public static boolean isSystemPackage(PackageManager pm, PackageInfo pkg) {
889         if (sSystemSignature == null) {
890             sSystemSignature = new Signature[]{ getSystemSignature(pm) };
891         }
892         return sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg));
893     }
894 
895     private static Signature[] sSystemSignature;
896 
getFirstSignature(PackageInfo pkg)897     private static Signature getFirstSignature(PackageInfo pkg) {
898         if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
899             return pkg.signatures[0];
900         }
901         return null;
902     }
903 
getSystemSignature(PackageManager pm)904     private static Signature getSystemSignature(PackageManager pm) {
905         try {
906             final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
907             return getFirstSignature(sys);
908         } catch (NameNotFoundException e) {
909         }
910         return null;
911     }
912 
913     /**
914      * Returns elapsed time for the given millis, in the following format:
915      * 2d 5h 40m 29s
916      * @param context the application context
917      * @param millis the elapsed time in milli seconds
918      * @param withSeconds include seconds?
919      * @return the formatted elapsed time
920      */
formatElapsedTime(Context context, double millis, boolean withSeconds)921     public static String formatElapsedTime(Context context, double millis, boolean withSeconds) {
922         StringBuilder sb = new StringBuilder();
923         int seconds = (int) Math.floor(millis / 1000);
924         if (!withSeconds) {
925             // Round up.
926             seconds += 30;
927         }
928 
929         int days = 0, hours = 0, minutes = 0;
930         if (seconds >= SECONDS_PER_DAY) {
931             days = seconds / SECONDS_PER_DAY;
932             seconds -= days * SECONDS_PER_DAY;
933         }
934         if (seconds >= SECONDS_PER_HOUR) {
935             hours = seconds / SECONDS_PER_HOUR;
936             seconds -= hours * SECONDS_PER_HOUR;
937         }
938         if (seconds >= SECONDS_PER_MINUTE) {
939             minutes = seconds / SECONDS_PER_MINUTE;
940             seconds -= minutes * SECONDS_PER_MINUTE;
941         }
942         if (withSeconds) {
943             if (days > 0) {
944                 sb.append(context.getString(R.string.battery_history_days,
945                         days, hours, minutes, seconds));
946             } else if (hours > 0) {
947                 sb.append(context.getString(R.string.battery_history_hours,
948                         hours, minutes, seconds));
949             } else if (minutes > 0) {
950                 sb.append(context.getString(R.string.battery_history_minutes, minutes, seconds));
951             } else {
952                 sb.append(context.getString(R.string.battery_history_seconds, seconds));
953             }
954         } else {
955             if (days > 0) {
956                 sb.append(context.getString(R.string.battery_history_days_no_seconds,
957                         days, hours, minutes));
958             } else if (hours > 0) {
959                 sb.append(context.getString(R.string.battery_history_hours_no_seconds,
960                         hours, minutes));
961             } else {
962                 sb.append(context.getString(R.string.battery_history_minutes_no_seconds, minutes));
963             }
964         }
965         return sb.toString();
966     }
967 }
968