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