1 /*
2  * Copyright (C) 2017 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 android.view.inputmethod.cts.util;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.graphics.Bitmap;
23 import android.graphics.Color;
24 
25 import androidx.annotation.ColorInt;
26 import androidx.annotation.NonNull;
27 
28 import java.util.ArrayList;
29 
30 /**
31  * A utility class to evaluate if {@link android.view.Window#setNavigationBarColor(int)} is
32  * supported on the device or not.
33  */
34 public class NavigationBarColorVerifier {
35 
36     private static final class ScreenShot {
37         @ColorInt
38         public final int mBackgroundColor;
39         @NonNull
40         public final int[] mPixels;
41 
ScreenShot(@olorInt int backgroundColor, @NonNull Bitmap bitmap)42         ScreenShot(@ColorInt int backgroundColor, @NonNull Bitmap bitmap) {
43             mBackgroundColor = backgroundColor;
44             final int width = bitmap.getWidth();
45             final int height = bitmap.getHeight();
46             mPixels = new int[width * height];
47             bitmap.getPixels(mPixels, 0 /* offset */, width /* stride */, 0 /* x */, 0 /* y */,
48                     width, height);
49         }
50     }
51 
52     public enum ResultType {
53         /**
54          * {@link android.view.Window#setNavigationBarColor(int)} seems to be not supported.
55          */
56         NOT_SUPPORTED,
57         /**
58          * {@link android.view.Window#setNavigationBarColor(int)} seems to be supported.
59          */
60         SUPPORTED,
61     }
62 
63     static final class Result {
64         @NonNull
65         public final ResultType mResult;
66         @NonNull
67         public final String mAssertionMessage;
68 
Result(@onNull ResultType result, @NonNull String assertionMessage)69         Result(@NonNull ResultType result, @NonNull String assertionMessage) {
70             mResult = result;
71             mAssertionMessage = assertionMessage;
72         }
73 
74         @NonNull
getResult()75         public ResultType getResult() {
76             return mResult;
77         }
78 
79         @NonNull
getAssertionMessage()80         public String getAssertionMessage() {
81             return mAssertionMessage;
82         }
83     }
84 
85     @FunctionalInterface
86     public interface ScreenshotSupplier {
87         @NonNull
takeScreenshot(@olorInt int navigationBarColor)88         Bitmap takeScreenshot(@ColorInt int navigationBarColor) throws Exception;
89     }
90 
91     @NonNull
verify(@onNull ScreenshotSupplier screenshotSupplier)92     static Result verify(@NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
93         final ArrayList<ScreenShot> screenShots = new ArrayList<>();
94         final int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE};
95         for (int color : colors) {
96             screenShots.add(new ScreenShot(color, screenshotSupplier.takeScreenshot(color)));
97         }
98         return verifyInternal(screenShots);
99     }
100 
101     /**
102      * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is supported on this
103      * device.
104      *
105      * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
106      */
expectNavigationBarColorSupported( @onNull ScreenshotSupplier screenshotSupplier)107     public static void expectNavigationBarColorSupported(
108             @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
109         final Result result = verify(screenshotSupplier);
110         assertEquals(result.getAssertionMessage(), ResultType.SUPPORTED, result.getResult());
111     }
112 
113     /**
114      * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is not supported on this
115      * device.
116      *
117      * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
118      */
expectNavigationBarColorNotSupported( @onNull ScreenshotSupplier screenshotSupplier)119     public static void expectNavigationBarColorNotSupported(
120             @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
121         final Result result = verify(screenshotSupplier);
122         assertEquals(result.getAssertionMessage(), ResultType.NOT_SUPPORTED, result.getResult());
123     }
124 
verifyInternal(@onNull ArrayList<ScreenShot> screenShots)125     private static Result verifyInternal(@NonNull ArrayList<ScreenShot> screenShots) {
126         final int numScreenShots = screenShots.size();
127         assertTrue(
128                 "This algorithm requires at least 3 screen shots. size=" + numScreenShots,
129                 numScreenShots >= 3);
130         assertEquals("All screenshots must have different background colors",
131                 numScreenShots,
132                 screenShots.stream()
133                         .mapToInt(screenShot -> screenShot.mBackgroundColor)
134                         .distinct()
135                         .count());
136         assertEquals("All screenshots must have the same pixel count",
137                 1,
138                 screenShots.stream()
139                         .mapToInt(screenShot -> screenShot.mPixels.length)
140                         .distinct()
141                         .count());
142 
143         long numCompletelyFixedColorPixels = 0;
144         long numColorChangedAsRequestedPixels = 0;
145         final int numPixels = screenShots.get(0).mPixels.length;
146         for (int pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) {
147             final int i = pixelIndex;
148             final long numFoundColors = screenShots.stream()
149                     .mapToInt(screenShot -> screenShot.mPixels[i])
150                     .distinct()
151                     .count();
152             if (numFoundColors == 1) {
153                 numCompletelyFixedColorPixels++;
154             }
155             final long matchingScore =  screenShots.stream()
156                     .filter(screenShot -> screenShot.mPixels[i] == screenShot.mBackgroundColor)
157                     .count();
158             if (matchingScore == numScreenShots) {
159                 numColorChangedAsRequestedPixels++;
160             }
161         }
162         final String assertionMessage = "numPixels=" + numPixels
163                 + " numColorChangedAsRequestedPixels=" + numColorChangedAsRequestedPixels
164                 + " numCompletelyFixedColorPixels=" + numCompletelyFixedColorPixels;
165 
166         // OK, even 1 pixel is enough.
167         if (numColorChangedAsRequestedPixels > 0) {
168             return new Result(ResultType.SUPPORTED, assertionMessage);
169         }
170 
171         return new Result(ResultType.NOT_SUPPORTED, assertionMessage);
172     }
173 }
174