1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.annotation.TargetApi; 8 import android.app.Activity; 9 import android.app.ActivityManager; 10 import android.app.ActivityOptions; 11 import android.app.PendingIntent; 12 import android.content.ContentResolver; 13 import android.content.Context; 14 import android.content.Intent; 15 import android.content.pm.PackageManager; 16 import android.content.res.Configuration; 17 import android.content.res.Resources; 18 import android.content.res.Resources.NotFoundException; 19 import android.graphics.Bitmap; 20 import android.graphics.Color; 21 import android.graphics.ColorFilter; 22 import android.graphics.Rect; 23 import android.graphics.drawable.Drawable; 24 import android.graphics.drawable.VectorDrawable; 25 import android.net.Uri; 26 import android.os.Build; 27 import android.os.Bundle; 28 import android.os.PowerManager; 29 import android.os.Process; 30 import android.os.StatFs; 31 import android.os.StrictMode; 32 import android.os.UserManager; 33 import android.provider.Settings; 34 import android.support.annotation.NonNull; 35 import android.text.Html; 36 import android.text.Spanned; 37 import android.view.View; 38 import android.view.Window; 39 import android.view.WindowManager; 40 import android.view.inputmethod.InputMethodSubtype; 41 import android.view.textclassifier.TextClassifier; 42 import android.widget.TextView; 43 44 import java.io.File; 45 import java.io.UnsupportedEncodingException; 46 47 /** 48 * Utility class to use new APIs that were added after ICS (API level 14). 49 */ 50 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 51 public class ApiCompatibilityUtils { ApiCompatibilityUtils()52 private ApiCompatibilityUtils() { 53 } 54 55 /** 56 * Compares two long values numerically. The value returned is identical to what would be 57 * returned by {@link Long#compare(long, long)} which is available since API level 19. 58 */ compareLong(long lhs, long rhs)59 public static int compareLong(long lhs, long rhs) { 60 return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1); 61 } 62 63 /** 64 * Compares two boolean values. The value returned is identical to what would be returned by 65 * {@link Boolean#compare(boolean, boolean)} which is available since API level 19. 66 */ compareBoolean(boolean lhs, boolean rhs)67 public static int compareBoolean(boolean lhs, boolean rhs) { 68 return lhs == rhs ? 0 : lhs ? 1 : -1; 69 } 70 71 /** 72 * Checks that the object reference is not null and throws NullPointerException if it is. 73 * See {@link Objects#requireNonNull} which is available since API level 19. 74 * @param obj The object to check 75 */ 76 @NonNull requireNonNull(T obj)77 public static <T> T requireNonNull(T obj) { 78 if (obj == null) throw new NullPointerException(); 79 return obj; 80 } 81 82 /** 83 * Checks that the object reference is not null and throws NullPointerException if it is. 84 * See {@link Objects#requireNonNull} which is available since API level 19. 85 * @param obj The object to check 86 * @param message The message to put into NullPointerException 87 */ 88 @NonNull requireNonNull(T obj, String message)89 public static <T> T requireNonNull(T obj, String message) { 90 if (obj == null) throw new NullPointerException(message); 91 return obj; 92 } 93 94 /** 95 * {@link String#getBytes()} but specifying UTF-8 as the encoding and capturing the resulting 96 * UnsupportedEncodingException. 97 */ getBytesUtf8(String str)98 public static byte[] getBytesUtf8(String str) { 99 try { 100 return str.getBytes("UTF-8"); 101 } catch (UnsupportedEncodingException e) { 102 throw new IllegalStateException("UTF-8 encoding not available.", e); 103 } 104 } 105 106 /** 107 * Returns true if view's layout direction is right-to-left. 108 * 109 * @param view the View whose layout is being considered 110 */ isLayoutRtl(View view)111 public static boolean isLayoutRtl(View view) { 112 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 113 return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 114 } else { 115 // All layouts are LTR before JB MR1. 116 return false; 117 } 118 } 119 120 /** 121 * @see Configuration#getLayoutDirection() 122 */ getLayoutDirection(Configuration configuration)123 public static int getLayoutDirection(Configuration configuration) { 124 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 125 return configuration.getLayoutDirection(); 126 } else { 127 // All layouts are LTR before JB MR1. 128 return View.LAYOUT_DIRECTION_LTR; 129 } 130 } 131 132 /** 133 * @return True if the running version of the Android supports printing. 134 */ isPrintingSupported()135 public static boolean isPrintingSupported() { 136 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 137 } 138 139 /** 140 * @return True if the running version of the Android supports elevation. Elevation of a view 141 * determines the visual appearance of its shadow. 142 */ isElevationSupported()143 public static boolean isElevationSupported() { 144 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 145 } 146 147 /** 148 * @see android.view.View#setLayoutDirection(int) 149 */ setLayoutDirection(View view, int layoutDirection)150 public static void setLayoutDirection(View view, int layoutDirection) { 151 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 152 view.setLayoutDirection(layoutDirection); 153 } else { 154 // Do nothing. RTL layouts aren't supported before JB MR1. 155 } 156 } 157 158 /** 159 * @see android.view.View#setTextAlignment(int) 160 */ setTextAlignment(View view, int textAlignment)161 public static void setTextAlignment(View view, int textAlignment) { 162 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 163 view.setTextAlignment(textAlignment); 164 } else { 165 // Do nothing. RTL text isn't supported before JB MR1. 166 } 167 } 168 169 /** 170 * @see android.view.View#setTextDirection(int) 171 */ setTextDirection(View view, int textDirection)172 public static void setTextDirection(View view, int textDirection) { 173 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 174 view.setTextDirection(textDirection); 175 } else { 176 // Do nothing. RTL text isn't supported before JB MR1. 177 } 178 } 179 180 /** 181 * See {@link android.view.View#setLabelFor(int)}. 182 */ setLabelFor(View labelView, int id)183 public static void setLabelFor(View labelView, int id) { 184 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 185 labelView.setLabelFor(id); 186 } else { 187 // Do nothing. #setLabelFor() isn't supported before JB MR1. 188 } 189 } 190 191 /** 192 * @see android.widget.TextView#getCompoundDrawablesRelative() 193 */ getCompoundDrawablesRelative(TextView textView)194 public static Drawable[] getCompoundDrawablesRelative(TextView textView) { 195 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 196 return textView.getCompoundDrawablesRelative(); 197 } else { 198 return textView.getCompoundDrawables(); 199 } 200 } 201 202 /** 203 * @see android.widget.TextView#setCompoundDrawablesRelative(Drawable, Drawable, Drawable, 204 * Drawable) 205 */ setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top, Drawable end, Drawable bottom)206 public static void setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top, 207 Drawable end, Drawable bottom) { 208 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { 209 // On JB MR1, due to a platform bug, setCompoundDrawablesRelative() is a no-op if the 210 // view has ever been measured. As a workaround, use setCompoundDrawables() directly. 211 // See: http://crbug.com/368196 and http://crbug.com/361709 212 boolean isRtl = isLayoutRtl(textView); 213 textView.setCompoundDrawables(isRtl ? end : start, top, isRtl ? start : end, bottom); 214 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { 215 textView.setCompoundDrawablesRelative(start, top, end, bottom); 216 } else { 217 textView.setCompoundDrawables(start, top, end, bottom); 218 } 219 } 220 221 /** 222 * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, 223 * Drawable, Drawable, Drawable) 224 */ setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, Drawable start, Drawable top, Drawable end, Drawable bottom)225 public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, 226 Drawable start, Drawable top, Drawable end, Drawable bottom) { 227 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { 228 // Work around the platform bug described in setCompoundDrawablesRelative() above. 229 boolean isRtl = isLayoutRtl(textView); 230 textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top, 231 isRtl ? start : end, bottom); 232 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { 233 textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); 234 } else { 235 textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); 236 } 237 } 238 239 /** 240 * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, 241 * int) 242 */ setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, int start, int top, int end, int bottom)243 public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, 244 int start, int top, int end, int bottom) { 245 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) { 246 // Work around the platform bug described in setCompoundDrawablesRelative() above. 247 boolean isRtl = isLayoutRtl(textView); 248 textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top, 249 isRtl ? start : end, bottom); 250 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { 251 textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); 252 } else { 253 textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom); 254 } 255 } 256 257 /** 258 * @see android.text.Html#toHtml(Spanned, int) 259 * @param option is ignored on below N 260 */ 261 @SuppressWarnings("deprecation") toHtml(Spanned spanned, int option)262 public static String toHtml(Spanned spanned, int option) { 263 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 264 return Html.toHtml(spanned, option); 265 } else { 266 return Html.toHtml(spanned); 267 } 268 } 269 270 // These methods have a new name, and the old name is deprecated. 271 272 /** 273 * @see android.app.PendingIntent#getCreatorPackage() 274 */ 275 @SuppressWarnings("deprecation") getCreatorPackage(PendingIntent intent)276 public static String getCreatorPackage(PendingIntent intent) { 277 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 278 return intent.getCreatorPackage(); 279 } else { 280 return intent.getTargetPackage(); 281 } 282 } 283 284 /** 285 * @see android.provider.Settings.Global#DEVICE_PROVISIONED 286 */ 287 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) isDeviceProvisioned(Context context)288 public static boolean isDeviceProvisioned(Context context) { 289 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return true; 290 if (context == null) return true; 291 if (context.getContentResolver() == null) return true; 292 return Settings.Global.getInt( 293 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; 294 } 295 296 /** 297 * @see android.app.Activity#finishAndRemoveTask() 298 */ finishAndRemoveTask(Activity activity)299 public static void finishAndRemoveTask(Activity activity) { 300 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { 301 activity.finishAndRemoveTask(); 302 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { 303 // crbug.com/395772 : Fallback for Activity.finishAndRemoveTask() failing. 304 new FinishAndRemoveTaskWithRetry(activity).run(); 305 } else { 306 activity.finish(); 307 } 308 } 309 310 /** 311 * Set elevation if supported. 312 */ 313 @TargetApi(Build.VERSION_CODES.LOLLIPOP) setElevation(View view, float elevationValue)314 public static boolean setElevation(View view, float elevationValue) { 315 if (!isElevationSupported()) return false; 316 317 view.setElevation(elevationValue); 318 return true; 319 } 320 321 /** 322 * Gets an intent to start the Android system notification settings activity for an app. 323 * 324 * @param context Context of the app whose settings intent should be returned. 325 */ getNotificationSettingsIntent(Context context)326 public static Intent getNotificationSettingsIntent(Context context) { 327 Intent intent = new Intent(); 328 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 329 intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 330 intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); 331 } else { 332 intent.setAction("android.settings.ACTION_APP_NOTIFICATION_SETTINGS"); 333 intent.putExtra("app_package", context.getPackageName()); 334 intent.putExtra("app_uid", context.getApplicationInfo().uid); 335 } 336 return intent; 337 } 338 339 private static class FinishAndRemoveTaskWithRetry implements Runnable { 340 private static final long RETRY_DELAY_MS = 500; 341 private static final long MAX_TRY_COUNT = 3; 342 private final Activity mActivity; 343 private int mTryCount; 344 FinishAndRemoveTaskWithRetry(Activity activity)345 FinishAndRemoveTaskWithRetry(Activity activity) { 346 mActivity = activity; 347 } 348 349 @Override run()350 public void run() { 351 mActivity.finishAndRemoveTask(); 352 mTryCount++; 353 if (!mActivity.isFinishing()) { 354 if (mTryCount < MAX_TRY_COUNT) { 355 ThreadUtils.postOnUiThreadDelayed(this, RETRY_DELAY_MS); 356 } else { 357 mActivity.finish(); 358 } 359 } 360 } 361 } 362 363 /** 364 * @return Whether the screen of the device is interactive. 365 */ 366 @SuppressWarnings("deprecation") isInteractive(Context context)367 public static boolean isInteractive(Context context) { 368 PowerManager manager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 369 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { 370 return manager.isInteractive(); 371 } else { 372 return manager.isScreenOn(); 373 } 374 } 375 376 @SuppressWarnings("deprecation") getActivityNewDocumentFlag()377 public static int getActivityNewDocumentFlag() { 378 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 379 return Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 380 } else { 381 return Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; 382 } 383 } 384 385 /** 386 * @see android.provider.Settings.Secure#SKIP_FIRST_USE_HINTS 387 */ shouldSkipFirstUseHints(ContentResolver contentResolver)388 public static boolean shouldSkipFirstUseHints(ContentResolver contentResolver) { 389 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 390 return Settings.Secure.getInt( 391 contentResolver, Settings.Secure.SKIP_FIRST_USE_HINTS, 0) != 0; 392 } else { 393 return false; 394 } 395 } 396 397 /** 398 * @param activity Activity that should get the task description update. 399 * @param title Title of the activity. 400 * @param icon Icon of the activity. 401 * @param color Color of the activity. It must be a fully opaque color. 402 */ setTaskDescription(Activity activity, String title, Bitmap icon, int color)403 public static void setTaskDescription(Activity activity, String title, Bitmap icon, int color) { 404 // TaskDescription requires an opaque color. 405 assert Color.alpha(color) == 255; 406 407 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 408 ActivityManager.TaskDescription description = 409 new ActivityManager.TaskDescription(title, icon, color); 410 activity.setTaskDescription(description); 411 } 412 } 413 414 /** 415 * @see android.view.Window#setStatusBarColor(int color). 416 */ setStatusBarColor(Window window, int statusBarColor)417 public static void setStatusBarColor(Window window, int statusBarColor) { 418 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; 419 420 // If both system bars are black, we can remove these from our layout, 421 // removing or shrinking the SurfaceFlinger overlay required for our views. 422 // This benefits battery usage on L and M. However, this no longer provides a battery 423 // benefit as of N and starts to cause flicker bugs on O, so don't bother on O and up. 424 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && statusBarColor == Color.BLACK 425 && window.getNavigationBarColor() == Color.BLACK) { 426 window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 427 } else { 428 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 429 } 430 window.setStatusBarColor(statusBarColor); 431 } 432 433 /** 434 * Sets the status bar icons to dark or light. Note that this is only valid for 435 * Android M+. 436 * 437 * @param rootView The root view used to request updates to the system UI theming. 438 * @param useDarkIcons Whether the status bar icons should be dark. 439 */ setStatusBarIconColor(View rootView, boolean useDarkIcons)440 public static void setStatusBarIconColor(View rootView, boolean useDarkIcons) { 441 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; 442 443 int systemUiVisibility = rootView.getSystemUiVisibility(); 444 if (useDarkIcons) { 445 systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 446 } else { 447 systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 448 } 449 rootView.setSystemUiVisibility(systemUiVisibility); 450 } 451 452 /** 453 * @see android.content.res.Resources#getDrawable(int id). 454 * TODO(ltian): use {@link AppCompatResources} to parse drawable to prevent fail on 455 * {@link VectorDrawable}. (http://crbug.com/792129) 456 */ 457 @SuppressWarnings("deprecation") getDrawable(Resources res, int id)458 public static Drawable getDrawable(Resources res, int id) throws NotFoundException { 459 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); 460 try { 461 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 462 return res.getDrawable(id, null); 463 } else { 464 return res.getDrawable(id); 465 } 466 } finally { 467 StrictMode.setThreadPolicy(oldPolicy); 468 } 469 } 470 471 /** 472 * @see android.content.res.Resources#getDrawableForDensity(int id, int density). 473 */ 474 @SuppressWarnings("deprecation") getDrawableForDensity(Resources res, int id, int density)475 public static Drawable getDrawableForDensity(Resources res, int id, int density) { 476 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 477 return res.getDrawableForDensity(id, density, null); 478 } else { 479 return res.getDrawableForDensity(id, density); 480 } 481 } 482 483 /** 484 * @see android.app.Activity#finishAfterTransition(). 485 */ finishAfterTransition(Activity activity)486 public static void finishAfterTransition(Activity activity) { 487 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 488 activity.finishAfterTransition(); 489 } else { 490 activity.finish(); 491 } 492 } 493 494 /** 495 * @see android.content.pm.PackageManager#getUserBadgedIcon(Drawable, android.os.UserHandle). 496 */ getUserBadgedIcon(Context context, int id)497 public static Drawable getUserBadgedIcon(Context context, int id) { 498 Drawable drawable = getDrawable(context.getResources(), id); 499 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 500 PackageManager packageManager = context.getPackageManager(); 501 drawable = packageManager.getUserBadgedIcon(drawable, Process.myUserHandle()); 502 } 503 return drawable; 504 } 505 506 /** 507 * @see android.content.pm.PackageManager#getUserBadgedDrawableForDensity(Drawable drawable, 508 * UserHandle user, Rect badgeLocation, int badgeDensity). 509 */ getUserBadgedDrawableForDensity( Context context, Drawable drawable, Rect badgeLocation, int density)510 public static Drawable getUserBadgedDrawableForDensity( 511 Context context, Drawable drawable, Rect badgeLocation, int density) { 512 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 513 PackageManager packageManager = context.getPackageManager(); 514 return packageManager.getUserBadgedDrawableForDensity( 515 drawable, Process.myUserHandle(), badgeLocation, density); 516 } 517 return drawable; 518 } 519 520 /** 521 * @see android.content.res.Resources#getColor(int id). 522 */ 523 @SuppressWarnings("deprecation") getColor(Resources res, int id)524 public static int getColor(Resources res, int id) throws NotFoundException { 525 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 526 return res.getColor(id, null); 527 } else { 528 return res.getColor(id); 529 } 530 } 531 532 /** 533 * @see android.graphics.drawable.Drawable#getColorFilter(). 534 */ 535 @SuppressWarnings("NewApi") getColorFilter(Drawable drawable)536 public static ColorFilter getColorFilter(Drawable drawable) { 537 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 538 return drawable.getColorFilter(); 539 } else { 540 return null; 541 } 542 } 543 544 /** 545 * @see android.widget.TextView#setTextAppearance(int id). 546 */ 547 @SuppressWarnings("deprecation") setTextAppearance(TextView view, int id)548 public static void setTextAppearance(TextView view, int id) { 549 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 550 view.setTextAppearance(id); 551 } else { 552 view.setTextAppearance(view.getContext(), id); 553 } 554 } 555 556 /** 557 * See {@link android.os.StatFs#getAvailableBlocksLong}. 558 */ 559 @SuppressWarnings("deprecation") getAvailableBlocks(StatFs statFs)560 public static long getAvailableBlocks(StatFs statFs) { 561 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 562 return statFs.getAvailableBlocksLong(); 563 } else { 564 return statFs.getAvailableBlocks(); 565 } 566 } 567 568 /** 569 * See {@link android.os.StatFs#getBlockCount}. 570 */ 571 @SuppressWarnings("deprecation") getBlockCount(StatFs statFs)572 public static long getBlockCount(StatFs statFs) { 573 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 574 return statFs.getBlockCountLong(); 575 } else { 576 return statFs.getBlockCount(); 577 } 578 } 579 580 /** 581 * See {@link android.os.StatFs#getBlockSize}. 582 */ 583 @SuppressWarnings("deprecation") getBlockSize(StatFs statFs)584 public static long getBlockSize(StatFs statFs) { 585 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 586 return statFs.getBlockSizeLong(); 587 } else { 588 return statFs.getBlockSize(); 589 } 590 } 591 592 /** 593 * @param context The Android context, used to retrieve the UserManager system service. 594 * @return Whether the device is running in demo mode. 595 */ 596 @SuppressWarnings("NewApi") isDemoUser(Context context)597 public static boolean isDemoUser(Context context) { 598 // UserManager#isDemoUser() is only available in Android NMR1+. 599 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return false; 600 601 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 602 return userManager.isDemoUser(); 603 } 604 605 /** 606 * @see Context#checkPermission(String, int, int) 607 */ checkPermission(Context context, String permission, int pid, int uid)608 public static int checkPermission(Context context, String permission, int pid, int uid) { 609 try { 610 return context.checkPermission(permission, pid, uid); 611 } catch (RuntimeException e) { 612 // Some older versions of Android throw odd errors when checking for permissions, so 613 // just swallow the exception and treat it as the permission is denied. 614 // crbug.com/639099 615 return PackageManager.PERMISSION_DENIED; 616 } 617 } 618 619 /** 620 * @see android.view.inputmethod.InputMethodSubType#getLocate() 621 */ 622 @SuppressWarnings("deprecation") getLocale(InputMethodSubtype inputMethodSubType)623 public static String getLocale(InputMethodSubtype inputMethodSubType) { 624 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 625 return inputMethodSubType.getLanguageTag(); 626 } else { 627 return inputMethodSubType.getLocale(); 628 } 629 } 630 631 /** 632 * Get a URI for |file| which has the image capture. This function assumes that path of |file| 633 * is based on the result of UiUtils.getDirectoryForImageCapture(). 634 * 635 * @param file image capture file. 636 * @return URI for |file|. 637 */ getUriForImageCaptureFile(File file)638 public static Uri getUriForImageCaptureFile(File file) { 639 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 640 ? ContentUriUtils.getContentUriFromFile(file) 641 : Uri.fromFile(file); 642 } 643 644 /** 645 * Get the URI for a downloaded file. 646 * 647 * @param file A downloaded file. 648 * @return URI for |file|. 649 */ getUriForDownloadedFile(File file)650 public static Uri getUriForDownloadedFile(File file) { 651 return Build.VERSION.SDK_INT > Build.VERSION_CODES.M 652 ? FileUtils.getUriForFile(file) 653 : Uri.fromFile(file); 654 } 655 656 /** 657 * @see android.view.Window#FEATURE_INDETERMINATE_PROGRESS 658 */ setWindowIndeterminateProgress(Window window)659 public static void setWindowIndeterminateProgress(Window window) { 660 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 661 @SuppressWarnings("deprecation") 662 int featureNumber = Window.FEATURE_INDETERMINATE_PROGRESS; 663 664 @SuppressWarnings("deprecation") 665 int featureValue = Window.PROGRESS_VISIBILITY_OFF; 666 667 window.setFeatureInt(featureNumber, featureValue); 668 } 669 } 670 671 /** 672 * @param activity The {@link Activity} to check. 673 * @return Whether or not {@code activity} is currently in Android N+ multi-window mode. 674 */ isInMultiWindowMode(Activity activity)675 public static boolean isInMultiWindowMode(Activity activity) { 676 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 677 return false; 678 } 679 return activity.isInMultiWindowMode(); 680 } 681 682 /** 683 * Disables the Smart Select {@link TextClassifier} for the given {@link TextView} instance. 684 * @param textView The {@link TextView} that should have its classifier disabled. 685 */ 686 @TargetApi(Build.VERSION_CODES.O) disableSmartSelectionTextClassifier(TextView textView)687 public static void disableSmartSelectionTextClassifier(TextView textView) { 688 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; 689 690 textView.setTextClassifier(TextClassifier.NO_OP); 691 } 692 693 /** 694 * Creates an ActivityOptions Bundle with basic options and the LaunchDisplayId set. 695 * @param displayId The id of the display to launch into. 696 * @return The created bundle, or null if unsupported. 697 */ createLaunchDisplayIdActivityOptions(int displayId)698 public static Bundle createLaunchDisplayIdActivityOptions(int displayId) { 699 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null; 700 701 ActivityOptions options = ActivityOptions.makeBasic(); 702 options.setLaunchDisplayId(displayId); 703 return options.toBundle(); 704 } 705 } 706