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.SubscriptionInfo;
64 import android.telephony.SubscriptionManager;
65 import android.telephony.TelephonyManager;
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.UserIcons;
74 import com.android.settings.UserSpinnerAdapter.UserDetails;
75 import com.android.settings.dashboard.DashboardTile;
76 import com.android.settings.drawable.CircleFramedDrawable;
77 
78 import java.io.IOException;
79 import java.io.InputStream;
80 import java.net.InetAddress;
81 import java.text.NumberFormat;
82 import java.util.ArrayList;
83 import java.util.Iterator;
84 import java.util.List;
85 import java.util.Locale;
86 
87 public final class Utils {
88     private static final String TAG = "Settings";
89 
90     /**
91      * Set the preference's title to the matching activity's label.
92      */
93     public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1;
94 
95     /**
96      * The opacity level of a disabled icon.
97      */
98     public static final float DISABLED_ALPHA = 0.4f;
99 
100     /**
101      * Color spectrum to use to indicate badness.  0 is completely transparent (no data),
102      * 1 is most bad (red), the last value is least bad (green).
103      */
104     public static final int[] BADNESS_COLORS = new int[] {
105             0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00,
106             0xfffabf2c, 0xff679e37, 0xff0a7f42
107     };
108 
109     /**
110      * Name of the meta-data item that should be set in the AndroidManifest.xml
111      * to specify the icon that should be displayed for the preference.
112      */
113     private static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
114 
115     /**
116      * Name of the meta-data item that should be set in the AndroidManifest.xml
117      * to specify the title that should be displayed for the preference.
118      */
119     private static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
120 
121     /**
122      * Name of the meta-data item that should be set in the AndroidManifest.xml
123      * to specify the summary text that should be displayed for the preference.
124      */
125     private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
126 
127     private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
128 
129     private static final int SECONDS_PER_MINUTE = 60;
130     private static final int SECONDS_PER_HOUR = 60 * 60;
131     private static final int SECONDS_PER_DAY = 24 * 60 * 60;
132 
133     /**
134      * Finds a matching activity for a preference's intent. If a matching
135      * activity is not found, it will remove the preference.
136      *
137      * @param context The context.
138      * @param parentPreferenceGroup The preference group that contains the
139      *            preference whose intent is being resolved.
140      * @param preferenceKey The key of the preference whose intent is being
141      *            resolved.
142      * @param flags 0 or one or more of
143      *            {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY}
144      *            .
145      * @return Whether an activity was found. If false, the preference was
146      *         removed.
147      */
updatePreferenceToSpecificActivityOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags)148     public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
149             PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
150 
151         Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
152         if (preference == null) {
153             return false;
154         }
155 
156         Intent intent = preference.getIntent();
157         if (intent != null) {
158             // Find the activity that is in the system image
159             PackageManager pm = context.getPackageManager();
160             List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
161             int listSize = list.size();
162             for (int i = 0; i < listSize; i++) {
163                 ResolveInfo resolveInfo = list.get(i);
164                 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
165                         != 0) {
166 
167                     // Replace the intent with this specific activity
168                     preference.setIntent(new Intent().setClassName(
169                             resolveInfo.activityInfo.packageName,
170                             resolveInfo.activityInfo.name));
171 
172                     if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) {
173                         // Set the preference title to the activity's label
174                         preference.setTitle(resolveInfo.loadLabel(pm));
175                     }
176 
177                     return true;
178                 }
179             }
180         }
181 
182         // Did not find a matching activity, so remove the preference
183         parentPreferenceGroup.removePreference(preference);
184 
185         return false;
186     }
187 
updateTileToSpecificActivityFromMetaDataOrRemove(Context context, DashboardTile tile)188     public static boolean updateTileToSpecificActivityFromMetaDataOrRemove(Context context,
189             DashboardTile tile) {
190 
191         Intent intent = tile.intent;
192         if (intent != null) {
193             // Find the activity that is in the system image
194             PackageManager pm = context.getPackageManager();
195             List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
196             int listSize = list.size();
197             for (int i = 0; i < listSize; i++) {
198                 ResolveInfo resolveInfo = list.get(i);
199                 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
200                         != 0) {
201                     Drawable icon = null;
202                     String title = null;
203                     String summary = null;
204 
205                     // Get the activity's meta-data
206                     try {
207                         Resources res = pm.getResourcesForApplication(
208                                 resolveInfo.activityInfo.packageName);
209                         Bundle metaData = resolveInfo.activityInfo.metaData;
210 
211                         if (res != null && metaData != null) {
212                             icon = res.getDrawable(
213                                     metaData.getInt(META_DATA_PREFERENCE_ICON), null);
214                             title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
215                             summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
216                         }
217                     } catch (NameNotFoundException e) {
218                         // Ignore
219                     } catch (NotFoundException e) {
220                         // Ignore
221                     }
222 
223                     // Set the preference title to the activity's label if no
224                     // meta-data is found
225                     if (TextUtils.isEmpty(title)) {
226                         title = resolveInfo.loadLabel(pm).toString();
227                     }
228 
229                     // Set icon, title and summary for the preference
230                     // TODO:
231                     //tile.icon = icon;
232                     tile.title = title;
233                     tile.summary = summary;
234                     // Replace the intent with this specific activity
235                     tile.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
236                             resolveInfo.activityInfo.name);
237 
238                     return true;
239                 }
240             }
241         }
242 
243         return false;
244     }
245 
246     /**
247      * Returns true if Monkey is running.
248      */
isMonkeyRunning()249     public static boolean isMonkeyRunning() {
250         return ActivityManager.isUserAMonkey();
251     }
252 
253     /**
254      * Returns whether the device is voice-capable (meaning, it is also a phone).
255      */
isVoiceCapable(Context context)256     public static boolean isVoiceCapable(Context context) {
257         TelephonyManager telephony =
258                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
259         return telephony != null && telephony.isVoiceCapable();
260     }
261 
isWifiOnly(Context context)262     public static boolean isWifiOnly(Context context) {
263         ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
264                 Context.CONNECTIVITY_SERVICE);
265         return (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
266     }
267 
268     /**
269      * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses.
270      * @param context the application context
271      * @return the formatted and newline-separated IP addresses, or null if none.
272      */
getWifiIpAddresses(Context context)273     public static String getWifiIpAddresses(Context context) {
274         ConnectivityManager cm = (ConnectivityManager)
275                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
276         LinkProperties prop = cm.getLinkProperties(ConnectivityManager.TYPE_WIFI);
277         return formatIpAddresses(prop);
278     }
279 
280     /**
281      * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
282      * addresses.
283      * @param context the application context
284      * @return the formatted and newline-separated IP addresses, or null if none.
285      */
getDefaultIpAddresses(ConnectivityManager cm)286     public static String getDefaultIpAddresses(ConnectivityManager cm) {
287         LinkProperties prop = cm.getActiveLinkProperties();
288         return formatIpAddresses(prop);
289     }
290 
formatIpAddresses(LinkProperties prop)291     private static String formatIpAddresses(LinkProperties prop) {
292         if (prop == null) return null;
293         Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
294         // If there are no entries, return null
295         if (!iter.hasNext()) return null;
296         // Concatenate all available addresses, comma separated
297         String addresses = "";
298         while (iter.hasNext()) {
299             addresses += iter.next().getHostAddress();
300             if (iter.hasNext()) addresses += "\n";
301         }
302         return addresses;
303     }
304 
createLocaleFromString(String localeStr)305     public static Locale createLocaleFromString(String localeStr) {
306         // TODO: is there a better way to actually construct a locale that will match?
307         // The main problem is, on top of Java specs, locale.toString() and
308         // new Locale(locale.toString()).toString() do not return equal() strings in
309         // many cases, because the constructor takes the only string as the language
310         // code. So : new Locale("en", "US").toString() => "en_US"
311         // And : new Locale("en_US").toString() => "en_us"
312         if (null == localeStr)
313             return Locale.getDefault();
314         String[] brokenDownLocale = localeStr.split("_", 3);
315         // split may not return a 0-length array.
316         if (1 == brokenDownLocale.length) {
317             return new Locale(brokenDownLocale[0]);
318         } else if (2 == brokenDownLocale.length) {
319             return new Locale(brokenDownLocale[0], brokenDownLocale[1]);
320         } else {
321             return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]);
322         }
323     }
324 
325     /** Formats the ratio of amount/total as a percentage. */
formatPercentage(long amount, long total)326     public static String formatPercentage(long amount, long total) {
327         return formatPercentage(((double) amount) / total);
328     }
329 
330     /** Formats an integer from 0..100 as a percentage. */
formatPercentage(int percentage)331     public static String formatPercentage(int percentage) {
332         return formatPercentage(((double) percentage) / 100.0);
333     }
334 
335     /** Formats a double from 0.0..1.0 as a percentage. */
formatPercentage(double percentage)336     private static String formatPercentage(double percentage) {
337       return NumberFormat.getPercentInstance().format(percentage);
338     }
339 
isBatteryPresent(Intent batteryChangedIntent)340     public static boolean isBatteryPresent(Intent batteryChangedIntent) {
341         return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
342     }
343 
getBatteryPercentage(Intent batteryChangedIntent)344     public static String getBatteryPercentage(Intent batteryChangedIntent) {
345         return formatPercentage(getBatteryLevel(batteryChangedIntent));
346     }
347 
getBatteryLevel(Intent batteryChangedIntent)348     public static int getBatteryLevel(Intent batteryChangedIntent) {
349         int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
350         int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
351         return (level * 100) / scale;
352     }
353 
getBatteryStatus(Resources res, Intent batteryChangedIntent)354     public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
355         final Intent intent = batteryChangedIntent;
356 
357         int plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
358         int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
359                 BatteryManager.BATTERY_STATUS_UNKNOWN);
360         String statusString;
361         if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
362             int resId;
363             if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
364                 resId = R.string.battery_info_status_charging_ac;
365             } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
366                 resId = R.string.battery_info_status_charging_usb;
367             } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
368                 resId = R.string.battery_info_status_charging_wireless;
369             } else {
370                 resId = R.string.battery_info_status_charging;
371             }
372             statusString = res.getString(resId);
373         } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
374             statusString = res.getString(R.string.battery_info_status_discharging);
375         } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
376             statusString = res.getString(R.string.battery_info_status_not_charging);
377         } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
378             statusString = res.getString(R.string.battery_info_status_full);
379         } else {
380             statusString = res.getString(R.string.battery_info_status_unknown);
381         }
382 
383         return statusString;
384     }
385 
forcePrepareCustomPreferencesList( ViewGroup parent, View child, ListView list, boolean ignoreSidePadding)386     public static void forcePrepareCustomPreferencesList(
387             ViewGroup parent, View child, ListView list, boolean ignoreSidePadding) {
388         list.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
389         list.setClipToPadding(false);
390         prepareCustomPreferencesList(parent, child, list, ignoreSidePadding);
391     }
392 
393     /**
394      * Prepare a custom preferences layout, moving padding to {@link ListView}
395      * when outside scrollbars are requested. Usually used to display
396      * {@link ListView} and {@link TabWidget} with correct padding.
397      */
prepareCustomPreferencesList( ViewGroup parent, View child, View list, boolean ignoreSidePadding)398     public static void prepareCustomPreferencesList(
399             ViewGroup parent, View child, View list, boolean ignoreSidePadding) {
400         final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY;
401         if (movePadding) {
402             final Resources res = list.getResources();
403             final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin);
404             final int paddingBottom = res.getDimensionPixelSize(
405                     com.android.internal.R.dimen.preference_fragment_padding_bottom);
406 
407             if (parent instanceof PreferenceFrameLayout) {
408                 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true;
409 
410                 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide;
411                 list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom);
412             } else {
413                 list.setPaddingRelative(paddingSide, 0, paddingSide, paddingBottom);
414             }
415         }
416     }
417 
forceCustomPadding(View view, boolean additive)418     public static void forceCustomPadding(View view, boolean additive) {
419         final Resources res = view.getResources();
420         final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin);
421 
422         final int paddingStart = paddingSide + (additive ? view.getPaddingStart() : 0);
423         final int paddingEnd = paddingSide + (additive ? view.getPaddingEnd() : 0);
424         final int paddingBottom = res.getDimensionPixelSize(
425                 com.android.internal.R.dimen.preference_fragment_padding_bottom);
426 
427         view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom);
428     }
429 
430     /**
431      * Return string resource that best describes combination of tethering
432      * options available on this device.
433      */
getTetheringLabel(ConnectivityManager cm)434     public static int getTetheringLabel(ConnectivityManager cm) {
435         String[] usbRegexs = cm.getTetherableUsbRegexs();
436         String[] wifiRegexs = cm.getTetherableWifiRegexs();
437         String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
438 
439         boolean usbAvailable = usbRegexs.length != 0;
440         boolean wifiAvailable = wifiRegexs.length != 0;
441         boolean bluetoothAvailable = bluetoothRegexs.length != 0;
442 
443         if (wifiAvailable && usbAvailable && bluetoothAvailable) {
444             return R.string.tether_settings_title_all;
445         } else if (wifiAvailable && usbAvailable) {
446             return R.string.tether_settings_title_all;
447         } else if (wifiAvailable && bluetoothAvailable) {
448             return R.string.tether_settings_title_all;
449         } else if (wifiAvailable) {
450             return R.string.tether_settings_title_wifi;
451         } else if (usbAvailable && bluetoothAvailable) {
452             return R.string.tether_settings_title_usb_bluetooth;
453         } else if (usbAvailable) {
454             return R.string.tether_settings_title_usb;
455         } else {
456             return R.string.tether_settings_title_bluetooth;
457         }
458     }
459 
460     /* Used by UserSettings as well. Call this on a non-ui thread. */
copyMeProfilePhoto(Context context, UserInfo user)461     public static boolean copyMeProfilePhoto(Context context, UserInfo user) {
462         Uri contactUri = Profile.CONTENT_URI;
463 
464         InputStream avatarDataStream = Contacts.openContactPhotoInputStream(
465                     context.getContentResolver(),
466                     contactUri, true);
467         // If there's no profile photo, assign a default avatar
468         if (avatarDataStream == null) {
469             return false;
470         }
471         int userId = user != null ? user.id : UserHandle.myUserId();
472         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
473         Bitmap icon = BitmapFactory.decodeStream(avatarDataStream);
474         um.setUserIcon(userId, icon);
475         try {
476             avatarDataStream.close();
477         } catch (IOException ioe) { }
478         return true;
479     }
480 
getMeProfileName(Context context, boolean full)481     public static String getMeProfileName(Context context, boolean full) {
482         if (full) {
483             return getProfileDisplayName(context);
484         } else {
485             return getShorterNameIfPossible(context);
486         }
487     }
488 
getShorterNameIfPossible(Context context)489     private static String getShorterNameIfPossible(Context context) {
490         final String given = getLocalProfileGivenName(context);
491         return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context);
492     }
493 
getLocalProfileGivenName(Context context)494     private static String getLocalProfileGivenName(Context context) {
495         final ContentResolver cr = context.getContentResolver();
496 
497         // Find the raw contact ID for the local ME profile raw contact.
498         final long localRowProfileId;
499         final Cursor localRawProfile = cr.query(
500                 Profile.CONTENT_RAW_CONTACTS_URI,
501                 new String[] {RawContacts._ID},
502                 RawContacts.ACCOUNT_TYPE + " IS NULL AND " +
503                         RawContacts.ACCOUNT_NAME + " IS NULL",
504                 null, null);
505         if (localRawProfile == null) return null;
506 
507         try {
508             if (!localRawProfile.moveToFirst()) {
509                 return null;
510             }
511             localRowProfileId = localRawProfile.getLong(0);
512         } finally {
513             localRawProfile.close();
514         }
515 
516         // Find the structured name for the raw contact.
517         final Cursor structuredName = cr.query(
518                 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(),
519                 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME,
520                     CommonDataKinds.StructuredName.FAMILY_NAME},
521                 Data.RAW_CONTACT_ID + "=" + localRowProfileId,
522                 null, null);
523         if (structuredName == null) return null;
524 
525         try {
526             if (!structuredName.moveToFirst()) {
527                 return null;
528             }
529             String partialName = structuredName.getString(0);
530             if (TextUtils.isEmpty(partialName)) {
531                 partialName = structuredName.getString(1);
532             }
533             return partialName;
534         } finally {
535             structuredName.close();
536         }
537     }
538 
getProfileDisplayName(Context context)539     private static final String getProfileDisplayName(Context context) {
540         final ContentResolver cr = context.getContentResolver();
541         final Cursor profile = cr.query(Profile.CONTENT_URI,
542                 new String[] {Profile.DISPLAY_NAME}, null, null, null);
543         if (profile == null) return null;
544 
545         try {
546             if (!profile.moveToFirst()) {
547                 return null;
548             }
549             return profile.getString(0);
550         } finally {
551             profile.close();
552         }
553     }
554 
555     /** Not global warming, it's global change warning. */
buildGlobalChangeWarningDialog(final Context context, int titleResId, final Runnable positiveAction)556     public static Dialog buildGlobalChangeWarningDialog(final Context context, int titleResId,
557             final Runnable positiveAction) {
558         final AlertDialog.Builder builder = new AlertDialog.Builder(context);
559         builder.setTitle(titleResId);
560         builder.setMessage(R.string.global_change_warning);
561         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
562             @Override
563             public void onClick(DialogInterface dialog, int which) {
564                 positiveAction.run();
565             }
566         });
567         builder.setNegativeButton(android.R.string.cancel, null);
568 
569         return builder.create();
570     }
571 
hasMultipleUsers(Context context)572     public static boolean hasMultipleUsers(Context context) {
573         return ((UserManager) context.getSystemService(Context.USER_SERVICE))
574                 .getUsers().size() > 1;
575     }
576 
577     /**
578      * Start a new instance of the activity, showing only the given fragment.
579      * When launched in this mode, the given preference fragment will be instantiated and fill the
580      * entire activity.
581      *
582      * @param context The context.
583      * @param fragmentName The name of the fragment to display.
584      * @param args Optional arguments to supply to the fragment.
585      * @param resultTo Option fragment that should receive the result of the activity launch.
586      * @param resultRequestCode If resultTo is non-null, this is the request code in which
587      *                          to report the result.
588      * @param titleResId resource id for the String to display for the title of this set
589      *                   of preferences.
590      * @param title String to display for the title of this set of preferences.
591      */
startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title)592     public static void startWithFragment(Context context, String fragmentName, Bundle args,
593             Fragment resultTo, int resultRequestCode, int titleResId,
594             CharSequence title) {
595         startWithFragment(context, fragmentName, args, resultTo, resultRequestCode,
596                 null /* titleResPackageName */, titleResId, title, false /* not a shortcut */);
597     }
598 
599     /**
600      * Start a new instance of the activity, showing only the given fragment.
601      * When launched in this mode, the given preference fragment will be instantiated and fill the
602      * entire activity.
603      *
604      * @param context The context.
605      * @param fragmentName The name of the fragment to display.
606      * @param args Optional arguments to supply to the fragment.
607      * @param resultTo Option fragment that should receive the result of the activity launch.
608      * @param resultRequestCode If resultTo is non-null, this is the request code in which
609      *                          to report the result.
610      * @param titleResPackageName Optional package name for the resource id of the title.
611      * @param titleResId resource id for the String to display for the title of this set
612      *                   of preferences.
613      * @param title String to display for the title of this set of preferences.
614      */
startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, CharSequence title)615     public static void startWithFragment(Context context, String fragmentName, Bundle args,
616             Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId,
617             CharSequence title) {
618         startWithFragment(context, fragmentName, args, resultTo, resultRequestCode,
619                 titleResPackageName, titleResId, title, false /* not a shortcut */);
620     }
621 
startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title, boolean isShortcut)622     public static void startWithFragment(Context context, String fragmentName, Bundle args,
623             Fragment resultTo, int resultRequestCode, int titleResId,
624             CharSequence title, boolean isShortcut) {
625         Intent intent = onBuildStartFragmentIntent(context, fragmentName, args,
626                 null /* titleResPackageName */, titleResId, title, isShortcut);
627         if (resultTo == null) {
628             context.startActivity(intent);
629         } else {
630             resultTo.startActivityForResult(intent, resultRequestCode);
631         }
632     }
633 
startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut)634     public static void startWithFragment(Context context, String fragmentName, Bundle args,
635             Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId,
636             CharSequence title, boolean isShortcut) {
637         Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResPackageName,
638                 titleResId, title, isShortcut);
639         if (resultTo == null) {
640             context.startActivity(intent);
641         } else {
642             resultTo.startActivityForResult(intent, resultRequestCode);
643         }
644     }
645 
startWithFragmentAsUser(Context context, String fragmentName, Bundle args, int titleResId, CharSequence title, boolean isShortcut, UserHandle userHandle)646     public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args,
647             int titleResId, CharSequence title, boolean isShortcut,
648             UserHandle userHandle) {
649         Intent intent = onBuildStartFragmentIntent(context, fragmentName, args,
650                 null /* titleResPackageName */, titleResId, title, isShortcut);
651         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
652         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
653         context.startActivityAsUser(intent, userHandle);
654     }
655 
startWithFragmentAsUser(Context context, String fragmentName, Bundle args, String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut, UserHandle userHandle)656     public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args,
657             String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut,
658             UserHandle userHandle) {
659         Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResPackageName,
660                 titleResId, title, isShortcut);
661         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
662         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
663         context.startActivityAsUser(intent, userHandle);
664     }
665 
666     /**
667      * Build an Intent to launch a new activity showing the selected fragment.
668      * The implementation constructs an Intent that re-launches the current activity with the
669      * appropriate arguments to display the fragment.
670      *
671      *
672      * @param context The Context.
673      * @param fragmentName The name of the fragment to display.
674      * @param args Optional arguments to supply to the fragment.
675      * @param titleResPackageName Optional package name for the resource id of the title.
676      * @param titleResId Optional title resource id to show for this item.
677      * @param title Optional title to show for this item.
678      * @param isShortcut  tell if this is a Launcher Shortcut or not
679      * @return Returns an Intent that can be launched to display the given
680      * fragment.
681      */
onBuildStartFragmentIntent(Context context, String fragmentName, Bundle args, String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut)682     public static Intent onBuildStartFragmentIntent(Context context, String fragmentName,
683             Bundle args, String titleResPackageName, int titleResId, CharSequence title,
684             boolean isShortcut) {
685         Intent intent = new Intent(Intent.ACTION_MAIN);
686         intent.setClass(context, SubSettings.class);
687         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName);
688         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
689         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME,
690                 titleResPackageName);
691         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId);
692         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
693         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut);
694         return intent;
695     }
696 
697     /**
698      * Returns the managed profile of the current user or null if none found.
699      */
getManagedProfile(UserManager userManager)700     public static UserHandle getManagedProfile(UserManager userManager) {
701         List<UserHandle> userProfiles = userManager.getUserProfiles();
702         final int count = userProfiles.size();
703         for (int i = 0; i < count; i++) {
704             final UserHandle profile = userProfiles.get(i);
705             if (profile.getIdentifier() == userManager.getUserHandle()) {
706                 continue;
707             }
708             final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier());
709             if (userInfo.isManagedProfile()) {
710                 return profile;
711             }
712         }
713         return null;
714     }
715 
716     /**
717      * Returns true if the current profile is a managed one.
718      */
isManagedProfile(UserManager userManager)719     public static boolean isManagedProfile(UserManager userManager) {
720         UserInfo currentUser = userManager.getUserInfo(userManager.getUserHandle());
721         return currentUser.isManagedProfile();
722     }
723 
724     /**
725      * Creates a {@link UserSpinnerAdapter} if there is more than one profile on the device.
726      *
727      * <p> The adapter can be used to populate a spinner that switches between the Settings
728      * app on the different profiles.
729      *
730      * @return a {@link UserSpinnerAdapter} or null if there is only one profile.
731      */
createUserSpinnerAdapter(UserManager userManager, Context context)732     public static UserSpinnerAdapter createUserSpinnerAdapter(UserManager userManager,
733             Context context) {
734         List<UserHandle> userProfiles = userManager.getUserProfiles();
735         if (userProfiles.size() < 2) {
736             return null;
737         }
738 
739         UserHandle myUserHandle = new UserHandle(UserHandle.myUserId());
740         // The first option should be the current profile
741         userProfiles.remove(myUserHandle);
742         userProfiles.add(0, myUserHandle);
743 
744         ArrayList<UserDetails> userDetails = new ArrayList<UserDetails>(userProfiles.size());
745         final int count = userProfiles.size();
746         for (int i = 0; i < count; i++) {
747             userDetails.add(new UserDetails(userProfiles.get(i), userManager, context));
748         }
749         return new UserSpinnerAdapter(context, userDetails);
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      * Note: This is secure in the sense that it only returns a target user different to the current
759      * one if the app launching this activity is the Settings app itself, running in the same user
760      * or in one that is in the same profile group, or if the user id is provided by the system.
761      */
getSecureTargetUser(IBinder activityToken, UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras)762     public static UserHandle getSecureTargetUser(IBinder activityToken,
763            UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) {
764         UserHandle currentUser = new UserHandle(UserHandle.myUserId());
765         IActivityManager am = ActivityManagerNative.getDefault();
766         try {
767             String launchedFromPackage = am.getLaunchedFromPackage(activityToken);
768             boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage);
769 
770             UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
771                     am.getLaunchedFromUid(activityToken)));
772             if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
773                 // Check it's secure
774                 if (isProfileOf(um, launchedFromUser)) {
775                     return launchedFromUser;
776                 }
777             }
778             UserHandle extrasUser = intentExtras != null
779                     ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
780             if (extrasUser != null && !extrasUser.equals(currentUser)) {
781                 // Check it's secure
782                 if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) {
783                     return extrasUser;
784                 }
785             }
786             UserHandle argumentsUser = arguments != null
787                     ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
788             if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
789                 // Check it's secure
790                 if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) {
791                     return argumentsUser;
792                 }
793             }
794         } catch (RemoteException e) {
795             // Should not happen
796             Log.v(TAG, "Could not talk to activity manager.", e);
797         }
798         return currentUser;
799    }
800 
801     /**
802      * Returns the target user for a Settings activity.
803      *
804      * The target user can be either the current user, the user that launched this activity or
805      * the user contained as an extra in the arguments or intent extras.
806      *
807      * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if
808      * possible.
809      *
810      * @see #getInsecureTargetUser(IBinder, Bundle, Bundle)
811      */
getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments, @Nullable Bundle intentExtras)812    public static UserHandle getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments,
813            @Nullable Bundle intentExtras) {
814        UserHandle currentUser = new UserHandle(UserHandle.myUserId());
815        IActivityManager am = ActivityManagerNative.getDefault();
816        try {
817            UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
818                    am.getLaunchedFromUid(activityToken)));
819            if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
820                return launchedFromUser;
821            }
822            UserHandle extrasUser = intentExtras != null
823                    ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
824            if (extrasUser != null && !extrasUser.equals(currentUser)) {
825                return extrasUser;
826            }
827            UserHandle argumentsUser = arguments != null
828                    ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
829            if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
830                return argumentsUser;
831            }
832        } catch (RemoteException e) {
833            // Should not happen
834            Log.v(TAG, "Could not talk to activity manager.", e);
835            return null;
836        }
837        return currentUser;
838    }
839 
840    /**
841     * Returns true if the user provided is in the same profiles group as the current user.
842     */
isProfileOf(UserManager um, UserHandle otherUser)843    private static boolean isProfileOf(UserManager um, UserHandle otherUser) {
844        if (um == null || otherUser == null) return false;
845        return (UserHandle.myUserId() == otherUser.getIdentifier())
846                || um.getUserProfiles().contains(otherUser);
847    }
848 
849     /**
850      * Creates a dialog to confirm with the user if it's ok to remove the user
851      * and delete all the data.
852      *
853      * @param context a Context object
854      * @param removingUserId The userId of the user to remove
855      * @param onConfirmListener Callback object for positive action
856      * @return the created Dialog
857      */
createRemoveConfirmationDialog(Context context, int removingUserId, DialogInterface.OnClickListener onConfirmListener)858     public static Dialog createRemoveConfirmationDialog(Context context, int removingUserId,
859             DialogInterface.OnClickListener onConfirmListener) {
860         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
861         UserInfo userInfo = um.getUserInfo(removingUserId);
862         int titleResId;
863         int messageResId;
864         if (UserHandle.myUserId() == removingUserId) {
865             titleResId = R.string.user_confirm_remove_self_title;
866             messageResId = R.string.user_confirm_remove_self_message;
867         } else if (userInfo.isRestricted()) {
868             titleResId = R.string.user_profile_confirm_remove_title;
869             messageResId = R.string.user_profile_confirm_remove_message;
870         } else if (userInfo.isManagedProfile()) {
871             titleResId = R.string.work_profile_confirm_remove_title;
872             messageResId = R.string.work_profile_confirm_remove_message;
873         } else {
874             titleResId = R.string.user_confirm_remove_title;
875             messageResId = R.string.user_confirm_remove_message;
876         }
877         Dialog dlg = new AlertDialog.Builder(context)
878                 .setTitle(titleResId)
879                 .setMessage(messageResId)
880                 .setPositiveButton(R.string.user_delete_button,
881                         onConfirmListener)
882                 .setNegativeButton(android.R.string.cancel, null)
883                 .create();
884         return dlg;
885     }
886 
887     /**
888      * Returns whether or not this device is able to be OEM unlocked.
889      */
isOemUnlockEnabled(Context context)890     static boolean isOemUnlockEnabled(Context context) {
891         PersistentDataBlockManager manager =(PersistentDataBlockManager)
892                 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
893         return manager.getOemUnlockEnabled();
894     }
895 
896     /**
897      * Allows enabling or disabling OEM unlock on this device. OEM unlocked
898      * devices allow users to flash other OSes to them.
899      */
setOemUnlockEnabled(Context context, boolean enabled)900     static void setOemUnlockEnabled(Context context, boolean enabled) {
901         PersistentDataBlockManager manager =(PersistentDataBlockManager)
902                 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
903         manager.setOemUnlockEnabled(enabled);
904     }
905 
906     /**
907      * Returns a circular icon for a user.
908      */
getUserIcon(Context context, UserManager um, UserInfo user)909     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
910         if (user.isManagedProfile()) {
911             // We use predefined values for managed profiles
912             Bitmap b = BitmapFactory.decodeResource(context.getResources(),
913                     com.android.internal.R.drawable.ic_corp_icon);
914             return CircleFramedDrawable.getInstance(context, b);
915         }
916         if (user.iconPath != null) {
917             Bitmap icon = um.getUserIcon(user.id);
918             if (icon != null) {
919                 return CircleFramedDrawable.getInstance(context, icon);
920             }
921         }
922         return UserIcons.getDefaultUserIcon(user.id, /* light= */ false);
923     }
924 
925     /**
926      * Returns a label for the user, in the form of "User: user name" or "Work profile".
927      */
getUserLabel(Context context, UserInfo info)928     public static String getUserLabel(Context context, UserInfo info) {
929         if (info.isManagedProfile()) {
930             // We use predefined values for managed profiles
931             return context.getString(R.string.managed_user_title);
932         }
933         String name = info != null ? info.name : null;
934         if (name == null && info != null) {
935             name = Integer.toString(info.id);
936         } else if (info == null) {
937             name = context.getString(R.string.unknown);
938         }
939         return context.getResources().getString(R.string.running_process_item_user_label, name);
940     }
941 
942     /**
943      * Return whether or not the user should have a SIM Cards option in Settings.
944      * TODO: Change back to returning true if count is greater than one after testing.
945      * TODO: See bug 16533525.
946      */
showSimCardTile(Context context)947     public static boolean showSimCardTile(Context context) {
948         final TelephonyManager tm =
949                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
950 
951         return tm.getSimCount() > 1;
952     }
953 
954     /**
955      * Determine whether a package is a "system package", in which case certain things (like
956      * disabling notifications or disabling the package altogether) should be disallowed.
957      */
isSystemPackage(PackageManager pm, PackageInfo pkg)958     public static boolean isSystemPackage(PackageManager pm, PackageInfo pkg) {
959         if (sSystemSignature == null) {
960             sSystemSignature = new Signature[]{ getSystemSignature(pm) };
961         }
962         return sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg));
963     }
964 
965     private static Signature[] sSystemSignature;
966 
getFirstSignature(PackageInfo pkg)967     private static Signature getFirstSignature(PackageInfo pkg) {
968         if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
969             return pkg.signatures[0];
970         }
971         return null;
972     }
973 
getSystemSignature(PackageManager pm)974     private static Signature getSystemSignature(PackageManager pm) {
975         try {
976             final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
977             return getFirstSignature(sys);
978         } catch (NameNotFoundException e) {
979         }
980         return null;
981     }
982 
983     /**
984      * Returns elapsed time for the given millis, in the following format:
985      * 2d 5h 40m 29s
986      * @param context the application context
987      * @param millis the elapsed time in milli seconds
988      * @param withSeconds include seconds?
989      * @return the formatted elapsed time
990      */
formatElapsedTime(Context context, double millis, boolean withSeconds)991     public static String formatElapsedTime(Context context, double millis, boolean withSeconds) {
992         StringBuilder sb = new StringBuilder();
993         int seconds = (int) Math.floor(millis / 1000);
994         if (!withSeconds) {
995             // Round up.
996             seconds += 30;
997         }
998 
999         int days = 0, hours = 0, minutes = 0;
1000         if (seconds >= SECONDS_PER_DAY) {
1001             days = seconds / SECONDS_PER_DAY;
1002             seconds -= days * SECONDS_PER_DAY;
1003         }
1004         if (seconds >= SECONDS_PER_HOUR) {
1005             hours = seconds / SECONDS_PER_HOUR;
1006             seconds -= hours * SECONDS_PER_HOUR;
1007         }
1008         if (seconds >= SECONDS_PER_MINUTE) {
1009             minutes = seconds / SECONDS_PER_MINUTE;
1010             seconds -= minutes * SECONDS_PER_MINUTE;
1011         }
1012         if (withSeconds) {
1013             if (days > 0) {
1014                 sb.append(context.getString(R.string.battery_history_days,
1015                         days, hours, minutes, seconds));
1016             } else if (hours > 0) {
1017                 sb.append(context.getString(R.string.battery_history_hours,
1018                         hours, minutes, seconds));
1019             } else if (minutes > 0) {
1020                 sb.append(context.getString(R.string.battery_history_minutes, minutes, seconds));
1021             } else {
1022                 sb.append(context.getString(R.string.battery_history_seconds, seconds));
1023             }
1024         } else {
1025             if (days > 0) {
1026                 sb.append(context.getString(R.string.battery_history_days_no_seconds,
1027                         days, hours, minutes));
1028             } else if (hours > 0) {
1029                 sb.append(context.getString(R.string.battery_history_hours_no_seconds,
1030                         hours, minutes));
1031             } else {
1032                 sb.append(context.getString(R.string.battery_history_minutes_no_seconds, minutes));
1033             }
1034         }
1035         return sb.toString();
1036     }
1037 
1038     /**
1039      * finds a record with subId.
1040      * Since the number of SIMs are few, an array is fine.
1041      */
findRecordBySubId(Context context, final int subId)1042     public static SubscriptionInfo findRecordBySubId(Context context, final int subId) {
1043         final List<SubscriptionInfo> subInfoList =
1044                 SubscriptionManager.from(context).getActiveSubscriptionInfoList();
1045         if (subInfoList != null) {
1046             final int subInfoLength = subInfoList.size();
1047 
1048             for (int i = 0; i < subInfoLength; ++i) {
1049                 final SubscriptionInfo sir = subInfoList.get(i);
1050                 if (sir != null && sir.getSubscriptionId() == subId) {
1051                     return sir;
1052                 }
1053             }
1054         }
1055 
1056         return null;
1057     }
1058 
1059     /**
1060      * finds a record with slotId.
1061      * Since the number of SIMs are few, an array is fine.
1062      */
findRecordBySlotId(Context context, final int slotId)1063     public static SubscriptionInfo findRecordBySlotId(Context context, final int slotId) {
1064         final List<SubscriptionInfo> subInfoList =
1065                 SubscriptionManager.from(context).getActiveSubscriptionInfoList();
1066         if (subInfoList != null) {
1067             final int subInfoLength = subInfoList.size();
1068 
1069             for (int i = 0; i < subInfoLength; ++i) {
1070                 final SubscriptionInfo sir = subInfoList.get(i);
1071                 if (sir.getSimSlotIndex() == slotId) {
1072                     //Right now we take the first subscription on a SIM.
1073                     return sir;
1074                 }
1075             }
1076         }
1077 
1078         return null;
1079     }
1080 
1081     /**
1082      * Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed).
1083      * @param userManager Instance of UserManager
1084      * @param checkUser The user to check the existence of.
1085      * @return UserInfo of the user or null for non-existent user.
1086      */
getExistingUser(UserManager userManager, UserHandle checkUser)1087     public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) {
1088         final List<UserInfo> users = userManager.getUsers(true /* excludeDying */);
1089         final int checkUserId = checkUser.getIdentifier();
1090         for (UserInfo user : users) {
1091             if (user.id == checkUserId) {
1092                 return user;
1093             }
1094         }
1095         return null;
1096     }
1097 
1098 }
1099