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