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 import static android.content.Intent.EXTRA_USER_ID; 21 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; 22 import static android.text.format.DateUtils.FORMAT_SHOW_DATE; 23 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.app.AlertDialog; 27 import android.app.AppGlobals; 28 import android.app.AppOpsManager; 29 import android.app.Dialog; 30 import android.app.Fragment; 31 import android.app.IActivityManager; 32 import android.app.KeyguardManager; 33 import android.app.admin.DevicePolicyManager; 34 import android.content.ActivityNotFoundException; 35 import android.content.ComponentName; 36 import android.content.ContentResolver; 37 import android.content.Context; 38 import android.content.DialogInterface; 39 import android.content.Intent; 40 import android.content.IntentFilter; 41 import android.content.pm.ApplicationInfo; 42 import android.content.pm.IPackageManager; 43 import android.content.pm.IntentFilterVerificationInfo; 44 import android.content.pm.PackageManager; 45 import android.content.pm.PackageManager.NameNotFoundException; 46 import android.content.pm.ResolveInfo; 47 import android.content.pm.UserInfo; 48 import android.content.res.Resources; 49 import android.content.res.TypedArray; 50 import android.database.Cursor; 51 import android.graphics.Bitmap; 52 import android.graphics.BitmapFactory; 53 import android.hardware.fingerprint.FingerprintManager; 54 import android.net.ConnectivityManager; 55 import android.net.LinkProperties; 56 import android.net.Network; 57 import android.net.Uri; 58 import android.net.wifi.WifiManager; 59 import android.os.BatteryManager; 60 import android.os.Bundle; 61 import android.os.IBinder; 62 import android.os.INetworkManagementService; 63 import android.os.Looper; 64 import android.os.RemoteException; 65 import android.os.ServiceManager; 66 import android.os.UserHandle; 67 import android.os.UserManager; 68 import android.os.storage.StorageManager; 69 import android.os.storage.VolumeInfo; 70 import android.preference.PreferenceFrameLayout; 71 import android.provider.ContactsContract.CommonDataKinds; 72 import android.provider.ContactsContract.Contacts; 73 import android.provider.ContactsContract.Data; 74 import android.provider.ContactsContract.Profile; 75 import android.provider.ContactsContract.RawContacts; 76 import android.provider.Settings; 77 import android.support.annotation.StringRes; 78 import android.support.v7.preference.Preference; 79 import android.support.v7.preference.PreferenceGroup; 80 import android.support.v7.preference.PreferenceManager; 81 import android.support.v7.preference.PreferenceScreen; 82 import android.telephony.TelephonyManager; 83 import android.text.Spannable; 84 import android.text.SpannableString; 85 import android.text.SpannableStringBuilder; 86 import android.text.Spanned; 87 import android.text.TextUtils; 88 import android.text.format.DateUtils; 89 import android.text.style.TtsSpan; 90 import android.util.ArraySet; 91 import android.util.Log; 92 import android.util.SparseArray; 93 import android.util.TypedValue; 94 import android.view.LayoutInflater; 95 import android.view.View; 96 import android.view.ViewGroup; 97 import android.view.animation.Animation; 98 import android.view.animation.Animation.AnimationListener; 99 import android.view.animation.AnimationUtils; 100 import android.widget.ListView; 101 import android.widget.TabWidget; 102 103 import com.android.internal.app.UnlaunchableAppActivity; 104 import com.android.internal.util.ArrayUtils; 105 import com.android.internal.util.UserIcons; 106 import com.android.internal.widget.LockPatternUtils; 107 import com.android.settings.password.FingerprintManagerWrapper; 108 import com.android.settings.password.IFingerprintManager; 109 110 import java.io.IOException; 111 import java.io.InputStream; 112 import java.net.InetAddress; 113 import java.util.ArrayList; 114 import java.util.Iterator; 115 import java.util.List; 116 import java.util.Locale; 117 118 public final class Utils extends com.android.settingslib.Utils { 119 120 private static final String TAG = "Settings"; 121 122 /** 123 * Set the preference's title to the matching activity's label. 124 */ 125 public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1; 126 127 /** 128 * The opacity level of a disabled icon. 129 */ 130 public static final float DISABLED_ALPHA = 0.4f; 131 132 /** 133 * Color spectrum to use to indicate badness. 0 is completely transparent (no data), 134 * 1 is most bad (red), the last value is least bad (green). 135 */ 136 public static final int[] BADNESS_COLORS = new int[] { 137 0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00, 138 0xfffabf2c, 0xff679e37, 0xff0a7f42 139 }; 140 141 private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; 142 143 private static final int SECONDS_PER_MINUTE = 60; 144 private static final int SECONDS_PER_HOUR = 60 * 60; 145 private static final int SECONDS_PER_DAY = 24 * 60 * 60; 146 147 public static final String OS_PKG = "os"; 148 149 private static SparseArray<Bitmap> sDarkDefaultUserBitmapCache = new SparseArray<Bitmap>(); 150 151 /** 152 * Finds a matching activity for a preference's intent. If a matching 153 * activity is not found, it will remove the preference. 154 * 155 * @param context The context. 156 * @param parentPreferenceGroup The preference group that contains the 157 * preference whose intent is being resolved. 158 * @param preferenceKey The key of the preference whose intent is being 159 * resolved. 160 * @param flags 0 or one or more of 161 * {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY} 162 * . 163 * @return Whether an activity was found. If false, the preference was 164 * removed. 165 */ updatePreferenceToSpecificActivityOrRemove(Context context, PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags)166 public static boolean updatePreferenceToSpecificActivityOrRemove(Context context, 167 PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) { 168 169 Preference preference = parentPreferenceGroup.findPreference(preferenceKey); 170 if (preference == null) { 171 return false; 172 } 173 174 Intent intent = preference.getIntent(); 175 if (intent != null) { 176 // Find the activity that is in the system image 177 PackageManager pm = context.getPackageManager(); 178 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 179 int listSize = list.size(); 180 for (int i = 0; i < listSize; i++) { 181 ResolveInfo resolveInfo = list.get(i); 182 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 183 != 0) { 184 185 // Replace the intent with this specific activity 186 preference.setIntent(new Intent().setClassName( 187 resolveInfo.activityInfo.packageName, 188 resolveInfo.activityInfo.name)); 189 190 if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) { 191 // Set the preference title to the activity's label 192 preference.setTitle(resolveInfo.loadLabel(pm)); 193 } 194 195 return true; 196 } 197 } 198 } 199 200 // Did not find a matching activity, so remove the preference 201 parentPreferenceGroup.removePreference(preference); 202 203 return false; 204 } 205 206 /** 207 * Returns the UserManager for a given context 208 * 209 * @throws IllegalStateException if no UserManager could be retrieved. 210 */ getUserManager(Context context)211 public static UserManager getUserManager(Context context) { 212 UserManager um = UserManager.get(context); 213 if (um == null) { 214 throw new IllegalStateException("Unable to load UserManager"); 215 } 216 return um; 217 } 218 219 /** 220 * Returns true if Monkey is running. 221 */ isMonkeyRunning()222 public static boolean isMonkeyRunning() { 223 return ActivityManager.isUserAMonkey(); 224 } 225 226 /** 227 * Returns whether the device is voice-capable (meaning, it is also a phone). 228 */ isVoiceCapable(Context context)229 public static boolean isVoiceCapable(Context context) { 230 TelephonyManager telephony = 231 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 232 return telephony != null && telephony.isVoiceCapable(); 233 } 234 isWifiOnly(Context context)235 public static boolean isWifiOnly(Context context) { 236 ConnectivityManager cm = (ConnectivityManager)context.getSystemService( 237 Context.CONNECTIVITY_SERVICE); 238 return (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); 239 } 240 241 /** 242 * Returns the WIFI IP Addresses, if any, taking into account IPv4 and IPv6 style addresses. 243 * @param context the application context 244 * @return the formatted and newline-separated IP addresses, or null if none. 245 */ getWifiIpAddresses(Context context)246 public static String getWifiIpAddresses(Context context) { 247 WifiManager wifiManager = context.getSystemService(WifiManager.class); 248 Network currentNetwork = wifiManager.getCurrentNetwork(); 249 if (currentNetwork != null) { 250 ConnectivityManager cm = (ConnectivityManager) 251 context.getSystemService(Context.CONNECTIVITY_SERVICE); 252 LinkProperties prop = cm.getLinkProperties(currentNetwork); 253 return formatIpAddresses(prop); 254 } 255 return null; 256 } 257 258 /** 259 * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style 260 * addresses. 261 * @return the formatted and newline-separated IP addresses, or null if none. 262 */ getDefaultIpAddresses(ConnectivityManager cm)263 public static String getDefaultIpAddresses(ConnectivityManager cm) { 264 LinkProperties prop = cm.getActiveLinkProperties(); 265 return formatIpAddresses(prop); 266 } 267 formatIpAddresses(LinkProperties prop)268 private static String formatIpAddresses(LinkProperties prop) { 269 if (prop == null) return null; 270 Iterator<InetAddress> iter = prop.getAllAddresses().iterator(); 271 // If there are no entries, return null 272 if (!iter.hasNext()) return null; 273 // Concatenate all available addresses, comma separated 274 String addresses = ""; 275 while (iter.hasNext()) { 276 addresses += iter.next().getHostAddress(); 277 if (iter.hasNext()) addresses += "\n"; 278 } 279 return addresses; 280 } 281 createLocaleFromString(String localeStr)282 public static Locale createLocaleFromString(String localeStr) { 283 // TODO: is there a better way to actually construct a locale that will match? 284 // The main problem is, on top of Java specs, locale.toString() and 285 // new Locale(locale.toString()).toString() do not return equal() strings in 286 // many cases, because the constructor takes the only string as the language 287 // code. So : new Locale("en", "US").toString() => "en_US" 288 // And : new Locale("en_US").toString() => "en_us" 289 if (null == localeStr) 290 return Locale.getDefault(); 291 String[] brokenDownLocale = localeStr.split("_", 3); 292 // split may not return a 0-length array. 293 if (1 == brokenDownLocale.length) { 294 return new Locale(brokenDownLocale[0]); 295 } else if (2 == brokenDownLocale.length) { 296 return new Locale(brokenDownLocale[0], brokenDownLocale[1]); 297 } else { 298 return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]); 299 } 300 } 301 isBatteryPresent(Intent batteryChangedIntent)302 public static boolean isBatteryPresent(Intent batteryChangedIntent) { 303 return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); 304 } 305 getBatteryPercentage(Intent batteryChangedIntent)306 public static String getBatteryPercentage(Intent batteryChangedIntent) { 307 return formatPercentage(getBatteryLevel(batteryChangedIntent)); 308 } 309 310 /** 311 * Prepare a custom preferences layout, moving padding to {@link ListView} 312 * when outside scrollbars are requested. Usually used to display 313 * {@link ListView} and {@link TabWidget} with correct padding. 314 */ prepareCustomPreferencesList( ViewGroup parent, View child, View list, boolean ignoreSidePadding)315 public static void prepareCustomPreferencesList( 316 ViewGroup parent, View child, View list, boolean ignoreSidePadding) { 317 final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY; 318 if (movePadding) { 319 final Resources res = list.getResources(); 320 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin); 321 final int paddingBottom = res.getDimensionPixelSize( 322 com.android.internal.R.dimen.preference_fragment_padding_bottom); 323 324 if (parent instanceof PreferenceFrameLayout) { 325 ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true; 326 327 final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide; 328 list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom); 329 } else { 330 list.setPaddingRelative(paddingSide, 0, paddingSide, paddingBottom); 331 } 332 } 333 } 334 forceCustomPadding(View view, boolean additive)335 public static void forceCustomPadding(View view, boolean additive) { 336 final Resources res = view.getResources(); 337 final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin); 338 339 final int paddingStart = paddingSide + (additive ? view.getPaddingStart() : 0); 340 final int paddingEnd = paddingSide + (additive ? view.getPaddingEnd() : 0); 341 final int paddingBottom = res.getDimensionPixelSize( 342 com.android.internal.R.dimen.preference_fragment_padding_bottom); 343 344 view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom); 345 } 346 347 /* Used by UserSettings as well. Call this on a non-ui thread. */ copyMeProfilePhoto(Context context, UserInfo user)348 public static void copyMeProfilePhoto(Context context, UserInfo user) { 349 Uri contactUri = Profile.CONTENT_URI; 350 351 int userId = user != null ? user.id : UserHandle.myUserId(); 352 353 InputStream avatarDataStream = Contacts.openContactPhotoInputStream( 354 context.getContentResolver(), 355 contactUri, true); 356 // If there's no profile photo, assign a default avatar 357 if (avatarDataStream == null) { 358 assignDefaultPhoto(context, userId); 359 return; 360 } 361 362 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 363 Bitmap icon = BitmapFactory.decodeStream(avatarDataStream); 364 um.setUserIcon(userId, icon); 365 try { 366 avatarDataStream.close(); 367 } catch (IOException ioe) { } 368 } 369 370 /** 371 * Assign the default photo to user with {@paramref userId} 372 * @param context used to get the {@link UserManager} 373 * @param userId used to get the icon bitmap 374 * @return true if assign photo successfully, false if failed 375 */ assignDefaultPhoto(Context context, int userId)376 public static boolean assignDefaultPhoto(Context context, int userId) { 377 if (context == null) { 378 return false; 379 } 380 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 381 Bitmap bitmap = getDefaultUserIconAsBitmap(userId); 382 um.setUserIcon(userId, bitmap); 383 384 return true; 385 } 386 getMeProfileName(Context context, boolean full)387 public static String getMeProfileName(Context context, boolean full) { 388 if (full) { 389 return getProfileDisplayName(context); 390 } else { 391 return getShorterNameIfPossible(context); 392 } 393 } 394 getShorterNameIfPossible(Context context)395 private static String getShorterNameIfPossible(Context context) { 396 final String given = getLocalProfileGivenName(context); 397 return !TextUtils.isEmpty(given) ? given : getProfileDisplayName(context); 398 } 399 getLocalProfileGivenName(Context context)400 private static String getLocalProfileGivenName(Context context) { 401 final ContentResolver cr = context.getContentResolver(); 402 403 // Find the raw contact ID for the local ME profile raw contact. 404 final long localRowProfileId; 405 final Cursor localRawProfile = cr.query( 406 Profile.CONTENT_RAW_CONTACTS_URI, 407 new String[] {RawContacts._ID}, 408 RawContacts.ACCOUNT_TYPE + " IS NULL AND " + 409 RawContacts.ACCOUNT_NAME + " IS NULL", 410 null, null); 411 if (localRawProfile == null) return null; 412 413 try { 414 if (!localRawProfile.moveToFirst()) { 415 return null; 416 } 417 localRowProfileId = localRawProfile.getLong(0); 418 } finally { 419 localRawProfile.close(); 420 } 421 422 // Find the structured name for the raw contact. 423 final Cursor structuredName = cr.query( 424 Profile.CONTENT_URI.buildUpon().appendPath(Contacts.Data.CONTENT_DIRECTORY).build(), 425 new String[] {CommonDataKinds.StructuredName.GIVEN_NAME, 426 CommonDataKinds.StructuredName.FAMILY_NAME}, 427 Data.RAW_CONTACT_ID + "=" + localRowProfileId, 428 null, null); 429 if (structuredName == null) return null; 430 431 try { 432 if (!structuredName.moveToFirst()) { 433 return null; 434 } 435 String partialName = structuredName.getString(0); 436 if (TextUtils.isEmpty(partialName)) { 437 partialName = structuredName.getString(1); 438 } 439 return partialName; 440 } finally { 441 structuredName.close(); 442 } 443 } 444 getProfileDisplayName(Context context)445 private static final String getProfileDisplayName(Context context) { 446 final ContentResolver cr = context.getContentResolver(); 447 final Cursor profile = cr.query(Profile.CONTENT_URI, 448 new String[] {Profile.DISPLAY_NAME}, null, null, null); 449 if (profile == null) return null; 450 451 try { 452 if (!profile.moveToFirst()) { 453 return null; 454 } 455 return profile.getString(0); 456 } finally { 457 profile.close(); 458 } 459 } 460 461 /** Not global warming, it's global change warning. */ buildGlobalChangeWarningDialog(final Context context, int titleResId, final Runnable positiveAction)462 public static Dialog buildGlobalChangeWarningDialog(final Context context, int titleResId, 463 final Runnable positiveAction) { 464 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 465 builder.setTitle(titleResId); 466 builder.setMessage(R.string.global_change_warning); 467 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 468 @Override 469 public void onClick(DialogInterface dialog, int which) { 470 positiveAction.run(); 471 } 472 }); 473 builder.setNegativeButton(android.R.string.cancel, null); 474 475 return builder.create(); 476 } 477 hasMultipleUsers(Context context)478 public static boolean hasMultipleUsers(Context context) { 479 return ((UserManager) context.getSystemService(Context.USER_SERVICE)) 480 .getUsers().size() > 1; 481 } 482 483 /** 484 * Start a new instance of the activity, showing only the given fragment. 485 * When launched in this mode, the given preference fragment will be instantiated and fill the 486 * entire activity. 487 * 488 * @param context The context. 489 * @param fragmentName The name of the fragment to display. 490 * @param args Optional arguments to supply to the fragment. 491 * @param resultTo Option fragment that should receive the result of the activity launch. 492 * @param resultRequestCode If resultTo is non-null, this is the request code in which 493 * to report the result. 494 * @param titleResId resource id for the String to display for the title of this set 495 * of preferences. 496 * @param title String to display for the title of this set of preferences. 497 * @param metricsCategory The current metricsCategory for logging source when fragment starts 498 */ startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title, int metricsCategory)499 public static void startWithFragment(Context context, String fragmentName, Bundle args, 500 Fragment resultTo, int resultRequestCode, int titleResId, 501 CharSequence title, int metricsCategory) { 502 startWithFragment(context, fragmentName, args, resultTo, resultRequestCode, 503 null /* titleResPackageName */, titleResId, title, false /* not a shortcut */, 504 metricsCategory); 505 } 506 507 /** 508 * Start a new instance of the activity, showing only the given fragment. 509 * When launched in this mode, the given preference fragment will be instantiated and fill the 510 * entire activity. 511 * 512 * @param context The context. 513 * @param fragmentName The name of the fragment to display. 514 * @param args Optional arguments to supply to the fragment. 515 * @param resultTo Option fragment that should receive the result of the activity launch. 516 * @param resultRequestCode If resultTo is non-null, this is the request code in which 517 * to report the result. 518 * @param titleResPackageName Optional package name for the resource id of the title. 519 * @param titleResId resource id for the String to display for the title of this set 520 * of preferences. 521 * @param title String to display for the title of this set of preferences. 522 * @param metricsCategory The current metricsCategory for logging source when fragment starts 523 */ startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, CharSequence title, int metricsCategory)524 public static void startWithFragment(Context context, String fragmentName, Bundle args, 525 Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, 526 CharSequence title, int metricsCategory) { 527 startWithFragment(context, fragmentName, args, resultTo, resultRequestCode, 528 titleResPackageName, titleResId, title, false /* not a shortcut */, 529 metricsCategory); 530 } 531 startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title, boolean isShortcut, int metricsCategory)532 public static void startWithFragment(Context context, String fragmentName, Bundle args, 533 Fragment resultTo, int resultRequestCode, int titleResId, 534 CharSequence title, boolean isShortcut, int metricsCategory) { 535 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, 536 null /* titleResPackageName */, titleResId, title, isShortcut, metricsCategory); 537 if (resultTo == null) { 538 context.startActivity(intent); 539 } else { 540 resultTo.getActivity().startActivityForResult(intent, resultRequestCode); 541 } 542 } 543 startWithFragment(Context context, String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut, int metricsCategory)544 public static void startWithFragment(Context context, String fragmentName, Bundle args, 545 Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId, 546 CharSequence title, boolean isShortcut, int metricsCategory) { 547 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResPackageName, 548 titleResId, title, isShortcut, metricsCategory); 549 if (resultTo == null) { 550 context.startActivity(intent); 551 } else { 552 resultTo.startActivityForResult(intent, resultRequestCode); 553 } 554 } 555 startWithFragmentAsUser(Context context, String fragmentName, Bundle args, int titleResId, CharSequence title, boolean isShortcut, int metricsCategory, UserHandle userHandle)556 public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args, 557 int titleResId, CharSequence title, boolean isShortcut, int metricsCategory, 558 UserHandle userHandle) { 559 // workaround to avoid crash in b/17523189 560 if (userHandle.getIdentifier() == UserHandle.myUserId()) { 561 startWithFragment(context, fragmentName, args, null, 0, titleResId, title, isShortcut, 562 metricsCategory); 563 } else { 564 Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, 565 null /* titleResPackageName */, titleResId, title, isShortcut, metricsCategory); 566 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 567 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 568 context.startActivityAsUser(intent, userHandle); 569 } 570 } 571 572 /** 573 * Build an Intent to launch a new activity showing the selected fragment. 574 * The implementation constructs an Intent that re-launches the current activity with the 575 * appropriate arguments to display the fragment. 576 * 577 * 578 * @param context The Context. 579 * @param fragmentName The name of the fragment to display. 580 * @param args Optional arguments to supply to the fragment. 581 * @param titleResPackageName Optional package name for the resource id of the title. 582 * @param titleResId Optional title resource id to show for this item. 583 * @param title Optional title to show for this item. 584 * @param isShortcut tell if this is a Launcher Shortcut or not 585 * @param sourceMetricsCategory The context (source) from which an action is performed 586 * @return Returns an Intent that can be launched to display the given 587 * fragment. 588 */ onBuildStartFragmentIntent(Context context, String fragmentName, Bundle args, String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut, int sourceMetricsCategory)589 public static Intent onBuildStartFragmentIntent(Context context, String fragmentName, 590 Bundle args, String titleResPackageName, int titleResId, CharSequence title, 591 boolean isShortcut, int sourceMetricsCategory) { 592 Intent intent = new Intent(Intent.ACTION_MAIN); 593 intent.setClass(context, SubSettings.class); 594 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName); 595 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 596 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME, 597 titleResPackageName); 598 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId); 599 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title); 600 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut); 601 intent.putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, sourceMetricsCategory); 602 return intent; 603 } 604 605 /** 606 * Returns the managed profile of the current user or {@code null} if none is found or a profile 607 * exists but it is disabled. 608 */ getManagedProfile(UserManager userManager)609 public static UserHandle getManagedProfile(UserManager userManager) { 610 List<UserHandle> userProfiles = userManager.getUserProfiles(); 611 final int count = userProfiles.size(); 612 for (int i = 0; i < count; i++) { 613 final UserHandle profile = userProfiles.get(i); 614 if (profile.getIdentifier() == userManager.getUserHandle()) { 615 continue; 616 } 617 final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier()); 618 if (userInfo.isManagedProfile()) { 619 return profile; 620 } 621 } 622 return null; 623 } 624 625 /** 626 * Returns the managed profile of the current user or {@code null} if none is found. Unlike 627 * {@link #getManagedProfile} this method returns enabled and disabled managed profiles. 628 */ getManagedProfileWithDisabled(UserManager userManager)629 public static UserHandle getManagedProfileWithDisabled(UserManager userManager) { 630 // TODO: Call getManagedProfileId from here once Robolectric supports 631 // API level 24 and UserManager.getProfileIdsWithDisabled can be Mocked (to avoid having 632 // yet another implementation that loops over user profiles in this method). In the meantime 633 // we need to use UserManager.getProfiles that is available on API 23 (the one currently 634 // used for Settings Robolectric tests). 635 final int myUserId = UserHandle.myUserId(); 636 List<UserInfo> profiles = userManager.getProfiles(myUserId); 637 final int count = profiles.size(); 638 for (int i = 0; i < count; i++) { 639 final UserInfo profile = profiles.get(i); 640 if (profile.isManagedProfile() 641 && profile.getUserHandle().getIdentifier() != myUserId) { 642 return profile.getUserHandle(); 643 } 644 } 645 return null; 646 } 647 648 /** 649 * Retrieves the id for the given user's managed profile. 650 * 651 * @return the managed profile id or UserHandle.USER_NULL if there is none. 652 */ getManagedProfileId(UserManager um, int parentUserId)653 public static int getManagedProfileId(UserManager um, int parentUserId) { 654 int[] profileIds = um.getProfileIdsWithDisabled(parentUserId); 655 for (int profileId : profileIds) { 656 if (profileId != parentUserId) { 657 return profileId; 658 } 659 } 660 return UserHandle.USER_NULL; 661 } 662 663 /** 664 * Returns the target user for a Settings activity. 665 * <p> 666 * User would be retrieved in this order: 667 * <ul> 668 * <li> If this activity is launched from other user, return that user id. 669 * <li> If this is launched from the Settings app in same user, return the user contained as an 670 * extra in the arguments or intent extras. 671 * <li> Otherwise, return UserHandle.myUserId(). 672 * </ul> 673 * <p> 674 * Note: This is secure in the sense that it only returns a target user different to the current 675 * one if the app launching this activity is the Settings app itself, running in the same user 676 * or in one that is in the same profile group, or if the user id is provided by the system. 677 */ getSecureTargetUser(IBinder activityToken, UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras)678 public static UserHandle getSecureTargetUser(IBinder activityToken, 679 UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) { 680 UserHandle currentUser = new UserHandle(UserHandle.myUserId()); 681 IActivityManager am = ActivityManager.getService(); 682 try { 683 String launchedFromPackage = am.getLaunchedFromPackage(activityToken); 684 boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage); 685 686 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId( 687 am.getLaunchedFromUid(activityToken))); 688 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) { 689 // Check it's secure 690 if (isProfileOf(um, launchedFromUser)) { 691 return launchedFromUser; 692 } 693 } 694 UserHandle extrasUser = getUserHandleFromBundle(intentExtras); 695 if (extrasUser != null && !extrasUser.equals(currentUser)) { 696 // Check it's secure 697 if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) { 698 return extrasUser; 699 } 700 } 701 UserHandle argumentsUser = getUserHandleFromBundle(arguments); 702 if (argumentsUser != null && !argumentsUser.equals(currentUser)) { 703 // Check it's secure 704 if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) { 705 return argumentsUser; 706 } 707 } 708 } catch (RemoteException e) { 709 // Should not happen 710 Log.v(TAG, "Could not talk to activity manager.", e); 711 } 712 return currentUser; 713 } 714 715 /** 716 * Lookup both {@link Intent#EXTRA_USER} and {@link Intent#EXTRA_USER_ID} in the bundle 717 * and return the {@link UserHandle} object. Return {@code null} if nothing is found. 718 */ getUserHandleFromBundle(Bundle bundle)719 private static @Nullable UserHandle getUserHandleFromBundle(Bundle bundle) { 720 if (bundle == null) { 721 return null; 722 } 723 final UserHandle user = bundle.getParcelable(EXTRA_USER); 724 if (user != null) { 725 return user; 726 } 727 final int userId = bundle.getInt(EXTRA_USER_ID, -1); 728 if (userId != -1) { 729 return UserHandle.of(userId); 730 } 731 return null; 732 } 733 734 /** 735 * Returns the target user for a Settings activity. 736 * 737 * The target user can be either the current user, the user that launched this activity or 738 * the user contained as an extra in the arguments or intent extras. 739 * 740 * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if 741 * possible. 742 * 743 * @see #getInsecureTargetUser(IBinder, Bundle, Bundle) 744 */ getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments, @Nullable Bundle intentExtras)745 public static UserHandle getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments, 746 @Nullable Bundle intentExtras) { 747 UserHandle currentUser = new UserHandle(UserHandle.myUserId()); 748 IActivityManager am = ActivityManager.getService(); 749 try { 750 UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId( 751 am.getLaunchedFromUid(activityToken))); 752 if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) { 753 return launchedFromUser; 754 } 755 UserHandle extrasUser = intentExtras != null 756 ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null; 757 if (extrasUser != null && !extrasUser.equals(currentUser)) { 758 return extrasUser; 759 } 760 UserHandle argumentsUser = arguments != null 761 ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null; 762 if (argumentsUser != null && !argumentsUser.equals(currentUser)) { 763 return argumentsUser; 764 } 765 } catch (RemoteException e) { 766 // Should not happen 767 Log.v(TAG, "Could not talk to activity manager.", e); 768 return null; 769 } 770 return currentUser; 771 } 772 773 /** 774 * Returns true if the user provided is in the same profiles group as the current user. 775 */ isProfileOf(UserManager um, UserHandle otherUser)776 private static boolean isProfileOf(UserManager um, UserHandle otherUser) { 777 if (um == null || otherUser == null) return false; 778 return (UserHandle.myUserId() == otherUser.getIdentifier()) 779 || um.getUserProfiles().contains(otherUser); 780 } 781 782 /** 783 * Return whether or not the user should have a SIM Cards option in Settings. 784 * TODO: Change back to returning true if count is greater than one after testing. 785 * TODO: See bug 16533525. 786 */ showSimCardTile(Context context)787 public static boolean showSimCardTile(Context context) { 788 final TelephonyManager tm = 789 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 790 791 return tm.getSimCount() > 1; 792 } 793 794 /** 795 * Returns elapsed time for the given millis, in the following format: 796 * 2d 5h 40m 29s 797 * @param context the application context 798 * @param millis the elapsed time in milli seconds 799 * @param withSeconds include seconds? 800 * @return the formatted elapsed time 801 */ formatElapsedTime(Context context, double millis, boolean withSeconds)802 public static CharSequence formatElapsedTime(Context context, double millis, 803 boolean withSeconds) { 804 SpannableStringBuilder sb = new SpannableStringBuilder(); 805 int seconds = (int) Math.floor(millis / 1000); 806 if (!withSeconds) { 807 // Round up. 808 seconds += 30; 809 } 810 811 int days = 0, hours = 0, minutes = 0; 812 if (seconds >= SECONDS_PER_DAY) { 813 days = seconds / SECONDS_PER_DAY; 814 seconds -= days * SECONDS_PER_DAY; 815 } 816 if (seconds >= SECONDS_PER_HOUR) { 817 hours = seconds / SECONDS_PER_HOUR; 818 seconds -= hours * SECONDS_PER_HOUR; 819 } 820 if (seconds >= SECONDS_PER_MINUTE) { 821 minutes = seconds / SECONDS_PER_MINUTE; 822 seconds -= minutes * SECONDS_PER_MINUTE; 823 } 824 if (withSeconds) { 825 if (days > 0) { 826 sb.append(context.getString(R.string.battery_history_days, 827 days, hours, minutes, seconds)); 828 } else if (hours > 0) { 829 sb.append(context.getString(R.string.battery_history_hours, 830 hours, minutes, seconds)); 831 } else if (minutes > 0) { 832 sb.append(context.getString(R.string.battery_history_minutes, minutes, seconds)); 833 } else { 834 sb.append(context.getString(R.string.battery_history_seconds, seconds)); 835 } 836 } else { 837 if (days > 0) { 838 sb.append(context.getString(R.string.battery_history_days_no_seconds, 839 days, hours, minutes)); 840 } else if (hours > 0) { 841 sb.append(context.getString(R.string.battery_history_hours_no_seconds, 842 hours, minutes)); 843 } else { 844 sb.append(context.getString(R.string.battery_history_minutes_no_seconds, minutes)); 845 846 // Add ttsSpan if it only have minute value, because it will be read as "meters" 847 TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes) 848 .setUnit("minute").build(); 849 sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 850 } 851 } 852 853 return sb; 854 } 855 856 /** 857 * Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed). 858 * @param userManager Instance of UserManager 859 * @param checkUser The user to check the existence of. 860 * @return UserInfo of the user or null for non-existent user. 861 */ getExistingUser(UserManager userManager, UserHandle checkUser)862 public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) { 863 final List<UserInfo> users = userManager.getUsers(true /* excludeDying */); 864 final int checkUserId = checkUser.getIdentifier(); 865 for (UserInfo user : users) { 866 if (user.id == checkUserId) { 867 return user; 868 } 869 } 870 return null; 871 } 872 inflateCategoryHeader(LayoutInflater inflater, ViewGroup parent)873 public static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup parent) { 874 final TypedArray a = inflater.getContext().obtainStyledAttributes(null, 875 com.android.internal.R.styleable.Preference, 876 com.android.internal.R.attr.preferenceCategoryStyle, 0); 877 final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 878 0); 879 a.recycle(); 880 return inflater.inflate(resId, parent, false); 881 } 882 883 /** 884 * Return if we are running low on storage space or not. 885 * 886 * @param context The context 887 * @return true if we are running low on storage space 888 */ isLowStorage(Context context)889 public static boolean isLowStorage(Context context) { 890 final StorageManager sm = StorageManager.from(context); 891 return (sm.getStorageBytesUntilLow(context.getFilesDir()) < 0); 892 } 893 894 /** 895 * Returns a default user icon (as a {@link Bitmap}) for the given user. 896 * 897 * Note that for guest users, you should pass in {@code UserHandle.USER_NULL}. 898 * @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon 899 */ getDefaultUserIconAsBitmap(int userId)900 public static Bitmap getDefaultUserIconAsBitmap(int userId) { 901 Bitmap bitmap = null; 902 // Try finding the corresponding bitmap in the dark bitmap cache 903 bitmap = sDarkDefaultUserBitmapCache.get(userId); 904 if (bitmap == null) { 905 bitmap = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(userId, false)); 906 // Save it to cache 907 sDarkDefaultUserBitmapCache.put(userId, bitmap); 908 } 909 return bitmap; 910 } 911 hasPreferredActivities(PackageManager pm, String packageName)912 public static boolean hasPreferredActivities(PackageManager pm, String packageName) { 913 // Get list of preferred activities 914 List<ComponentName> prefActList = new ArrayList<>(); 915 // Intent list cannot be null. so pass empty list 916 List<IntentFilter> intentList = new ArrayList<>(); 917 pm.getPreferredActivities(intentList, prefActList, packageName); 918 Log.d(TAG, "Have " + prefActList.size() + " number of activities in preferred list"); 919 return prefActList.size() > 0; 920 } 921 getHandledDomains(PackageManager pm, String packageName)922 public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) { 923 List<IntentFilterVerificationInfo> iviList = pm.getIntentFilterVerifications(packageName); 924 List<IntentFilter> filters = pm.getAllIntentFilters(packageName); 925 926 ArraySet<String> result = new ArraySet<>(); 927 if (iviList.size() > 0) { 928 for (IntentFilterVerificationInfo ivi : iviList) { 929 for (String host : ivi.getDomains()) { 930 result.add(host); 931 } 932 } 933 } 934 if (filters != null && filters.size() > 0) { 935 for (IntentFilter filter : filters) { 936 if (filter.hasCategory(Intent.CATEGORY_BROWSABLE) 937 && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) || 938 filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) { 939 result.addAll(filter.getHostsList()); 940 } 941 } 942 } 943 return result; 944 } 945 handleLoadingContainer(View loading, View doneLoading, boolean done, boolean animate)946 public static void handleLoadingContainer(View loading, View doneLoading, boolean done, 947 boolean animate) { 948 setViewShown(loading, !done, animate); 949 setViewShown(doneLoading, done, animate); 950 } 951 setViewShown(final View view, boolean shown, boolean animate)952 private static void setViewShown(final View view, boolean shown, boolean animate) { 953 if (animate) { 954 Animation animation = AnimationUtils.loadAnimation(view.getContext(), 955 shown ? android.R.anim.fade_in : android.R.anim.fade_out); 956 if (shown) { 957 view.setVisibility(View.VISIBLE); 958 } else { 959 animation.setAnimationListener(new AnimationListener() { 960 @Override 961 public void onAnimationStart(Animation animation) { 962 } 963 964 @Override 965 public void onAnimationRepeat(Animation animation) { 966 } 967 968 @Override 969 public void onAnimationEnd(Animation animation) { 970 view.setVisibility(View.INVISIBLE); 971 } 972 }); 973 } 974 view.startAnimation(animation); 975 } else { 976 view.clearAnimation(); 977 view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE); 978 } 979 } 980 981 /** 982 * Returns the application info of the currently installed MDM package. 983 */ getAdminApplicationInfo(Context context, int profileId)984 public static ApplicationInfo getAdminApplicationInfo(Context context, int profileId) { 985 DevicePolicyManager dpm = 986 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 987 ComponentName mdmPackage = dpm.getProfileOwnerAsUser(profileId); 988 if (mdmPackage == null) { 989 return null; 990 } 991 String mdmPackageName = mdmPackage.getPackageName(); 992 try { 993 IPackageManager ipm = AppGlobals.getPackageManager(); 994 ApplicationInfo mdmApplicationInfo = 995 ipm.getApplicationInfo(mdmPackageName, 0, profileId); 996 return mdmApplicationInfo; 997 } catch (RemoteException e) { 998 Log.e(TAG, "Error while retrieving application info for package " + mdmPackageName 999 + ", userId " + profileId, e); 1000 return null; 1001 } 1002 } 1003 isBandwidthControlEnabled()1004 public static boolean isBandwidthControlEnabled() { 1005 final INetworkManagementService netManager = INetworkManagementService.Stub 1006 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); 1007 try { 1008 return netManager.isBandwidthControlEnabled(); 1009 } catch (RemoteException e) { 1010 return false; 1011 } 1012 } 1013 1014 /** 1015 * Returns an accessible SpannableString. 1016 * @param displayText the text to display 1017 * @param accessibileText the text text-to-speech engines should read 1018 */ createAccessibleSequence(CharSequence displayText, String accessibileText)1019 public static SpannableString createAccessibleSequence(CharSequence displayText, 1020 String accessibileText) { 1021 SpannableString str = new SpannableString(displayText); 1022 str.setSpan(new TtsSpan.TextBuilder(accessibileText).build(), 0, 1023 displayText.length(), 1024 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 1025 return str; 1026 } 1027 1028 /** 1029 * Returns the user id present in the bundle with {@link Intent#EXTRA_USER_ID} if it 1030 * belongs to the current user. 1031 * 1032 * @throws SecurityException if the given userId does not belong to the current user group. 1033 */ getUserIdFromBundle(Context context, Bundle bundle)1034 public static int getUserIdFromBundle(Context context, Bundle bundle) { 1035 if (bundle == null) { 1036 return getCredentialOwnerUserId(context); 1037 } 1038 int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 1039 return enforceSameOwner(context, userId); 1040 } 1041 1042 /** 1043 * Returns the given user id if it belongs to the current user. 1044 * 1045 * @throws SecurityException if the given userId does not belong to the current user group. 1046 */ enforceSameOwner(Context context, int userId)1047 public static int enforceSameOwner(Context context, int userId) { 1048 final UserManager um = getUserManager(context); 1049 final int[] profileIds = um.getProfileIdsWithDisabled(UserHandle.myUserId()); 1050 if (ArrayUtils.contains(profileIds, userId)) { 1051 return userId; 1052 } 1053 throw new SecurityException("Given user id " + userId + " does not belong to user " 1054 + UserHandle.myUserId()); 1055 } 1056 1057 /** 1058 * Returns the effective credential owner of the calling user. 1059 */ getCredentialOwnerUserId(Context context)1060 public static int getCredentialOwnerUserId(Context context) { 1061 return getCredentialOwnerUserId(context, UserHandle.myUserId()); 1062 } 1063 1064 /** 1065 * Returns the user id of the credential owner of the given user id. 1066 */ getCredentialOwnerUserId(Context context, int userId)1067 public static int getCredentialOwnerUserId(Context context, int userId) { 1068 UserManager um = getUserManager(context); 1069 return um.getCredentialOwnerProfile(userId); 1070 } 1071 resolveResource(Context context, int attr)1072 public static int resolveResource(Context context, int attr) { 1073 TypedValue value = new TypedValue(); 1074 context.getTheme().resolveAttribute(attr, value, true); 1075 return value.resourceId; 1076 } 1077 1078 private static final StringBuilder sBuilder = new StringBuilder(50); 1079 private static final java.util.Formatter sFormatter = new java.util.Formatter( 1080 sBuilder, Locale.getDefault()); 1081 formatDateRange(Context context, long start, long end)1082 public static String formatDateRange(Context context, long start, long end) { 1083 final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 1084 1085 synchronized (sBuilder) { 1086 sBuilder.setLength(0); 1087 return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) 1088 .toString(); 1089 } 1090 } 1091 getNonIndexable(int xml, Context context)1092 public static List<String> getNonIndexable(int xml, Context context) { 1093 if (Looper.myLooper() == null) { 1094 // Hack to make sure Preferences can initialize. Prefs expect a looper, but 1095 // don't actually use it for the basic stuff here. 1096 Looper.prepare(); 1097 } 1098 final List<String> ret = new ArrayList<>(); 1099 PreferenceManager manager = new PreferenceManager(context); 1100 PreferenceScreen screen = manager.inflateFromResource(context, xml, null); 1101 checkPrefs(screen, ret); 1102 1103 return ret; 1104 } 1105 checkPrefs(PreferenceGroup group, List<String> ret)1106 private static void checkPrefs(PreferenceGroup group, List<String> ret) { 1107 if (group == null) return; 1108 for (int i = 0; i < group.getPreferenceCount(); i++) { 1109 Preference pref = group.getPreference(i); 1110 if (pref instanceof SelfAvailablePreference 1111 && !((SelfAvailablePreference) pref).isAvailable(group.getContext())) { 1112 ret.add(pref.getKey()); 1113 if (pref instanceof PreferenceGroup) { 1114 addAll((PreferenceGroup) pref, ret); 1115 } 1116 } else if (pref instanceof PreferenceGroup) { 1117 checkPrefs((PreferenceGroup) pref, ret); 1118 } 1119 } 1120 } 1121 addAll(PreferenceGroup group, List<String> ret)1122 private static void addAll(PreferenceGroup group, List<String> ret) { 1123 if (group == null) return; 1124 for (int i = 0; i < group.getPreferenceCount(); i++) { 1125 Preference pref = group.getPreference(i); 1126 ret.add(pref.getKey()); 1127 if (pref instanceof PreferenceGroup) { 1128 addAll((PreferenceGroup) pref, ret); 1129 } 1130 } 1131 } 1132 isDeviceProvisioned(Context context)1133 public static boolean isDeviceProvisioned(Context context) { 1134 return Settings.Global.getInt(context.getContentResolver(), 1135 Settings.Global.DEVICE_PROVISIONED, 0) != 0; 1136 } 1137 startQuietModeDialogIfNecessary(Context context, UserManager um, int userId)1138 public static boolean startQuietModeDialogIfNecessary(Context context, UserManager um, 1139 int userId) { 1140 if (um.isQuietModeEnabled(UserHandle.of(userId))) { 1141 final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent(userId); 1142 context.startActivity(intent); 1143 return true; 1144 } 1145 return false; 1146 } 1147 unlockWorkProfileIfNecessary(Context context, int userId)1148 public static boolean unlockWorkProfileIfNecessary(Context context, int userId) { 1149 try { 1150 if (!ActivityManager.getService().isUserRunning(userId, 1151 ActivityManager.FLAG_AND_LOCKED)) { 1152 return false; 1153 } 1154 } catch (RemoteException e) { 1155 return false; 1156 } 1157 if (!(new LockPatternUtils(context)).isSecure(userId)) { 1158 return false; 1159 } 1160 return confirmWorkProfileCredentials(context, userId); 1161 } 1162 confirmWorkProfileCredentialsIfNecessary(Context context, int userId)1163 public static boolean confirmWorkProfileCredentialsIfNecessary(Context context, int userId) { 1164 KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); 1165 if (!km.isDeviceLocked(userId)) { 1166 return false; 1167 } 1168 return confirmWorkProfileCredentials(context, userId); 1169 } 1170 confirmWorkProfileCredentials(Context context, int userId)1171 private static boolean confirmWorkProfileCredentials(Context context, int userId) { 1172 final KeyguardManager km = (KeyguardManager) context.getSystemService( 1173 Context.KEYGUARD_SERVICE); 1174 final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, userId); 1175 if (unlockIntent != null) { 1176 context.startActivity(unlockIntent); 1177 return true; 1178 } else { 1179 return false; 1180 } 1181 } 1182 getApplicationLabel(Context context, String packageName)1183 public static CharSequence getApplicationLabel(Context context, String packageName) { 1184 try { 1185 final ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo( 1186 packageName, 1187 PackageManager.MATCH_DISABLED_COMPONENTS 1188 | PackageManager.MATCH_ANY_USER); 1189 return appInfo.loadLabel(context.getPackageManager()); 1190 } catch (PackageManager.NameNotFoundException e) { 1191 Log.w(TAG, "Unable to find info for package: " + packageName); 1192 } 1193 return null; 1194 } 1195 isPackageDirectBootAware(Context context, String packageName)1196 public static boolean isPackageDirectBootAware(Context context, String packageName) { 1197 try { 1198 final ApplicationInfo ai = context.getPackageManager().getApplicationInfo( 1199 packageName, 0); 1200 return ai.isDirectBootAware() || ai.isPartiallyDirectBootAware(); 1201 } catch (NameNotFoundException ignored) { 1202 } 1203 return false; 1204 } 1205 1206 /** 1207 * Returns a context created from the given context for the given user, or null if it fails 1208 */ createPackageContextAsUser(Context context, int userId)1209 public static Context createPackageContextAsUser(Context context, int userId) { 1210 try { 1211 return context.createPackageContextAsUser( 1212 context.getPackageName(), 0 /* flags */, UserHandle.of(userId)); 1213 } catch (PackageManager.NameNotFoundException e) { 1214 Log.e(TAG, "Failed to create user context", e); 1215 } 1216 return null; 1217 } 1218 getFingerprintManagerOrNull(Context context)1219 public static FingerprintManager getFingerprintManagerOrNull(Context context) { 1220 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { 1221 return context.getSystemService(FingerprintManager.class); 1222 } else { 1223 return null; 1224 } 1225 } 1226 getFingerprintManagerWrapperOrNull(Context context)1227 public static IFingerprintManager getFingerprintManagerWrapperOrNull(Context context) { 1228 FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context); 1229 if (fingerprintManager != null) { 1230 return new FingerprintManagerWrapper(fingerprintManager); 1231 } else { 1232 return null; 1233 } 1234 } 1235 1236 /** 1237 * Launches an intent which may optionally have a user id defined. 1238 * @param fragment Fragment to use to launch the activity. 1239 * @param intent Intent to launch. 1240 */ launchIntent(Fragment fragment, Intent intent)1241 public static void launchIntent(Fragment fragment, Intent intent) { 1242 try { 1243 final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); 1244 1245 if (userId == -1) { 1246 fragment.startActivity(intent); 1247 } else { 1248 fragment.getActivity().startActivityAsUser(intent, new UserHandle(userId)); 1249 } 1250 } catch (ActivityNotFoundException e) { 1251 Log.w(TAG, "No activity found for " + intent); 1252 } 1253 } 1254 isCarrierDemoUser(Context context)1255 public static boolean isCarrierDemoUser(Context context) { 1256 final String carrierDemoModeSetting = 1257 context.getString(com.android.internal.R.string.config_carrierDemoModeSetting); 1258 return UserManager.isDeviceInDemoMode(context) 1259 && getUserManager(context).isDemoUser() 1260 && !TextUtils.isEmpty(carrierDemoModeSetting) 1261 && (Settings.Secure.getInt(context.getContentResolver(), 1262 carrierDemoModeSetting, 0) == 1); 1263 } 1264 1265 /** 1266 * Returns if a given user is a profile of another user. 1267 * @param user The user whose profiles will be checked. 1268 * @param profile The (potential) profile. 1269 * @return if the profile is actually a profile 1270 */ isProfileOf(UserInfo user, UserInfo profile)1271 public static boolean isProfileOf(UserInfo user, UserInfo profile) { 1272 return user.id == profile.id || 1273 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID 1274 && user.profileGroupId == profile.profileGroupId); 1275 } 1276 1277 /** 1278 * Tries to initalize a volume with the given bundle. If it is a valid, private, and readable 1279 * {@link VolumeInfo}, it is returned. If it is not valid, null is returned. 1280 */ 1281 @Nullable maybeInitializeVolume(StorageManager sm, Bundle bundle)1282 public static VolumeInfo maybeInitializeVolume(StorageManager sm, Bundle bundle) { 1283 final String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID, 1284 VolumeInfo.ID_PRIVATE_INTERNAL); 1285 VolumeInfo volume = sm.findVolumeById(volumeId); 1286 return isVolumeValid(volume) ? volume : null; 1287 } 1288 1289 /** 1290 * Return the resource id to represent the install status for an app 1291 */ 1292 @StringRes getInstallationStatus(ApplicationInfo info)1293 public static int getInstallationStatus(ApplicationInfo info) { 1294 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 1295 return R.string.not_installed; 1296 } 1297 return info.enabled ? R.string.installed : R.string.disabled; 1298 } 1299 1300 /** 1301 * Control if other apps can display overlays. By default this is allowed. Be sure to 1302 * re-enable overlays, as the effect is system-wide. 1303 */ setOverlayAllowed(Context context, IBinder token, boolean allowed)1304 public static void setOverlayAllowed(Context context, IBinder token, boolean allowed) { 1305 AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); 1306 if (appOpsManager != null) { 1307 appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, 1308 !allowed, token); 1309 appOpsManager.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW, 1310 !allowed, token); 1311 } 1312 } 1313 1314 1315 isVolumeValid(VolumeInfo volume)1316 private static boolean isVolumeValid(VolumeInfo volume) { 1317 return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE) 1318 && volume.isMountedReadable(); 1319 } 1320 1321 } 1322