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