1 /** 2 * Copyright (C) 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17 package com.android.settings; 18 19 import static android.content.Intent.EXTRA_USER; 20 21 import android.annotation.Nullable; 22 import android.app.ActivityManager; 23 import android.app.ActivityManagerNative; 24 import android.app.AlertDialog; 25 import android.app.Dialog; 26 import android.app.Fragment; 27 import android.app.IActivityManager; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.DialogInterface; 31 import android.content.Intent; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageInfo; 34 import android.content.pm.PackageManager; 35 import android.content.pm.PackageManager.NameNotFoundException; 36 import android.content.pm.ResolveInfo; 37 import android.content.pm.Signature; 38 import android.content.pm.UserInfo; 39 import android.content.res.Resources; 40 import android.content.res.Resources.NotFoundException; 41 import android.database.Cursor; 42 import android.graphics.Bitmap; 43 import android.graphics.BitmapFactory; 44 import android.graphics.drawable.Drawable; 45 import android.net.ConnectivityManager; 46 import android.net.LinkProperties; 47 import android.net.Uri; 48 import android.os.BatteryManager; 49 import android.os.Bundle; 50 import android.os.IBinder; 51 import android.os.RemoteException; 52 import android.os.UserHandle; 53 import android.os.UserManager; 54 import android.preference.Preference; 55 import android.preference.PreferenceFrameLayout; 56 import android.preference.PreferenceGroup; 57 import android.provider.ContactsContract.CommonDataKinds; 58 import android.provider.ContactsContract.Contacts; 59 import android.provider.ContactsContract.Data; 60 import android.provider.ContactsContract.Profile; 61 import android.provider.ContactsContract.RawContacts; 62 import android.service.persistentdata.PersistentDataBlockManager; 63 import android.telephony.TelephonyManager; 64 import android.text.BidiFormatter; 65 import android.text.TextDirectionHeuristics; 66 import android.text.TextUtils; 67 import android.util.Log; 68 import android.view.View; 69 import android.view.ViewGroup; 70 import android.widget.ListView; 71 import android.widget.TabWidget; 72 73 import com.android.internal.util.ImageUtils; 74 import com.android.internal.util.UserIcons; 75 import com.android.settings.UserSpinnerAdapter.UserDetails; 76 import com.android.settings.dashboard.DashboardCategory; 77 import com.android.settings.dashboard.DashboardTile; 78 import com.android.settings.drawable.CircleFramedDrawable; 79 80 import java.io.IOException; 81 import java.io.InputStream; 82 import java.net.InetAddress; 83 import java.text.NumberFormat; 84 import java.util.ArrayList; 85 import java.util.Iterator; 86 import java.util.List; 87 import java.util.Locale; 88 89 public final class Utils { 90 private static final String TAG = "Settings"; 91 92 /** 93 * Set the preference's title to the matching activity's label. 94 */ 95 public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1; 96 97 /** 98 * The opacity level of a disabled icon. 99 */ 100 public static final float DISABLED_ALPHA = 0.4f; 101 102 /** 103 * Color spectrum to use to indicate badness. 0 is completely transparent (no data), 104 * 1 is most bad (red), the last value is least bad (green). 105 */ 106 public static final int[] BADNESS_COLORS = new int[] { 107 0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00, 108 0xfffabf2c, 0xff679e37, 0xff0a7f42 109 }; 110 111 /** 112 * Name of the meta-data item that should be set in the AndroidManifest.xml 113 * to specify the icon that should be displayed for the preference. 114 */ 115 private static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon"; 116 117 /** 118 * Name of the meta-data item that should be set in the AndroidManifest.xml 119 * to specify the title that should be displayed for the preference. 120 */ 121 private static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title"; 122 123 /** 124 * Name of the meta-data item that should be set in the AndroidManifest.xml 125 * to specify the summary text that should be displayed for the preference. 126 */ 127 private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary"; 128 129 private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; 130 131 private static final int SECONDS_PER_MINUTE = 60; 132 private static final int SECONDS_PER_HOUR = 60 * 60; 133 private static final int SECONDS_PER_DAY = 24 * 60 * 60; 134 135 /** 136 * Finds a matching activity for a preference's intent. If a matching 137 * activity is not found, it will remove the preference. 138 * 139 * @param context The context. 140 * @param parentPreferenceGroup The preference group that contains the 141 * preference whose intent is being resolved. 142 * @param preferenceKey The key of the preference whose intent is being 143 * resolved. 144 * @param flags 0 or one or more of 145 * {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY} 146 * . 147 * @return Whether an activity was found. If false, the preference was 148 * removed. 149 */ updatePreferenceToSpecificActivityOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags)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 updateTileToSpecificActivityFromMetaDataOrRemove(Context context, DashboardTile tile)190 public static boolean updateTileToSpecificActivityFromMetaDataOrRemove(Context context, 191 DashboardTile tile) { 192 193 Intent intent = tile.intent; 194 if (intent != null) { 195 // Find the activity that is in the system image 196 PackageManager pm = context.getPackageManager(); 197 List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); 198 int listSize = list.size(); 199 for (int i = 0; i < listSize; i++) { 200 ResolveInfo resolveInfo = list.get(i); 201 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 202 != 0) { 203 Drawable icon = null; 204 String title = null; 205 String summary = null; 206 207 // Get the activity's meta-data 208 try { 209 Resources res = pm.getResourcesForApplication( 210 resolveInfo.activityInfo.packageName); 211 Bundle metaData = resolveInfo.activityInfo.metaData; 212 213 if (res != null && metaData != null) { 214 icon = res.getDrawable(metaData.getInt(META_DATA_PREFERENCE_ICON)); 215 title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); 216 summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY)); 217 } 218 } catch (NameNotFoundException e) { 219 // Ignore 220 } catch (NotFoundException e) { 221 // Ignore 222 } 223 224 // Set the preference title to the activity's label if no 225 // meta-data is found 226 if (TextUtils.isEmpty(title)) { 227 title = resolveInfo.loadLabel(pm).toString(); 228 } 229 230 // Set icon, title and summary for the preference 231 // TODO: 232 //tile.icon = icon; 233 tile.title = title; 234 tile.summary = summary; 235 // Replace the intent with this specific activity 236 tile.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName, 237 resolveInfo.activityInfo.name); 238 239 return true; 240 } 241 } 242 } 243 244 return false; 245 } 246 247 /** 248 * Returns true if Monkey is running. 249 */ isMonkeyRunning()250 public static boolean isMonkeyRunning() { 251 return ActivityManager.isUserAMonkey(); 252 } 253 254 /** 255 * Returns whether the device is voice-capable (meaning, it is also a phone). 256 */ isVoiceCapable(Context context)257 public static boolean isVoiceCapable(Context context) { 258 TelephonyManager telephony = 259 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 260 return telephony != null && telephony.isVoiceCapable(); 261 } 262 isWifiOnly(Context context)263 public static boolean isWifiOnly(Context context) { 264 ConnectivityManager cm = (ConnectivityManager)context.getSystemService( 265 Context.CONNECTIVITY_SERVICE); 266 return (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); 267 } 268 269 /** 270 * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses. 271 * @param context the application context 272 * @return the formatted and newline-separated IP addresses, or null if none. 273 */ getWifiIpAddresses(Context context)274 public static String getWifiIpAddresses(Context context) { 275 ConnectivityManager cm = (ConnectivityManager) 276 context.getSystemService(Context.CONNECTIVITY_SERVICE); 277 LinkProperties prop = cm.getLinkProperties(ConnectivityManager.TYPE_WIFI); 278 return formatIpAddresses(prop); 279 } 280 281 /** 282 * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style 283 * addresses. 284 * @param context the application context 285 * @return the formatted and newline-separated IP addresses, or null if none. 286 */ getDefaultIpAddresses(ConnectivityManager cm)287 public static String getDefaultIpAddresses(ConnectivityManager cm) { 288 LinkProperties prop = cm.getActiveLinkProperties(); 289 return formatIpAddresses(prop); 290 } 291 formatIpAddresses(LinkProperties prop)292 private static String formatIpAddresses(LinkProperties prop) { 293 if (prop == null) return null; 294 Iterator<InetAddress> iter = prop.getAllAddresses().iterator(); 295 // If there are no entries, return null 296 if (!iter.hasNext()) return null; 297 // Concatenate all available addresses, comma separated 298 String addresses = ""; 299 while (iter.hasNext()) { 300 addresses += iter.next().getHostAddress(); 301 if (iter.hasNext()) addresses += "\n"; 302 } 303 return addresses; 304 } 305 createLocaleFromString(String localeStr)306 public static Locale createLocaleFromString(String localeStr) { 307 // TODO: is there a better way to actually construct a locale that will match? 308 // The main problem is, on top of Java specs, locale.toString() and 309 // new Locale(locale.toString()).toString() do not return equal() strings in 310 // many cases, because the constructor takes the only string as the language 311 // code. So : new Locale("en", "US").toString() => "en_US" 312 // And : new Locale("en_US").toString() => "en_us" 313 if (null == localeStr) 314 return Locale.getDefault(); 315 String[] brokenDownLocale = localeStr.split("_", 3); 316 // split may not return a 0-length array. 317 if (1 == brokenDownLocale.length) { 318 return new Locale(brokenDownLocale[0]); 319 } else if (2 == brokenDownLocale.length) { 320 return new Locale(brokenDownLocale[0], brokenDownLocale[1]); 321 } else { 322 return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]); 323 } 324 } 325 326 /** Formats the ratio of amount/total as a percentage. */ formatPercentage(long amount, long total)327 public static String formatPercentage(long amount, long total) { 328 return formatPercentage(((double) amount) / total); 329 } 330 331 /** Formats an integer from 0..100 as a percentage. */ formatPercentage(int percentage)332 public static String formatPercentage(int percentage) { 333 return formatPercentage(((double) percentage) / 100.0); 334 } 335 336 /** Formats a double from 0.0..1.0 as a percentage. */ formatPercentage(double percentage)337 private static String formatPercentage(double percentage) { 338 BidiFormatter bf = BidiFormatter.getInstance(); 339 return bf.unicodeWrap(NumberFormat.getPercentInstance().format(percentage)); 340 } 341 isBatteryPresent(Intent batteryChangedIntent)342 public static boolean isBatteryPresent(Intent batteryChangedIntent) { 343 return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); 344 } 345 getBatteryPercentage(Intent batteryChangedIntent)346 public static String getBatteryPercentage(Intent batteryChangedIntent) { 347 return formatPercentage(getBatteryLevel(batteryChangedIntent)); 348 } 349 getBatteryLevel(Intent batteryChangedIntent)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 getBatteryStatus(Resources res, Intent batteryChangedIntent)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 forcePrepareCustomPreferencesList( ViewGroup parent, View child, ListView list, boolean ignoreSidePadding)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 */ prepareCustomPreferencesList( ViewGroup parent, View child, View list, boolean ignoreSidePadding)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 forceCustomPadding(View view, boolean additive)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 */ getTetheringLabel(ConnectivityManager cm)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. */ copyMeProfilePhoto(Context context, UserInfo user)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 getMeProfileName(Context context, boolean full)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 getShorterNameIfPossible(Context context)491 private static String getShorterNameIfPossible(Context context) { 492 final String given = getLocalProfileGivenName(context); 493 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context); 494 } 495 getLocalProfileGivenName(Context context)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 getProfileDisplayName(Context context)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. */ buildGlobalChangeWarningDialog(final Context context, int titleResId, final Runnable positiveAction)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 hasMultipleUsers(Context context)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 */ startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title)594 public static void startWithFragment(Context context, String fragmentName, Bundle args, 595 Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title) { 596 startWithFragment(context, fragmentName, args, resultTo, resultRequestCode, 597 titleResId, title, false /* not a shortcut */); 598 } 599 startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title, boolean isShortcut)600 public static void startWithFragment(Context context, String fragmentName, Bundle args, 601 Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title, 602 boolean isShortcut) { 603 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResId, 604 title, isShortcut); 605 if (resultTo == null) { 606 context.startActivity(intent); 607 } else { 608 resultTo.startActivityForResult(intent, resultRequestCode); 609 } 610 } 611 startWithFragmentAsUser(Context context, String fragmentName, Bundle args, int titleResId, CharSequence title, boolean isShortcut, UserHandle userHandle)612 public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args, 613 int titleResId, CharSequence title, boolean isShortcut, UserHandle userHandle) { 614 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResId, 615 title, isShortcut); 616 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 617 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 618 context.startActivityAsUser(intent, userHandle); 619 } 620 621 /** 622 * Build an Intent to launch a new activity showing the selected fragment. 623 * The implementation constructs an Intent that re-launches the current activity with the 624 * appropriate arguments to display the fragment. 625 * 626 * 627 * @param context The Context. 628 * @param fragmentName The name of the fragment to display. 629 * @param args Optional arguments to supply to the fragment. 630 * @param titleResId Optional title resource id to show for this item. 631 * @param title Optional title to show for this item. 632 * @param isShortcut tell if this is a Launcher Shortcut or not 633 * @return Returns an Intent that can be launched to display the given 634 * fragment. 635 */ onBuildStartFragmentIntent(Context context, String fragmentName, Bundle args, int titleResId, CharSequence title, boolean isShortcut)636 public static Intent onBuildStartFragmentIntent(Context context, String fragmentName, 637 Bundle args, int titleResId, CharSequence title, boolean isShortcut) { 638 Intent intent = new Intent(Intent.ACTION_MAIN); 639 intent.setClass(context, SubSettings.class); 640 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName); 641 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 642 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId); 643 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title); 644 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut); 645 return intent; 646 } 647 648 /** 649 * Returns the managed profile of the current user or null if none found. 650 */ getManagedProfile(UserManager userManager)651 public static UserHandle getManagedProfile(UserManager userManager) { 652 List<UserHandle> userProfiles = userManager.getUserProfiles(); 653 final int count = userProfiles.size(); 654 for (int i = 0; i < count; i++) { 655 final UserHandle profile = userProfiles.get(i); 656 if (profile.getIdentifier() == userManager.getUserHandle()) { 657 continue; 658 } 659 final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier()); 660 if (userInfo.isManagedProfile()) { 661 return profile; 662 } 663 } 664 return null; 665 } 666 667 /** 668 * Returns true if the current profile is a managed one. 669 */ isManagedProfile(UserManager userManager)670 public static boolean isManagedProfile(UserManager userManager) { 671 UserInfo currentUser = userManager.getUserInfo(userManager.getUserHandle()); 672 return currentUser.isManagedProfile(); 673 } 674 675 /** 676 * Creates a {@link UserSpinnerAdapter} if there is more than one profile on the device. 677 * 678 * <p> The adapter can be used to populate a spinner that switches between the Settings 679 * app on the different profiles. 680 * 681 * @return a {@link UserSpinnerAdapter} or null if there is only one profile. 682 */ createUserSpinnerAdapter(UserManager userManager, Context context)683 public static UserSpinnerAdapter createUserSpinnerAdapter(UserManager userManager, 684 Context context) { 685 List<UserHandle> userProfiles = userManager.getUserProfiles(); 686 if (userProfiles.size() < 2) { 687 return null; 688 } 689 690 UserHandle myUserHandle = new UserHandle(UserHandle.myUserId()); 691 // The first option should be the current profile 692 userProfiles.remove(myUserHandle); 693 userProfiles.add(0, myUserHandle); 694 695 ArrayList<UserDetails> userDetails = new ArrayList<UserDetails>(userProfiles.size()); 696 final int count = userProfiles.size(); 697 for (int i = 0; i < count; i++) { 698 userDetails.add(new UserDetails(userProfiles.get(i), userManager, context)); 699 } 700 return new UserSpinnerAdapter(context, userDetails); 701 } 702 703 /** 704 * Returns the target user for a Settings activity. 705 * 706 * The target user can be either the current user, the user that launched this activity or 707 * the user contained as an extra in the arguments or intent extras. 708 * 709 * Note: This is secure in the sense that it only returns a target user different to the current 710 * one if the app launching this activity is the Settings app itself, running in the same user 711 * or in one that is in the same profile group, or if the user id is provided by the system. 712 */ getSecureTargetUser(IBinder activityToken, UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras)713 public static UserHandle getSecureTargetUser(IBinder activityToken, 714 UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) { 715 UserHandle currentUser = new UserHandle(UserHandle.myUserId()); 716 IActivityManager am = ActivityManagerNative.getDefault(); 717 try { 718 String launchedFromPackage = am.getLaunchedFromPackage(activityToken); 719 boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage); 720 721 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId( 722 am.getLaunchedFromUid(activityToken))); 723 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) { 724 // Check it's secure 725 if (isProfileOf(um, launchedFromUser)) { 726 return launchedFromUser; 727 } 728 } 729 UserHandle extrasUser = intentExtras != null 730 ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null; 731 if (extrasUser != null && !extrasUser.equals(currentUser)) { 732 // Check it's secure 733 if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) { 734 return extrasUser; 735 } 736 } 737 UserHandle argumentsUser = arguments != null 738 ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null; 739 if (argumentsUser != null && !argumentsUser.equals(currentUser)) { 740 // Check it's secure 741 if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) { 742 return argumentsUser; 743 } 744 } 745 } catch (RemoteException e) { 746 // Should not happen 747 Log.v(TAG, "Could not talk to activity manager.", e); 748 } 749 return currentUser; 750 } 751 752 /** 753 * Returns the target user for a Settings activity. 754 * 755 * The target user can be either the current user, the user that launched this activity or 756 * the user contained as an extra in the arguments or intent extras. 757 * 758 * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if 759 * possible. 760 * 761 * @see #getInsecureTargetUser(IBinder, Bundle, Bundle) 762 */ getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments, @Nullable Bundle intentExtras)763 public static UserHandle getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments, 764 @Nullable Bundle intentExtras) { 765 UserHandle currentUser = new UserHandle(UserHandle.myUserId()); 766 IActivityManager am = ActivityManagerNative.getDefault(); 767 try { 768 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId( 769 am.getLaunchedFromUid(activityToken))); 770 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) { 771 return launchedFromUser; 772 } 773 UserHandle extrasUser = intentExtras != null 774 ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null; 775 if (extrasUser != null && !extrasUser.equals(currentUser)) { 776 return extrasUser; 777 } 778 UserHandle argumentsUser = arguments != null 779 ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null; 780 if (argumentsUser != null && !argumentsUser.equals(currentUser)) { 781 return argumentsUser; 782 } 783 } catch (RemoteException e) { 784 // Should not happen 785 Log.v(TAG, "Could not talk to activity manager.", e); 786 return null; 787 } 788 return currentUser; 789 } 790 791 /** 792 * Returns true if the user provided is in the same profiles group as the current user. 793 */ isProfileOf(UserManager um, UserHandle otherUser)794 private static boolean isProfileOf(UserManager um, UserHandle otherUser) { 795 if (um == null || otherUser == null) return false; 796 return (UserHandle.myUserId() == otherUser.getIdentifier()) 797 || um.getUserProfiles().contains(otherUser); 798 } 799 800 /** 801 * Creates a dialog to confirm with the user if it's ok to remove the user 802 * and delete all the data. 803 * 804 * @param context a Context object 805 * @param removingUserId The userId of the user to remove 806 * @param onConfirmListener Callback object for positive action 807 * @return the created Dialog 808 */ createRemoveConfirmationDialog(Context context, int removingUserId, DialogInterface.OnClickListener onConfirmListener)809 public static Dialog createRemoveConfirmationDialog(Context context, int removingUserId, 810 DialogInterface.OnClickListener onConfirmListener) { 811 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 812 UserInfo userInfo = um.getUserInfo(removingUserId); 813 int titleResId; 814 int messageResId; 815 if (UserHandle.myUserId() == removingUserId) { 816 titleResId = R.string.user_confirm_remove_self_title; 817 messageResId = R.string.user_confirm_remove_self_message; 818 } else if (userInfo.isRestricted()) { 819 titleResId = R.string.user_profile_confirm_remove_title; 820 messageResId = R.string.user_profile_confirm_remove_message; 821 } else if (userInfo.isManagedProfile()) { 822 titleResId = R.string.work_profile_confirm_remove_title; 823 messageResId = R.string.work_profile_confirm_remove_message; 824 } else { 825 titleResId = R.string.user_confirm_remove_title; 826 messageResId = R.string.user_confirm_remove_message; 827 } 828 Dialog dlg = new AlertDialog.Builder(context) 829 .setTitle(titleResId) 830 .setMessage(messageResId) 831 .setPositiveButton(R.string.user_delete_button, 832 onConfirmListener) 833 .setNegativeButton(android.R.string.cancel, null) 834 .create(); 835 return dlg; 836 } 837 838 /** 839 * Returns whether or not this device is able to be OEM unlocked. 840 */ isOemUnlockEnabled(Context context)841 static boolean isOemUnlockEnabled(Context context) { 842 PersistentDataBlockManager manager =(PersistentDataBlockManager) 843 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); 844 return manager.getOemUnlockEnabled(); 845 } 846 847 /** 848 * Allows enabling or disabling OEM unlock on this device. OEM unlocked 849 * devices allow users to flash other OSes to them. 850 */ setOemUnlockEnabled(Context context, boolean enabled)851 static void setOemUnlockEnabled(Context context, boolean enabled) { 852 PersistentDataBlockManager manager =(PersistentDataBlockManager) 853 context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); 854 manager.setOemUnlockEnabled(enabled); 855 } 856 857 /** 858 * Returns a circular icon for a user. 859 */ getUserIcon(Context context, UserManager um, UserInfo user)860 public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { 861 if (user.iconPath != null) { 862 Bitmap icon = um.getUserIcon(user.id); 863 if (icon != null) { 864 return CircleFramedDrawable.getInstance(context, icon); 865 } 866 } 867 return UserIcons.getDefaultUserIcon(user.id, /* light= */ false); 868 } 869 870 /** 871 * Return whether or not the user should have a SIM Cards option in Settings. 872 * TODO: Change back to returning true if count is greater than one after testing. 873 * TODO: See bug 16533525. 874 */ showSimCardTile(Context context)875 public static boolean showSimCardTile(Context context) { 876 final TelephonyManager tm = 877 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 878 879 // TODO: Uncomment to re-enable SimSettings. 880 // return tm.getSimCount() > 0; 881 return false; 882 } 883 884 /** 885 * Determine whether a package is a "system package", in which case certain things (like 886 * disabling notifications or disabling the package altogether) should be disallowed. 887 */ isSystemPackage(PackageManager pm, PackageInfo pkg)888 public static boolean isSystemPackage(PackageManager pm, PackageInfo pkg) { 889 if (sSystemSignature == null) { 890 sSystemSignature = new Signature[]{ getSystemSignature(pm) }; 891 } 892 return sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg)); 893 } 894 895 private static Signature[] sSystemSignature; 896 getFirstSignature(PackageInfo pkg)897 private static Signature getFirstSignature(PackageInfo pkg) { 898 if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) { 899 return pkg.signatures[0]; 900 } 901 return null; 902 } 903 getSystemSignature(PackageManager pm)904 private static Signature getSystemSignature(PackageManager pm) { 905 try { 906 final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES); 907 return getFirstSignature(sys); 908 } catch (NameNotFoundException e) { 909 } 910 return null; 911 } 912 913 /** 914 * Returns elapsed time for the given millis, in the following format: 915 * 2d 5h 40m 29s 916 * @param context the application context 917 * @param millis the elapsed time in milli seconds 918 * @param withSeconds include seconds? 919 * @return the formatted elapsed time 920 */ formatElapsedTime(Context context, double millis, boolean withSeconds)921 public static String formatElapsedTime(Context context, double millis, boolean withSeconds) { 922 StringBuilder sb = new StringBuilder(); 923 int seconds = (int) Math.floor(millis / 1000); 924 if (!withSeconds) { 925 // Round up. 926 seconds += 30; 927 } 928 929 int days = 0, hours = 0, minutes = 0; 930 if (seconds >= SECONDS_PER_DAY) { 931 days = seconds / SECONDS_PER_DAY; 932 seconds -= days * SECONDS_PER_DAY; 933 } 934 if (seconds >= SECONDS_PER_HOUR) { 935 hours = seconds / SECONDS_PER_HOUR; 936 seconds -= hours * SECONDS_PER_HOUR; 937 } 938 if (seconds >= SECONDS_PER_MINUTE) { 939 minutes = seconds / SECONDS_PER_MINUTE; 940 seconds -= minutes * SECONDS_PER_MINUTE; 941 } 942 if (withSeconds) { 943 if (days > 0) { 944 sb.append(context.getString(R.string.battery_history_days, 945 days, hours, minutes, seconds)); 946 } else if (hours > 0) { 947 sb.append(context.getString(R.string.battery_history_hours, 948 hours, minutes, seconds)); 949 } else if (minutes > 0) { 950 sb.append(context.getString(R.string.battery_history_minutes, minutes, seconds)); 951 } else { 952 sb.append(context.getString(R.string.battery_history_seconds, seconds)); 953 } 954 } else { 955 if (days > 0) { 956 sb.append(context.getString(R.string.battery_history_days_no_seconds, 957 days, hours, minutes)); 958 } else if (hours > 0) { 959 sb.append(context.getString(R.string.battery_history_hours_no_seconds, 960 hours, minutes)); 961 } else { 962 sb.append(context.getString(R.string.battery_history_minutes_no_seconds, minutes)); 963 } 964 } 965 return sb.toString(); 966 } 967 } 968