1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.shared.recents.utilities;
18 
19 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
20 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
21 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
22 
23 import android.annotation.TargetApi;
24 import android.content.Context;
25 import android.graphics.Color;
26 import android.graphics.Rect;
27 import android.inputmethodservice.InputMethodService;
28 import android.os.Build;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.util.DisplayMetrics;
32 import android.view.Surface;
33 import android.view.WindowManager;
34 
35 /* Common code */
36 public class Utilities {
37 
38     private static final float TABLET_MIN_DPS = 600;
39 
40     /**
41      * Posts a runnable on a handler at the front of the queue ignoring any sync barriers.
42      */
postAtFrontOfQueueAsynchronously(Handler h, Runnable r)43     public static void postAtFrontOfQueueAsynchronously(Handler h, Runnable r) {
44         Message msg = h.obtainMessage().setCallback(r);
45         h.sendMessageAtFrontOfQueue(msg);
46     }
47 
isRotationAnimationCCW(int from, int to)48     public static boolean isRotationAnimationCCW(int from, int to) {
49         // All 180deg WM rotation animations are CCW, match that
50         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
51         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
52         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
53         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
54         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
55         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
56         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
57         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
58         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
59         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
60         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
61         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
62         return false; // Default
63     }
64 
65     /**
66      * Compares the ratio of two quantities and returns whether that ratio is greater than the
67      * provided bound. Order of quantities does not matter. Bound should be a decimal representation
68      * of a percentage.
69      */
isRelativePercentDifferenceGreaterThan(float first, float second, float bound)70     public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
71             float bound) {
72         return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
73     }
74 
75     /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
computeContrastBetweenColors(int bg, int fg)76     public static float computeContrastBetweenColors(int bg, int fg) {
77         float bgR = Color.red(bg) / 255f;
78         float bgG = Color.green(bg) / 255f;
79         float bgB = Color.blue(bg) / 255f;
80         bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
81         bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
82         bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
83         float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
84 
85         float fgR = Color.red(fg) / 255f;
86         float fgG = Color.green(fg) / 255f;
87         float fgB = Color.blue(fg) / 255f;
88         fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
89         fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
90         fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
91         float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
92 
93         return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
94     }
95 
96     /**
97      * @return the clamped {@param value} between the provided {@param min} and {@param max}.
98      */
clamp(float value, float min, float max)99     public static float clamp(float value, float min, float max) {
100         return Math.max(min, Math.min(max, value));
101     }
102 
103     /**
104      * @return updated set of flags from InputMethodService based off {@param oldHints}
105      *          Leaves original hints unmodified
106      */
calculateBackDispositionHints(int oldHints, int backDisposition, boolean imeShown, boolean showImeSwitcher)107     public static int calculateBackDispositionHints(int oldHints, int backDisposition,
108             boolean imeShown, boolean showImeSwitcher) {
109         int hints = oldHints;
110         switch (backDisposition) {
111             case InputMethodService.BACK_DISPOSITION_DEFAULT:
112             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
113             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
114                 if (imeShown) {
115                     hints |= NAVIGATION_HINT_BACK_ALT;
116                 } else {
117                     hints &= ~NAVIGATION_HINT_BACK_ALT;
118                 }
119                 break;
120             case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
121                 hints &= ~NAVIGATION_HINT_BACK_ALT;
122                 break;
123         }
124         if (imeShown) {
125             hints |= NAVIGATION_HINT_IME_SHOWN;
126         } else {
127             hints &= ~NAVIGATION_HINT_IME_SHOWN;
128         }
129         if (showImeSwitcher) {
130             hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN;
131         } else {
132             hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN;
133         }
134 
135         return hints;
136     }
137 
138     /** @return whether or not {@param context} represents that of a large screen device or not */
139     @TargetApi(Build.VERSION_CODES.R)
isLargeScreen(Context context)140     public static boolean isLargeScreen(Context context) {
141         final WindowManager windowManager = context.getSystemService(WindowManager.class);
142         final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
143 
144         float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
145                 context.getResources().getConfiguration().densityDpi);
146         return smallestWidth >= TABLET_MIN_DPS;
147     }
148 
dpiFromPx(float size, int densityDpi)149     public static float dpiFromPx(float size, int densityDpi) {
150         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
151         return (size / densityRatio);
152     }
153 }
154