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.content.res.Resources;
26 import android.graphics.Color;
27 import android.graphics.Rect;
28 import android.inputmethodservice.InputMethodService;
29 import android.os.Build;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.util.DisplayMetrics;
33 import android.view.Surface;
34 import android.view.WindowManager;
35 
36 /* Common code */
37 public class Utilities {
38 
39     private static final float TABLET_MIN_DPS = 600;
40 
41     /**
42      * Posts a runnable on a handler at the front of the queue ignoring any sync barriers.
43      */
postAtFrontOfQueueAsynchronously(Handler h, Runnable r)44     public static void postAtFrontOfQueueAsynchronously(Handler h, Runnable r) {
45         Message msg = h.obtainMessage().setCallback(r);
46         h.sendMessageAtFrontOfQueue(msg);
47     }
48 
isRotationAnimationCCW(int from, int to)49     public static boolean isRotationAnimationCCW(int from, int to) {
50         // All 180deg WM rotation animations are CCW, match that
51         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
52         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
53         if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
54         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
55         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
56         if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
57         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
58         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
59         if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
60         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
61         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
62         if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
63         return false; // Default
64     }
65 
66     /**
67      * Compares the ratio of two quantities and returns whether that ratio is greater than the
68      * provided bound. Order of quantities does not matter. Bound should be a decimal representation
69      * of a percentage.
70      */
isRelativePercentDifferenceGreaterThan(float first, float second, float bound)71     public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
72             float bound) {
73         return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
74     }
75 
76     /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
computeContrastBetweenColors(int bg, int fg)77     public static float computeContrastBetweenColors(int bg, int fg) {
78         float bgR = Color.red(bg) / 255f;
79         float bgG = Color.green(bg) / 255f;
80         float bgB = Color.blue(bg) / 255f;
81         bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
82         bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
83         bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
84         float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
85 
86         float fgR = Color.red(fg) / 255f;
87         float fgG = Color.green(fg) / 255f;
88         float fgB = Color.blue(fg) / 255f;
89         fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
90         fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
91         fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
92         float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
93 
94         return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
95     }
96 
97     /**
98      * @return the clamped {@param value} between the provided {@param min} and {@param max}.
99      */
clamp(float value, float min, float max)100     public static float clamp(float value, float min, float max) {
101         return Math.max(min, Math.min(max, value));
102     }
103 
104     /**
105      * @return updated set of flags from InputMethodService based off {@param oldHints}
106      *          Leaves original hints unmodified
107      */
calculateBackDispositionHints(int oldHints, int backDisposition, boolean imeShown, boolean showImeSwitcher)108     public static int calculateBackDispositionHints(int oldHints, int backDisposition,
109             boolean imeShown, boolean showImeSwitcher) {
110         int hints = oldHints;
111         switch (backDisposition) {
112             case InputMethodService.BACK_DISPOSITION_DEFAULT:
113             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
114             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
115                 if (imeShown) {
116                     hints |= NAVIGATION_HINT_BACK_ALT;
117                 } else {
118                     hints &= ~NAVIGATION_HINT_BACK_ALT;
119                 }
120                 break;
121             case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
122                 hints &= ~NAVIGATION_HINT_BACK_ALT;
123                 break;
124         }
125         if (imeShown) {
126             hints |= NAVIGATION_HINT_IME_SHOWN;
127         } else {
128             hints &= ~NAVIGATION_HINT_IME_SHOWN;
129         }
130         if (showImeSwitcher) {
131             hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN;
132         } else {
133             hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN;
134         }
135 
136         return hints;
137     }
138 
139     /** @return whether or not {@param context} represents that of a large screen device or not */
140     @TargetApi(Build.VERSION_CODES.R)
isLargeScreen(Context context)141     public static boolean isLargeScreen(Context context) {
142         return isLargeScreen(context.getSystemService(WindowManager.class), context.getResources());
143     }
144 
145     /** @return whether or not {@param context} represents that of a large screen device or not */
isLargeScreen(WindowManager windowManager, Resources resources)146     public static boolean isLargeScreen(WindowManager windowManager, Resources resources) {
147         final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
148 
149         float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
150                 resources.getConfiguration().densityDpi);
151         return smallestWidth >= TABLET_MIN_DPS;
152     }
153 
dpiFromPx(float size, int densityDpi)154     public static float dpiFromPx(float size, int densityDpi) {
155         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
156         return (size / densityRatio);
157     }
158 }
159