• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.PendingIntent;
11 import android.content.ContentResolver;
12 import android.content.Context;
13 import android.content.Intent;
14 import android.content.pm.PackageManager;
15 import android.content.res.ColorStateList;
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.os.Build;
25 import android.os.PowerManager;
26 import android.os.Process;
27 import android.os.StatFs;
28 import android.os.UserManager;
29 import android.provider.Settings;
30 import android.view.View;
31 import android.view.ViewGroup.MarginLayoutParams;
32 import android.view.Window;
33 import android.view.WindowManager;
34 import android.widget.TextView;
35 
36 import java.lang.reflect.Method;
37 
38 /**
39  * Utility class to use new APIs that were added after ICS (API level 14).
40  */
41 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
42 public class ApiCompatibilityUtils {
ApiCompatibilityUtils()43     private ApiCompatibilityUtils() {
44     }
45 
46     /**
47      * Returns true if view's layout direction is right-to-left.
48      *
49      * @param view the View whose layout is being considered
50      */
isLayoutRtl(View view)51     public static boolean isLayoutRtl(View view) {
52         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
53             return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
54         } else {
55             // All layouts are LTR before JB MR1.
56             return false;
57         }
58     }
59 
60     /**
61      * @see Configuration#getLayoutDirection()
62      */
getLayoutDirection(Configuration configuration)63     public static int getLayoutDirection(Configuration configuration) {
64         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
65             return configuration.getLayoutDirection();
66         } else {
67             // All layouts are LTR before JB MR1.
68             return View.LAYOUT_DIRECTION_LTR;
69         }
70     }
71 
72     /**
73      * @return True if the running version of the Android supports printing.
74      */
isPrintingSupported()75     public static boolean isPrintingSupported() {
76         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
77     }
78 
79     /**
80      * @return True if the running version of the Android supports elevation. Elevation of a view
81      * determines the visual appearance of its shadow.
82      */
isElevationSupported()83     public static boolean isElevationSupported() {
84         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
85     }
86 
87     /**
88      * @see android.view.View#setLayoutDirection(int)
89      */
setLayoutDirection(View view, int layoutDirection)90     public static void setLayoutDirection(View view, int layoutDirection) {
91         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
92             view.setLayoutDirection(layoutDirection);
93         } else {
94             // Do nothing. RTL layouts aren't supported before JB MR1.
95         }
96     }
97 
98     /**
99      * @see android.view.View#setTextAlignment(int)
100      */
setTextAlignment(View view, int textAlignment)101     public static void setTextAlignment(View view, int textAlignment) {
102         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
103             view.setTextAlignment(textAlignment);
104         } else {
105             // Do nothing. RTL text isn't supported before JB MR1.
106         }
107     }
108 
109     /**
110      * @see android.view.View#setTextDirection(int)
111      */
setTextDirection(View view, int textDirection)112     public static void setTextDirection(View view, int textDirection) {
113         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
114             view.setTextDirection(textDirection);
115         } else {
116             // Do nothing. RTL text isn't supported before JB MR1.
117         }
118     }
119 
120     /**
121      * See {@link android.view.View#setLabelFor(int)}.
122      */
setLabelFor(View labelView, int id)123     public static void setLabelFor(View labelView, int id) {
124         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
125             labelView.setLabelFor(id);
126         } else {
127             // Do nothing. #setLabelFor() isn't supported before JB MR1.
128         }
129     }
130 
131     /**
132      * @see android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)
133      */
setMarginEnd(MarginLayoutParams layoutParams, int end)134     public static void setMarginEnd(MarginLayoutParams layoutParams, int end) {
135         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
136             layoutParams.setMarginEnd(end);
137         } else {
138             layoutParams.rightMargin = end;
139         }
140     }
141 
142     /**
143      * @see android.view.ViewGroup.MarginLayoutParams#getMarginEnd()
144      */
getMarginEnd(MarginLayoutParams layoutParams)145     public static int getMarginEnd(MarginLayoutParams layoutParams) {
146         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
147             return layoutParams.getMarginEnd();
148         } else {
149             return layoutParams.rightMargin;
150         }
151     }
152 
153     /**
154      * @see android.view.ViewGroup.MarginLayoutParams#setMarginStart(int)
155      */
setMarginStart(MarginLayoutParams layoutParams, int start)156     public static void setMarginStart(MarginLayoutParams layoutParams, int start) {
157         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
158             layoutParams.setMarginStart(start);
159         } else {
160             layoutParams.leftMargin = start;
161         }
162     }
163 
164     /**
165      * @see android.view.ViewGroup.MarginLayoutParams#getMarginStart()
166      */
getMarginStart(MarginLayoutParams layoutParams)167     public static int getMarginStart(MarginLayoutParams layoutParams) {
168         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
169             return layoutParams.getMarginStart();
170         } else {
171             return layoutParams.leftMargin;
172         }
173     }
174 
175     /**
176      * @see android.view.View#setPaddingRelative(int, int, int, int)
177      */
setPaddingRelative(View view, int start, int top, int end, int bottom)178     public static void setPaddingRelative(View view, int start, int top, int end, int bottom) {
179         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
180             view.setPaddingRelative(start, top, end, bottom);
181         } else {
182             // Before JB MR1, all layouts are left-to-right, so start == left, etc.
183             view.setPadding(start, top, end, bottom);
184         }
185     }
186 
187     /**
188      * @see android.view.View#getPaddingStart()
189      */
getPaddingStart(View view)190     public static int getPaddingStart(View view) {
191         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
192             return view.getPaddingStart();
193         } else {
194             // Before JB MR1, all layouts are left-to-right, so start == left.
195             return view.getPaddingLeft();
196         }
197     }
198 
199     /**
200      * @see android.view.View#getPaddingEnd()
201      */
getPaddingEnd(View view)202     public static int getPaddingEnd(View view) {
203         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
204             return view.getPaddingEnd();
205         } else {
206             // Before JB MR1, all layouts are left-to-right, so end == right.
207             return view.getPaddingRight();
208         }
209     }
210 
211     /**
212      * @see android.widget.TextView#setCompoundDrawablesRelative(Drawable, Drawable, Drawable,
213      *      Drawable)
214      */
setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top, Drawable end, Drawable bottom)215     public static void setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top,
216             Drawable end, Drawable bottom) {
217         if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
218             // On JB MR1, due to a platform bug, setCompoundDrawablesRelative() is a no-op if the
219             // view has ever been measured. As a workaround, use setCompoundDrawables() directly.
220             // See: http://crbug.com/368196 and http://crbug.com/361709
221             boolean isRtl = isLayoutRtl(textView);
222             textView.setCompoundDrawables(isRtl ? end : start, top, isRtl ? start : end, bottom);
223         } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
224             textView.setCompoundDrawablesRelative(start, top, end, bottom);
225         } else {
226             textView.setCompoundDrawables(start, top, end, bottom);
227         }
228     }
229 
230     /**
231      * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable,
232      *      Drawable, Drawable, Drawable)
233      */
setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, Drawable start, Drawable top, Drawable end, Drawable bottom)234     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView,
235             Drawable start, Drawable top, Drawable end, Drawable bottom) {
236         if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
237             // Work around the platform bug described in setCompoundDrawablesRelative() above.
238             boolean isRtl = isLayoutRtl(textView);
239             textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top,
240                     isRtl ? start : end, bottom);
241         } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
242             textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
243         } else {
244             textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
245         }
246     }
247 
248     /**
249      * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int,
250      *      int)
251      */
setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, int start, int top, int end, int bottom)252     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView,
253             int start, int top, int end, int bottom) {
254         if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
255             // Work around the platform bug described in setCompoundDrawablesRelative() above.
256             boolean isRtl = isLayoutRtl(textView);
257             textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top,
258                     isRtl ? start : end, bottom);
259         } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
260             textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
261         } else {
262             textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
263         }
264     }
265 
266     // These methods have a new name, and the old name is deprecated.
267 
268     /**
269      * @see android.app.PendingIntent#getCreatorPackage()
270      */
271     @SuppressWarnings("deprecation")
getCreatorPackage(PendingIntent intent)272     public static String getCreatorPackage(PendingIntent intent) {
273         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
274             return intent.getCreatorPackage();
275         } else {
276             return intent.getTargetPackage();
277         }
278     }
279 
280     /**
281      * @see android.provider.Settings.Global#DEVICE_PROVISIONED
282      */
283     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
isDeviceProvisioned(Context context)284     public static boolean isDeviceProvisioned(Context context) {
285         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return true;
286         if (context == null) return true;
287         if (context.getContentResolver() == null) return true;
288         return Settings.Global.getInt(
289                 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
290     }
291 
292     /**
293      * @see android.app.Activity#finishAndRemoveTask()
294      */
finishAndRemoveTask(Activity activity)295     public static void finishAndRemoveTask(Activity activity) {
296         if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
297             activity.finishAndRemoveTask();
298         } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
299             // crbug.com/395772 : Fallback for Activity.finishAndRemoveTask() failing.
300             new FinishAndRemoveTaskWithRetry(activity).run();
301         } else {
302             activity.finish();
303         }
304     }
305 
306     /**
307      * Set elevation if supported.
308      */
309     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
setElevation(View view, float elevationValue)310     public static boolean setElevation(View view, float elevationValue) {
311         if (!isElevationSupported()) return false;
312 
313         view.setElevation(elevationValue);
314         return true;
315     }
316 
317     private static class FinishAndRemoveTaskWithRetry implements Runnable {
318         private static final long RETRY_DELAY_MS = 500;
319         private static final long MAX_TRY_COUNT = 3;
320         private final Activity mActivity;
321         private int mTryCount;
322 
FinishAndRemoveTaskWithRetry(Activity activity)323         FinishAndRemoveTaskWithRetry(Activity activity) {
324             mActivity = activity;
325         }
326 
327         @Override
run()328         public void run() {
329             mActivity.finishAndRemoveTask();
330             mTryCount++;
331             if (!mActivity.isFinishing()) {
332                 if (mTryCount < MAX_TRY_COUNT) {
333                     ThreadUtils.postOnUiThreadDelayed(this, RETRY_DELAY_MS);
334                 } else {
335                     mActivity.finish();
336                 }
337             }
338         }
339     }
340 
341     /**
342      * @return Whether the screen of the device is interactive.
343      */
344     @SuppressWarnings("deprecation")
isInteractive(Context context)345     public static boolean isInteractive(Context context) {
346         PowerManager manager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
347         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
348             return manager.isInteractive();
349         } else {
350             return manager.isScreenOn();
351         }
352     }
353 
354     @SuppressWarnings("deprecation")
getActivityNewDocumentFlag()355     public static int getActivityNewDocumentFlag() {
356         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
357             return Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
358         } else {
359             return Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;
360         }
361     }
362 
363     /**
364      * @see android.provider.Settings.Secure#SKIP_FIRST_USE_HINTS
365      */
shouldSkipFirstUseHints(ContentResolver contentResolver)366     public static boolean shouldSkipFirstUseHints(ContentResolver contentResolver) {
367         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
368             return Settings.Secure.getInt(
369                     contentResolver, Settings.Secure.SKIP_FIRST_USE_HINTS, 0) != 0;
370         } else {
371             return false;
372         }
373     }
374 
375     /**
376      * @param activity Activity that should get the task description update.
377      * @param title Title of the activity.
378      * @param icon Icon of the activity.
379      * @param color Color of the activity. It must be a fully opaque color.
380      */
setTaskDescription(Activity activity, String title, Bitmap icon, int color)381     public static void setTaskDescription(Activity activity, String title, Bitmap icon, int color) {
382         // TaskDescription requires an opaque color.
383         assert Color.alpha(color) == 255;
384 
385         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
386             ActivityManager.TaskDescription description =
387                     new ActivityManager.TaskDescription(title, icon, color);
388             activity.setTaskDescription(description);
389         }
390     }
391 
392     /**
393      * @see android.view.Window#setStatusBarColor(int color).
394      */
setStatusBarColor(Window window, int statusBarColor)395     public static void setStatusBarColor(Window window, int statusBarColor) {
396         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
397             // If both system bars are black, we can remove these from our layout,
398             // removing or shrinking the SurfaceFlinger overlay required for our views.
399             if (statusBarColor == Color.BLACK && window.getNavigationBarColor() == Color.BLACK) {
400                 window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
401             } else {
402                 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
403             }
404             window.setStatusBarColor(statusBarColor);
405         }
406     }
407 
408     /**
409      * @see android.content.res.Resources#getDrawable(int id).
410      */
411     @SuppressWarnings("deprecation")
getDrawable(Resources res, int id)412     public static Drawable getDrawable(Resources res, int id) throws NotFoundException {
413         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
414             return res.getDrawable(id, null);
415         } else {
416             return res.getDrawable(id);
417         }
418     }
419 
420     /**
421      * @see android.content.res.Resources#getDrawableForDensity(int id, int density).
422      */
423     @SuppressWarnings("deprecation")
getDrawableForDensity(Resources res, int id, int density)424     public static Drawable getDrawableForDensity(Resources res, int id, int density) {
425         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
426             return res.getDrawableForDensity(id, density, null);
427         } else {
428             return res.getDrawableForDensity(id, density);
429         }
430     }
431 
432     /**
433      * @see android.app.Activity#finishAfterTransition().
434      */
finishAfterTransition(Activity activity)435     public static void finishAfterTransition(Activity activity) {
436         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
437             activity.finishAfterTransition();
438         } else {
439             activity.finish();
440         }
441     }
442 
443     /**
444      * @see android.content.pm.PackageManager#getUserBadgedIcon(Drawable, android.os.UserHandle).
445      */
getUserBadgedIcon(Context context, int id)446     public static Drawable getUserBadgedIcon(Context context, int id) {
447         Drawable drawable = getDrawable(context.getResources(), id);
448         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
449             PackageManager packageManager = context.getPackageManager();
450             drawable = packageManager.getUserBadgedIcon(drawable, Process.myUserHandle());
451         }
452         return drawable;
453     }
454 
455     /**
456      * @see android.content.pm.PackageManager#getUserBadgedDrawableForDensity(Drawable drawable,
457      * UserHandle user, Rect badgeLocation, int badgeDensity).
458      */
getUserBadgedDrawableForDensity( Context context, Drawable drawable, Rect badgeLocation, int density)459     public static Drawable getUserBadgedDrawableForDensity(
460             Context context, Drawable drawable, Rect badgeLocation, int density) {
461         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
462             PackageManager packageManager = context.getPackageManager();
463             return packageManager.getUserBadgedDrawableForDensity(
464                     drawable, Process.myUserHandle(), badgeLocation, density);
465         }
466         return drawable;
467     }
468 
469     /**
470      * @see android.content.res.Resources#getColor(int id).
471      */
472     @SuppressWarnings("deprecation")
getColor(Resources res, int id)473     public static int getColor(Resources res, int id) throws NotFoundException {
474         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
475             return res.getColor(id, null);
476         } else {
477             return res.getColor(id);
478         }
479     }
480 
481     /**
482      * @see android.graphics.drawable.Drawable#getColorFilter().
483      */
484     @SuppressWarnings("NewApi")
getColorFilter(Drawable drawable)485     public static ColorFilter getColorFilter(Drawable drawable) {
486         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
487             return drawable.getColorFilter();
488         } else {
489             return null;
490         }
491     }
492 
493     /**
494      * @see android.content.res.Resources#getColorStateList(int id).
495      */
496     @SuppressWarnings("deprecation")
getColorStateList(Resources res, int id)497     public static ColorStateList getColorStateList(Resources res, int id) throws NotFoundException {
498         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
499             return res.getColorStateList(id, null);
500         } else {
501             return res.getColorStateList(id);
502         }
503     }
504 
505     /**
506      * @see android.widget.TextView#setTextAppearance(int id).
507      */
508     @SuppressWarnings("deprecation")
setTextAppearance(TextView view, int id)509     public static void setTextAppearance(TextView view, int id) {
510         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
511             view.setTextAppearance(id);
512         } else {
513             view.setTextAppearance(view.getContext(), id);
514         }
515     }
516 
517     /**
518      * See {@link android.os.StatFs#getBlockCount()}.
519      */
520     @SuppressWarnings("deprecation")
getBlockCount(StatFs statFs)521     public static long getBlockCount(StatFs statFs) {
522         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
523             return statFs.getBlockCountLong();
524         } else {
525             return statFs.getBlockCount();
526         }
527     }
528 
529     /**
530      * See {@link android.os.StatFs#getBlockSize()}.
531      */
532     @SuppressWarnings("deprecation")
getBlockSize(StatFs statFs)533     public static long getBlockSize(StatFs statFs) {
534         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
535             return statFs.getBlockSizeLong();
536         } else {
537             return statFs.getBlockSize();
538         }
539     }
540 
541     /**
542      * @param context The Android context, used to retrieve the UserManager system service.
543      * @return Whether the device is running in demo mode.
544      */
isDemoUser(Context context)545     public static boolean isDemoUser(Context context) {
546         // UserManager#isDemoUser() is only available in Android versions greater than N.
547         if (!BuildInfo.isGreaterThanN()) return false;
548 
549         try {
550             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
551             Method isDemoUserMethod = UserManager.class.getMethod("isDemoUser");
552             boolean isDemoUser = (boolean) isDemoUserMethod.invoke(userManager);
553             return isDemoUser;
554         } catch (RuntimeException e) {
555             // Ignore to avoid crashing on startup.
556         } catch (Exception e) {
557             // Ignore.
558         }
559 
560         return false;
561     }
562 }
563