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