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.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      */
    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 
    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      */
    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      */
    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 
    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      */
    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      */
    286     public static String getDefaultIpAddresses(ConnectivityManager cm) {
    287         LinkProperties prop = cm.getActiveLinkProperties();
    288         return formatIpAddresses(prop);
    289     }
    290 
    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 
    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. */
    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. */
    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. */
    336     private static String formatPercentage(double percentage) {
    337       return NumberFormat.getPercentInstance().format(percentage);
    338     }
    339 
    340     public static boolean isBatteryPresent(Intent batteryChangedIntent) {
    341         return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
    342     }
    343 
    344     public static String getBatteryPercentage(Intent batteryChangedIntent) {
    345         return formatPercentage(getBatteryLevel(batteryChangedIntent));
    346     }
    347 
    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 
    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 
    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      */
    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 
    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      */
    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. */
    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 
    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 
    489     private static String getShorterNameIfPossible(Context context) {
    490         final String given = getLocalProfileGivenName(context);
    491         return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context);
    492     }
    493 
    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 
    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. */
    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 
    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      */
    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      */
    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 
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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     */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    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      */
   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      */
   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      */
   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