Home | History | Annotate | Download | only in settings
      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      */
    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 
    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      */
    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      */
    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 
    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      */
    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      */
    287     public static String getDefaultIpAddresses(ConnectivityManager cm) {
    288         LinkProperties prop = cm.getActiveLinkProperties();
    289         return formatIpAddresses(prop);
    290     }
    291 
    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 
    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. */
    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. */
    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. */
    337     private static String formatPercentage(double percentage) {
    338       BidiFormatter bf = BidiFormatter.getInstance();
    339       return bf.unicodeWrap(NumberFormat.getPercentInstance().format(percentage));
    340     }
    341 
    342     public static boolean isBatteryPresent(Intent batteryChangedIntent) {
    343         return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
    344     }
    345 
    346     public static String getBatteryPercentage(Intent batteryChangedIntent) {
    347         return formatPercentage(getBatteryLevel(batteryChangedIntent));
    348     }
    349 
    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 
    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 
    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      */
    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 
    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      */
    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. */
    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 
    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 
    491     private static String getShorterNameIfPossible(Context context) {
    492         final String given = getLocalProfileGivenName(context);
    493         return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context);
    494     }
    495 
    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 
    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. */
    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 
    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      */
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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     */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    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