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
5package org.chromium.base;
6
7import android.annotation.TargetApi;
8import android.app.Activity;
9import android.app.ActivityManager;
10import android.app.PendingIntent;
11import android.content.ContentResolver;
12import android.content.Context;
13import android.content.Intent;
14import android.content.pm.PackageManager;
15import android.content.res.ColorStateList;
16import android.content.res.Configuration;
17import android.content.res.Resources;
18import android.content.res.Resources.NotFoundException;
19import android.graphics.Bitmap;
20import android.graphics.Color;
21import android.graphics.ColorFilter;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.os.Build;
25import android.os.PowerManager;
26import android.os.Process;
27import android.os.StatFs;
28import android.os.UserManager;
29import android.provider.Settings;
30import android.view.View;
31import android.view.ViewGroup.MarginLayoutParams;
32import android.view.Window;
33import android.view.WindowManager;
34import android.widget.TextView;
35
36import 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)
42public class 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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")
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)
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     */
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)
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
323        FinishAndRemoveTaskWithRetry(Activity activity) {
324            mActivity = activity;
325        }
326
327        @Override
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")
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")
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     */
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     */
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     */
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")
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")
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     */
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     */
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     */
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")
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")
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")
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")
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")
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")
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     */
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