1 /*
2  * Copyright (C) 2021 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.system.helpers;
18 
19 import static android.app.WallpaperManager.FLAG_SYSTEM;
20 
21 import android.app.WallpaperManager;
22 import android.content.Context;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapRegionDecoder;
25 import android.graphics.Color;
26 import android.graphics.Rect;
27 import android.text.TextUtils;
28 
29 import androidx.annotation.ColorInt;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 import androidx.test.uiautomator.UiDevice;
33 
34 import java.io.ByteArrayInputStream;
35 import java.io.ByteArrayOutputStream;
36 import java.io.File;
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Comparator;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Objects;
44 
45 /** A common helper class for theme scenario tests. */
46 public class ThemeHelper {
47 
48     private static final float COLOR_TOLERANCE = 0.01F;
49 
50     @NonNull private final UiDevice mUiDevice;
51     @NonNull private final Context mContext;
52     @NonNull private final WallpaperManager mWallpaperManager;
53 
ThemeHelper(@onNull UiDevice uiDevice, @NonNull Context context)54     public ThemeHelper(@NonNull UiDevice uiDevice, @NonNull Context context) {
55         mUiDevice = uiDevice;
56         mContext = context;
57         mWallpaperManager =
58                 Objects.requireNonNull(mContext.getSystemService(WallpaperManager.class));
59     }
60 
61     /**
62      * Sets wallpaper
63      *
64      * @param color the color for wallpaper
65      * @throws IOException exception during setWallpaper
66      */
setWallpaper(@olorInt int color)67     public void setWallpaper(@ColorInt int color) throws IOException {
68         final Bitmap bitmap =
69                 Bitmap.createBitmap(
70                         mUiDevice.getDisplayWidth(),
71                         mUiDevice.getDisplayHeight(),
72                         Bitmap.Config.ARGB_8888);
73         bitmap.eraseColor(color);
74 
75         final byte[] byteArray;
76         try {
77             final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
78             final boolean compressResult =
79                     bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
80             if (!compressResult) {
81                 throw new IOException("Fail to compress bitmap");
82             }
83             byteArray = outStream.toByteArray();
84             mWallpaperManager.setStream(
85                     new ByteArrayInputStream(byteArray), null, false, FLAG_SYSTEM);
86         } finally {
87             bitmap.recycle();
88         }
89     }
90 
91     /**
92      * Gets primary color from WallpaperColors
93      *
94      * @return primary color
95      */
96     @ColorInt
getWallpaperPrimaryColor()97     public int getWallpaperPrimaryColor() {
98         return mWallpaperManager.getWallpaperColors(FLAG_SYSTEM).getPrimaryColor().toArgb();
99     }
100 
deleteFileIfExist(@ullable String fileAbsPath)101     private void deleteFileIfExist(@Nullable String fileAbsPath) {
102         if (TextUtils.isEmpty(fileAbsPath)) {
103             return;
104         }
105         new File(fileAbsPath).deleteOnExit();
106     }
107 
108     /**
109      * Gets highest count of color inside this bitmap
110      *
111      * @param bitmap the bitmap for analysis
112      * @return most rendered color
113      */
114     @NonNull
getMostColor(@onNull Bitmap bitmap)115     public Color getMostColor(@NonNull Bitmap bitmap) {
116         HashMap<Integer, Integer> colors = new HashMap<>();
117         for (int x = 0; x < bitmap.getWidth(); ++x) {
118             for (int y = 0; y < bitmap.getHeight(); ++y) {
119                 @ColorInt int color = bitmap.getColor(x, y).toArgb();
120                 colors.put(color, colors.containsKey(color) ? colors.get(color) + 1 : 1);
121             }
122         }
123         List<Map.Entry<Integer, Integer>> colorList = new ArrayList<>(colors.entrySet());
124         final Map.Entry<Integer, Integer> mostColorCountEntry =
125                 colorList.stream().max(Comparator.comparingInt(Map.Entry::getValue)).get();
126         return Color.valueOf(mostColorCountEntry.getKey());
127     }
128 
takeScreenshotToFile(@onNull String filename)129     private String takeScreenshotToFile(@NonNull String filename) {
130         File f = new File(mContext.getFilesDir(), filename);
131         mUiDevice.takeScreenshot(f);
132         if (f.exists()) {
133             return f.getAbsolutePath();
134         }
135         return null;
136     }
137 
138     /**
139      * Takes a screenshot and calculates in the specific rect
140      *
141      * @param rect the rect for calculating the most rendered color
142      * @return most rendered color
143      * @throws IOException exception if taking screenshot fails
144      */
getScreenshotMostColorAsRect(@onNull Rect rect)145     public Color getScreenshotMostColorAsRect(@NonNull Rect rect) throws IOException {
146         String fileAbsPath = null;
147         Bitmap bitmap = null;
148         BitmapRegionDecoder bitmapRegionDecoder = null;
149         try {
150             fileAbsPath = takeScreenshotToFile("test1");
151             bitmapRegionDecoder = BitmapRegionDecoder.newInstance(fileAbsPath);
152             bitmap = bitmapRegionDecoder.decodeRegion(rect, null);
153             return getMostColor(bitmap);
154         } finally {
155             if (bitmap != null) {
156                 bitmap.recycle();
157             }
158             if (bitmapRegionDecoder != null) {
159                 bitmapRegionDecoder.recycle();
160             }
161             deleteFileIfExist(fileAbsPath);
162         }
163     }
164 
165     /**
166      * Gets current activated Quick Setting background color.
167      *
168      * @return color The theme color for activated quick setting tile background
169      */
170     @NonNull
getSysuiActivatedThemeColor()171     public Color getSysuiActivatedThemeColor() {
172         return Color.valueOf(mContext.getColor(android.R.color.system_accent1_100));
173     }
174 
175     /**
176      * Validate the colors in between are similar or not.
177      *
178      * @param color1 the first color
179      * @param color2 the second color
180      * @return true if the colors in between are similar, false otherwise
181      */
isSimilarColor(@onNull Color color1, @NonNull Color color2)182     public boolean isSimilarColor(@NonNull Color color1, @NonNull Color color2) {
183         return color1.alpha() == color2.alpha()
184                 && Math.abs(color1.red() - color2.red()) < COLOR_TOLERANCE
185                 && Math.abs(color1.green() - color2.green()) < COLOR_TOLERANCE
186                 && Math.abs(color1.blue() - color2.blue()) < COLOR_TOLERANCE;
187     }
188 }
189