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