1 /*
2  * Copyright (C) 2008 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 package android.graphics.cts;
17 
18 import static androidx.test.InstrumentationRegistry.getInstrumentation;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertTrue;
22 
23 import android.content.res.Resources;
24 import android.graphics.Color;
25 import android.platform.test.annotations.DisabledOnRavenwood;
26 import android.platform.test.ravenwood.RavenwoodRule;
27 import android.util.ArraySet;
28 import android.util.Log;
29 import android.util.TypedValue;
30 
31 import androidx.test.filters.SmallTest;
32 import androidx.test.runner.AndroidJUnit4;
33 
34 import org.junit.Rule;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 
38 import java.lang.annotation.Annotation;
39 import java.lang.reflect.Field;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.List;
43 import java.util.Set;
44 
45 @SmallTest
46 @RunWith(AndroidJUnit4.class)
47 public class ColorTest {
48 
49     private static final String LOG_TAG = ColorTest.class.getSimpleName();
50 
51     @Rule
52     public final RavenwoodRule mRavenwood = new RavenwoodRule();
53 
54     @Test
55     @DisabledOnRavenwood(blockedBy = Resources.class)
resourceColor()56     public void resourceColor() {
57         int[][] colors = {
58                 { 0xff000000, android.R.color.background_dark  },
59                 { 0xffffffff, android.R.color.background_light },
60                 { 0xff000000, android.R.color.black },
61                 { 0xffaaaaaa, android.R.color.darker_gray },
62                 { 0xff00ddff, android.R.color.holo_blue_bright },
63                 { 0xff0099cc, android.R.color.holo_blue_dark },
64                 { 0xff33b5e5, android.R.color.holo_blue_light },
65                 { 0xff669900, android.R.color.holo_green_dark },
66                 { 0xff99cc00, android.R.color.holo_green_light },
67                 { 0xffff8800, android.R.color.holo_orange_dark },
68                 { 0xffffbb33, android.R.color.holo_orange_light },
69                 { 0xffaa66cc, android.R.color.holo_purple },
70                 { 0xffcc0000, android.R.color.holo_red_dark },
71                 { 0xffff4444, android.R.color.holo_red_light },
72                 { 0xffffffff, android.R.color.primary_text_dark },
73                 { 0xffffffff, android.R.color.primary_text_dark_nodisable },
74                 { 0xff000000, android.R.color.primary_text_light },
75                 { 0xff000000, android.R.color.primary_text_light_nodisable },
76                 { 0xffbebebe, android.R.color.secondary_text_dark },
77                 { 0xffbebebe, android.R.color.secondary_text_dark_nodisable },
78                 { 0xff323232, android.R.color.secondary_text_light },
79                 { 0xffbebebe, android.R.color.secondary_text_light_nodisable },
80                 { 0xff808080, android.R.color.tab_indicator_text },
81                 { 0xff808080, android.R.color.tertiary_text_dark },
82                 { 0xff808080, android.R.color.tertiary_text_light },
83                 { 0x00000000, android.R.color.transparent },
84                 { 0xffffffff, android.R.color.white },
85                 { 0xff000000, android.R.color.widget_edittext_dark },
86         };
87 
88         int[] systemColors = {
89                 android.R.color.system_neutral1_0,
90                 android.R.color.system_neutral1_10,
91                 android.R.color.system_neutral1_50,
92                 android.R.color.system_neutral1_100,
93                 android.R.color.system_neutral1_200,
94                 android.R.color.system_neutral1_300,
95                 android.R.color.system_neutral1_400,
96                 android.R.color.system_neutral1_500,
97                 android.R.color.system_neutral1_600,
98                 android.R.color.system_neutral1_700,
99                 android.R.color.system_neutral1_800,
100                 android.R.color.system_neutral1_900,
101                 android.R.color.system_neutral1_1000,
102                 android.R.color.system_neutral2_0,
103                 android.R.color.system_neutral2_10,
104                 android.R.color.system_neutral2_50,
105                 android.R.color.system_neutral2_100,
106                 android.R.color.system_neutral2_200,
107                 android.R.color.system_neutral2_300,
108                 android.R.color.system_neutral2_400,
109                 android.R.color.system_neutral2_500,
110                 android.R.color.system_neutral2_600,
111                 android.R.color.system_neutral2_700,
112                 android.R.color.system_neutral2_800,
113                 android.R.color.system_neutral2_900,
114                 android.R.color.system_neutral2_1000,
115                 android.R.color.system_accent1_0,
116                 android.R.color.system_accent1_10,
117                 android.R.color.system_accent1_50,
118                 android.R.color.system_accent1_100,
119                 android.R.color.system_accent1_200,
120                 android.R.color.system_accent1_300,
121                 android.R.color.system_accent1_400,
122                 android.R.color.system_accent1_500,
123                 android.R.color.system_accent1_600,
124                 android.R.color.system_accent1_700,
125                 android.R.color.system_accent1_800,
126                 android.R.color.system_accent1_900,
127                 android.R.color.system_accent1_1000,
128                 android.R.color.system_accent2_0,
129                 android.R.color.system_accent2_10,
130                 android.R.color.system_accent2_50,
131                 android.R.color.system_accent2_100,
132                 android.R.color.system_accent2_200,
133                 android.R.color.system_accent2_300,
134                 android.R.color.system_accent2_400,
135                 android.R.color.system_accent2_500,
136                 android.R.color.system_accent2_600,
137                 android.R.color.system_accent2_700,
138                 android.R.color.system_accent2_800,
139                 android.R.color.system_accent2_900,
140                 android.R.color.system_accent2_1000,
141                 android.R.color.system_accent3_0,
142                 android.R.color.system_accent3_10,
143                 android.R.color.system_accent3_50,
144                 android.R.color.system_accent3_100,
145                 android.R.color.system_accent3_200,
146                 android.R.color.system_accent3_300,
147                 android.R.color.system_accent3_400,
148                 android.R.color.system_accent3_500,
149                 android.R.color.system_accent3_600,
150                 android.R.color.system_accent3_700,
151                 android.R.color.system_accent3_800,
152                 android.R.color.system_accent3_900,
153                 android.R.color.system_accent3_1000,
154                 android.R.color.system_error_0,
155                 android.R.color.system_error_10,
156                 android.R.color.system_error_50,
157                 android.R.color.system_error_100,
158                 android.R.color.system_error_200,
159                 android.R.color.system_error_300,
160                 android.R.color.system_error_400,
161                 android.R.color.system_error_500,
162                 android.R.color.system_error_600,
163                 android.R.color.system_error_700,
164                 android.R.color.system_error_800,
165                 android.R.color.system_error_900,
166                 android.R.color.system_error_1000,
167         };
168 
169         int[] materialSystemColors = {
170                 android.R.color.system_primary_container_light,
171                 android.R.color.system_on_primary_container_light,
172                 android.R.color.system_primary_light,
173                 android.R.color.system_on_primary_light,
174                 android.R.color.system_secondary_container_light,
175                 android.R.color.system_on_secondary_container_light,
176                 android.R.color.system_secondary_light,
177                 android.R.color.system_on_secondary_light,
178                 android.R.color.system_tertiary_container_light,
179                 android.R.color.system_on_tertiary_container_light,
180                 android.R.color.system_tertiary_light,
181                 android.R.color.system_on_tertiary_light,
182                 android.R.color.system_background_light,
183                 android.R.color.system_on_background_light,
184                 android.R.color.system_surface_light,
185                 android.R.color.system_on_surface_light,
186                 android.R.color.system_surface_container_low_light,
187                 android.R.color.system_surface_container_lowest_light,
188                 android.R.color.system_surface_container_light,
189                 android.R.color.system_surface_container_high_light,
190                 android.R.color.system_surface_container_highest_light,
191                 android.R.color.system_surface_bright_light,
192                 android.R.color.system_surface_dim_light,
193                 android.R.color.system_surface_variant_light,
194                 android.R.color.system_on_surface_variant_light,
195                 android.R.color.system_outline_light,
196                 android.R.color.system_outline_variant_light,
197                 android.R.color.system_error_light,
198                 android.R.color.system_on_error_light,
199                 android.R.color.system_error_container_light,
200                 android.R.color.system_on_error_container_light,
201                 android.R.color.system_primary_fixed,
202                 android.R.color.system_primary_fixed_dim,
203                 android.R.color.system_on_primary_fixed,
204                 android.R.color.system_on_primary_fixed_variant,
205                 android.R.color.system_secondary_fixed,
206                 android.R.color.system_secondary_fixed_dim,
207                 android.R.color.system_on_secondary_fixed,
208                 android.R.color.system_on_secondary_fixed_variant,
209                 android.R.color.system_tertiary_fixed,
210                 android.R.color.system_tertiary_fixed_dim,
211                 android.R.color.system_on_tertiary_fixed,
212                 android.R.color.system_on_tertiary_fixed_variant,
213                 android.R.color.system_control_activated_light,
214                 android.R.color.system_control_normal_light,
215                 android.R.color.system_control_highlight_light,
216                 android.R.color.system_text_primary_inverse_light,
217                 android.R.color.system_text_secondary_and_tertiary_inverse_light,
218                 android.R.color.system_text_primary_inverse_disable_only_light,
219                 android.R.color.system_text_secondary_and_tertiary_inverse_disabled_light,
220                 android.R.color.system_text_hint_inverse_light,
221                 android.R.color.system_palette_key_color_primary_light,
222                 android.R.color.system_palette_key_color_secondary_light,
223                 android.R.color.system_palette_key_color_tertiary_light,
224                 android.R.color.system_palette_key_color_neutral_light,
225                 android.R.color.system_palette_key_color_neutral_variant_light,
226                 android.R.color.system_primary_container_dark,
227                 android.R.color.system_on_primary_container_dark,
228                 android.R.color.system_primary_dark,
229                 android.R.color.system_on_primary_dark,
230                 android.R.color.system_secondary_container_dark,
231                 android.R.color.system_on_secondary_container_dark,
232                 android.R.color.system_secondary_dark,
233                 android.R.color.system_on_secondary_dark,
234                 android.R.color.system_tertiary_container_dark,
235                 android.R.color.system_on_tertiary_container_dark,
236                 android.R.color.system_tertiary_dark,
237                 android.R.color.system_on_tertiary_dark,
238                 android.R.color.system_background_dark,
239                 android.R.color.system_on_background_dark,
240                 android.R.color.system_surface_dark,
241                 android.R.color.system_on_surface_dark,
242                 android.R.color.system_surface_container_low_dark,
243                 android.R.color.system_surface_container_lowest_dark,
244                 android.R.color.system_surface_container_dark,
245                 android.R.color.system_surface_container_high_dark,
246                 android.R.color.system_surface_container_highest_dark,
247                 android.R.color.system_surface_bright_dark,
248                 android.R.color.system_surface_dim_dark,
249                 android.R.color.system_surface_variant_dark,
250                 android.R.color.system_on_surface_variant_dark,
251                 android.R.color.system_outline_dark,
252                 android.R.color.system_outline_variant_dark,
253                 android.R.color.system_error_dark,
254                 android.R.color.system_on_error_dark,
255                 android.R.color.system_error_container_dark,
256                 android.R.color.system_on_error_container_dark,
257                 android.R.color.system_control_activated_dark,
258                 android.R.color.system_control_normal_dark,
259                 android.R.color.system_control_highlight_dark,
260                 android.R.color.system_text_primary_inverse_dark,
261                 android.R.color.system_text_secondary_and_tertiary_inverse_dark,
262                 android.R.color.system_text_primary_inverse_disable_only_dark,
263                 android.R.color.system_text_secondary_and_tertiary_inverse_disabled_dark,
264                 android.R.color.system_text_hint_inverse_dark,
265                 android.R.color.system_palette_key_color_primary_dark,
266                 android.R.color.system_palette_key_color_secondary_dark,
267                 android.R.color.system_palette_key_color_tertiary_dark,
268                 android.R.color.system_palette_key_color_neutral_dark,
269                 android.R.color.system_palette_key_color_neutral_variant_dark,
270                 android.R.color.system_surface_disabled,
271                 android.R.color.system_on_surface_disabled,
272                 android.R.color.system_outline_disabled,
273         };
274 
275         List<Integer> expectedColorStateLists = Arrays.asList(
276                 android.R.color.primary_text_dark,
277                 android.R.color.primary_text_dark_nodisable,
278                 android.R.color.primary_text_light,
279                 android.R.color.primary_text_light_nodisable,
280                 android.R.color.secondary_text_dark,
281                 android.R.color.secondary_text_dark_nodisable,
282                 android.R.color.secondary_text_light,
283                 android.R.color.secondary_text_light_nodisable,
284                 android.R.color.tab_indicator_text,
285                 android.R.color.tertiary_text_dark,
286                 android.R.color.tertiary_text_light,
287                 android.R.color.widget_edittext_dark
288         );
289 
290         Resources resources = getInstrumentation().getTargetContext().getResources();
291         for (int[] pair : colors) {
292             final int resourceId = pair[1];
293             final int expectedColor = pair[0];
294 
295             // validate color from getColor
296             int observedColor = resources.getColor(resourceId, null);
297             assertEquals("Color = " + Integer.toHexString(observedColor) + ", "
298                             + Integer.toHexString(expectedColor) + " expected",
299                     expectedColor,
300                     observedColor);
301 
302             // validate color from getValue
303             TypedValue value = new TypedValue();
304             resources.getValue(resourceId, value, true);
305 
306             // colors shouldn't depend on config changes
307             assertEquals(0, value.changingConfigurations);
308 
309             if (expectedColorStateLists.contains(resourceId)) {
310                 // ColorStateLists are strings
311                 assertEquals("CSLs should be strings", TypedValue.TYPE_STRING, value.type);
312             } else {
313                 // colors should be raw ints
314                 assertTrue("Type should be int",
315                         value.type >= TypedValue.TYPE_FIRST_INT
316                         && value.type <= TypedValue.TYPE_LAST_INT);
317 
318                 // Validate color from getValue
319                 assertEquals("Color should be expected value", expectedColor, value.data);
320             }
321         }
322 
323         // System-API colors are used to allow updateable platform components to use the same colors
324         // as the system. The actual value of the color does not matter. Hence only enforce that
325         // 'colors' contains all the public colors and ignore System-api colors.
326         ArrayList<String> missingColors = new ArrayList<>();
327         ArrayList<Integer> requiredPublicApiColorResIds = new ArrayList<>();
328         requiredPublicApiColorResIds.addAll(Arrays.stream(colors).map(pair -> pair[1]).toList());
329         requiredPublicApiColorResIds.addAll(Arrays.stream(systemColors).boxed().toList());
330         requiredPublicApiColorResIds.addAll(Arrays.stream(materialSystemColors).boxed().toList());
331 
332         Set<Integer> availablePublicApiColorResIds = new ArraySet<>();
333         for (Field declaredColor : android.R.color.class.getDeclaredFields()) {
334             if (Arrays.stream(declaredColor.getDeclaredAnnotations()).anyMatch(
335                     (Annotation a) -> a.toString().contains("SystemApi"))) {
336                 Log.i(LOG_TAG, declaredColor.getName() + " is SystemApi");
337                 continue;
338             }
339             Integer colorValue = -1;
340             try {
341                 colorValue = (Integer) declaredColor.get(null);
342             } catch (IllegalAccessException ignored) { }
343             if (colorValue != -1) {
344                 availablePublicApiColorResIds.add(colorValue);
345             }
346         }
347 
348         for (Integer requiredColorResId : requiredPublicApiColorResIds) {
349             if (!availablePublicApiColorResIds.contains(requiredColorResId)) {
350                 missingColors.add(resources.getResourceName(requiredColorResId));
351             }
352         }
353 
354         assertTrue(
355                 "android.R.color misses required color resources. Declared in list,but not "
356                         + "installed : "
357                         + missingColors, missingColors.isEmpty());
358     }
359     @Test
testAlpha()360     public void testAlpha() {
361         assertEquals(0xff, Color.alpha(Color.RED));
362         assertEquals(0xff, Color.alpha(Color.YELLOW));
363     }
364 
365     @Test
testArgb()366     public void testArgb() {
367         assertEquals(Color.RED, Color.argb(0xff, 0xff, 0x00, 0x00));
368         assertEquals(Color.YELLOW, Color.argb(0xff, 0xff, 0xff, 0x00));
369         assertEquals(Color.RED, Color.argb(1.0f, 1.0f, 0.0f, 0.0f));
370         assertEquals(Color.YELLOW, Color.argb(1.0f, 1.0f, 1.0f, 0.0f));
371     }
372 
373     @Test
testBlue()374     public void testBlue() {
375         assertEquals(0x00, Color.blue(Color.RED));
376         assertEquals(0x00, Color.blue(Color.YELLOW));
377     }
378 
379     @Test
testGreen()380     public void testGreen() {
381         assertEquals(0x00, Color.green(Color.RED));
382         assertEquals(0xff, Color.green(Color.GREEN));
383     }
384 
385     @Test(expected = RuntimeException.class)
testHSVToColorArrayTooShort()386     public void testHSVToColorArrayTooShort() {
387         // abnormal case: hsv length less than 3
388         float[] hsv = new float[2];
389         Color.HSVToColor(hsv);
390     }
391 
392     @Test
testHSVToColor()393     public void testHSVToColor() {
394         float[] hsv = new float[3];
395         Color.colorToHSV(Color.RED, hsv);
396         assertEquals(Color.RED, Color.HSVToColor(hsv));
397     }
398 
399     @Test
testHSVToColorWithAlpha()400     public void testHSVToColorWithAlpha() {
401         float[] hsv = new float[3];
402         Color.colorToHSV(Color.RED, hsv);
403         assertEquals(Color.RED, Color.HSVToColor(0xff, hsv));
404     }
405 
406     @Test(expected = IllegalArgumentException.class)
testParseColorStringOfInvalidLength()407     public void testParseColorStringOfInvalidLength() {
408         // abnormal case: colorString starts with '#' but length is neither 7 nor 9
409         Color.parseColor("#ff00ff0");
410     }
411 
412     @Test
testParseColor()413     public void testParseColor() {
414         assertEquals(Color.RED, Color.parseColor("#ff0000"));
415         assertEquals(Color.RED, Color.parseColor("#ffff0000"));
416 
417         assertEquals(Color.BLACK, Color.parseColor("black"));
418         assertEquals(Color.DKGRAY, Color.parseColor("darkgray"));
419         assertEquals(Color.GRAY, Color.parseColor("gray"));
420         assertEquals(Color.LTGRAY, Color.parseColor("lightgray"));
421         assertEquals(Color.WHITE, Color.parseColor("white"));
422         assertEquals(Color.RED, Color.parseColor("red"));
423         assertEquals(Color.GREEN, Color.parseColor("green"));
424         assertEquals(Color.BLUE, Color.parseColor("blue"));
425         assertEquals(Color.YELLOW, Color.parseColor("yellow"));
426         assertEquals(Color.CYAN, Color.parseColor("cyan"));
427         assertEquals(Color.MAGENTA, Color.parseColor("magenta"));
428     }
429 
430     @Test(expected = IllegalArgumentException.class)
testParseColorUnsupportedFormat()431     public void testParseColorUnsupportedFormat() {
432         // abnormal case: colorString doesn't start with '#' and is unknown color
433         Color.parseColor("hello");
434     }
435 
436     @Test
testRed()437     public void testRed() {
438         assertEquals(0xff, Color.red(Color.RED));
439         assertEquals(0xff, Color.red(Color.YELLOW));
440     }
441 
442     @Test
testRgb()443     public void testRgb() {
444         assertEquals(Color.RED, Color.rgb(0xff, 0x00, 0x00));
445         assertEquals(Color.YELLOW, Color.rgb(0xff, 0xff, 0x00));
446         assertEquals(Color.RED, Color.rgb(1.0f, 0.0f, 0.0f));
447         assertEquals(Color.YELLOW, Color.rgb(1.0f, 1.0f, 0.0f));
448     }
449 
450     @Test(expected = RuntimeException.class)
testRGBToHSVArrayTooShort()451     public void testRGBToHSVArrayTooShort() {
452         // abnormal case: hsv length less than 3
453         float[] hsv = new float[2];
454         Color.RGBToHSV(0xff, 0x00, 0x00, hsv);
455     }
456 
457     @Test
testRGBToHSV()458     public void testRGBToHSV() {
459         float[] hsv = new float[3];
460         Color.RGBToHSV(0xff, 0x00, 0x00, hsv);
461         assertEquals(Color.RED, Color.HSVToColor(hsv));
462     }
463 
464     @Test
testLuminance()465     public void testLuminance() {
466         assertEquals(0, Color.luminance(Color.BLACK), 0);
467         float eps = 0.000001f;
468         assertEquals(0.0722, Color.luminance(Color.BLUE), eps);
469         assertEquals(0.2126, Color.luminance(Color.RED), eps);
470         assertEquals(0.7152, Color.luminance(Color.GREEN), eps);
471         assertEquals(1, Color.luminance(Color.WHITE), 0);
472     }
473 }
474