1 /*
2  * Copyright (C) 2014 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.theme.cts;
18 
19 import com.android.cts.tradefed.build.CtsBuildHelper;
20 import com.android.cts.util.AbiUtils;
21 import com.android.cts.util.TimeoutReq;
22 import com.android.ddmlib.Log;
23 import com.android.ddmlib.Log.LogLevel;
24 import com.android.tradefed.build.IBuildInfo;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.testtype.DeviceTestCase;
28 import com.android.tradefed.testtype.IAbi;
29 import com.android.tradefed.testtype.IAbiReceiver;
30 import com.android.tradefed.testtype.IBuildReceiver;
31 
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.lang.String;
38 import java.util.HashMap;
39 import java.util.Scanner;
40 import java.util.concurrent.Executors;
41 import java.util.concurrent.ExecutorCompletionService;
42 import java.util.concurrent.ExecutorService;
43 import java.util.zip.ZipEntry;
44 import java.util.zip.ZipInputStream;
45 
46 /**
47  * Test to check non-modifiable themes have not been changed.
48  */
49 public class ThemeHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
50     private static final String LOG_TAG = "ThemeHostTest";
51     private static final String APK_NAME = "CtsThemeDeviceApp";
52     private static final String APP_PACKAGE_NAME = "android.theme.app";
53 
54     private static final String GENERATED_ASSETS_ZIP = "/sdcard/cts-theme-assets.zip";
55 
56     /** The class name of the main activity in the APK. */
57     private static final String CLASS = "GenerateImagesActivity";
58 
59     /** The command to launch the main activity. */
60     private static final String START_CMD = String.format(
61             "am start -W -a android.intent.action.MAIN -n %s/%s.%s", APP_PACKAGE_NAME,
62             APP_PACKAGE_NAME, CLASS);
63 
64     private static final String CLEAR_GENERATED_CMD = "rm -rf %s/*.png";
65     private static final String STOP_CMD = String.format("am force-stop %s", APP_PACKAGE_NAME);
66     private static final String HARDWARE_TYPE_CMD = "dumpsys | grep android.hardware.type";
67     private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
68     private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
69 
70     private final HashMap<String, File> mReferences = new HashMap<>();
71 
72     /** The ABI to use. */
73     private IAbi mAbi;
74 
75     /** A reference to the build. */
76     private CtsBuildHelper mBuild;
77 
78     /** A reference to the device under test. */
79     private ITestDevice mDevice;
80 
81     private ExecutorService mExecutionService;
82 
83     private ExecutorCompletionService<Boolean> mCompletionService;
84 
85     @Override
setAbi(IAbi abi)86     public void setAbi(IAbi abi) {
87         mAbi = abi;
88     }
89 
90     @Override
setBuild(IBuildInfo buildInfo)91     public void setBuild(IBuildInfo buildInfo) {
92         // Get the build, this is used to access the APK.
93         mBuild = CtsBuildHelper.createBuildHelper(buildInfo);
94     }
95 
96     @Override
setUp()97     protected void setUp() throws Exception {
98         super.setUp();
99 
100         mDevice = getDevice();
101         mDevice.uninstallPackage(APP_PACKAGE_NAME);
102 
103         // Get the APK from the build.
104         final File app = mBuild.getTestApp(String.format("%s.apk", APK_NAME));
105         final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
106 
107         mDevice.installPackage(app, false, options);
108 
109         final String density = getDensityBucketForDevice(mDevice);
110         final String zipFile = String.format("/%s.zip", density);
111         Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Loading resources from " + zipFile);
112 
113         final InputStream zipStream = ThemeHostTest.class.getResourceAsStream(zipFile);
114         if (zipStream != null) {
115             final ZipInputStream in = new ZipInputStream(zipStream);
116             try {
117                 ZipEntry ze;
118                 final byte[] buffer = new byte[1024];
119                 while ((ze = in.getNextEntry()) != null) {
120                     final String name = ze.getName();
121                     final File tmp = File.createTempFile("ref_" + name, ".png");
122                     final FileOutputStream out = new FileOutputStream(tmp);
123 
124                     int count;
125                     while ((count = in.read(buffer)) != -1) {
126                         out.write(buffer, 0, count);
127                     }
128 
129                     out.flush();
130                     out.close();
131                     mReferences.put(name, tmp);
132                 }
133             } catch (IOException e) {
134                 Log.logAndDisplay(LogLevel.ERROR, LOG_TAG, "Failed to unzip assets: " + zipFile);
135             } finally {
136                 in.close();
137             }
138         } else {
139             Log.logAndDisplay(LogLevel.ERROR, LOG_TAG, "Failed to get resource: " + zipFile);
140         }
141 
142         final int numCores = Runtime.getRuntime().availableProcessors();
143         mExecutionService = Executors.newFixedThreadPool(numCores * 2);
144         mCompletionService = new ExecutorCompletionService<>(mExecutionService);
145     }
146 
147     @Override
tearDown()148     protected void tearDown() throws Exception {
149         // Delete the temp files
150         for (File ref : mReferences.values()) {
151             ref.delete();
152         }
153 
154         mExecutionService.shutdown();
155 
156         // Remove the APK.
157         mDevice.uninstallPackage(APP_PACKAGE_NAME);
158 
159         // Remove generated images.
160         mDevice.executeShellCommand(CLEAR_GENERATED_CMD);
161 
162         super.tearDown();
163     }
164 
165     @TimeoutReq(minutes = 60)
testThemes()166     public void testThemes() throws Exception {
167         if (checkHardwareTypeSkipTest(mDevice.executeShellCommand(HARDWARE_TYPE_CMD).trim())) {
168             Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Skipped themes test for watch");
169             return;
170         }
171 
172         if (mReferences.isEmpty()) {
173             Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Skipped themes test due to no reference images");
174             return;
175         }
176 
177         Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Generating device images...");
178 
179         assertTrue("Aborted image generation", generateDeviceImages());
180 
181         // Pull ZIP file from remote device.
182         final File localZip = File.createTempFile("generated", ".zip");
183         mDevice.pullFile(GENERATED_ASSETS_ZIP, localZip);
184 
185         int numTasks = 0;
186 
187         Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Extracting generated images...");
188 
189         // Extract generated images to temporary files.
190         final byte[] data = new byte[4096];
191         final ZipInputStream zipInput = new ZipInputStream(new FileInputStream(localZip));
192         ZipEntry entry;
193         while ((entry = zipInput.getNextEntry()) != null) {
194             final String name = entry.getName();
195             final File expected = mReferences.get(name);
196             if (expected != null && expected.exists()) {
197                 final File actual = File.createTempFile("actual_" + name, ".png");
198                 final FileOutputStream pngOutput = new FileOutputStream(actual);
199 
200                 int count;
201                 while ((count = zipInput.read(data, 0, data.length)) != -1) {
202                     pngOutput.write(data, 0, count);
203                 }
204 
205                 pngOutput.flush();
206                 pngOutput.close();
207 
208                 mCompletionService.submit(new ComparisonTask(mDevice, expected, actual));
209                 numTasks++;
210             } else {
211                 Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Missing reference image for " + name);
212             }
213 
214             zipInput.closeEntry();
215         }
216 
217         zipInput.close();
218 
219         Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Waiting for comparison tasks...");
220 
221         int failures = 0;
222         for (int i = numTasks; i > 0; i--) {
223             failures += mCompletionService.take().get() ? 0 : 1;
224         }
225 
226         assertTrue(failures + " failures in theme test", failures == 0);
227 
228         Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Finished!");
229     }
230 
generateDeviceImages()231     private boolean generateDeviceImages() throws Exception {
232         // Clear logcat
233         mDevice.executeAdbCommand("logcat", "-c");
234 
235         // Stop any existing instances
236         mDevice.executeShellCommand(STOP_CMD);
237 
238         // Start activity
239         mDevice.executeShellCommand(START_CMD);
240 
241         Log.logAndDisplay(LogLevel.VERBOSE, LOG_TAG, "Starting image generation...");
242 
243         boolean aborted = false;
244         boolean waiting = true;
245         do {
246             // Dump logcat.
247             final String logs = mDevice.executeAdbCommand(
248                     "logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
249 
250             // Search for string.
251             final Scanner in = new Scanner(logs);
252             while (in.hasNextLine()) {
253                 final String line = in.nextLine();
254                 if (line.startsWith("I/" + CLASS)) {
255                     final String[] lineSplit = line.split(":");
256                     if (lineSplit.length >= 3) {
257                         final String cmd = lineSplit[1].trim();
258                         final String arg = lineSplit[2].trim();
259                         switch (cmd) {
260                             case "FAIL":
261                                 Log.logAndDisplay(LogLevel.WARN, LOG_TAG, line);
262                                 Log.logAndDisplay(LogLevel.WARN, LOG_TAG, "Aborting! Check host logs for details.");
263                                 aborted = true;
264                                 // fall-through
265                             case "OKAY":
266                                 waiting = false;
267                                 break;
268                         }
269                     }
270                 }
271             }
272             in.close();
273         } while (waiting && !aborted);
274 
275         Log.logAndDisplay(LogLevel.VERBOSE, LOG_TAG, "Image generation completed!");
276 
277         return !aborted;
278     }
279 
getDensityBucketForDevice(ITestDevice device)280     private static String getDensityBucketForDevice(ITestDevice device) {
281         final String densityProp;
282         if (device.getSerialNumber().startsWith("emulator-")) {
283             densityProp = DENSITY_PROP_EMULATOR;
284         } else {
285             densityProp = DENSITY_PROP_DEVICE;
286         }
287 
288         final int density;
289         try {
290             density = Integer.parseInt(device.getProperty(densityProp));
291         } catch (DeviceNotAvailableException e) {
292             return "unknown";
293         }
294 
295         switch (density) {
296             case 120:
297                 return "ldpi";
298             case 160:
299                 return "mdpi";
300             case 213:
301                 return "tvdpi";
302             case 240:
303                 return "hdpi";
304             case 320:
305                 return "xhdpi";
306             case 400:
307                 return "400dpi";
308             case 480:
309                 return "xxhdpi";
310             case 560:
311                 return "560dpi";
312             case 640:
313                 return "xxxhdpi";
314             default:
315                 return "" + density;
316         }
317     }
318 
checkHardwareTypeSkipTest(String hardwareTypeString)319     private static boolean checkHardwareTypeSkipTest(String hardwareTypeString) {
320         if (hardwareTypeString.contains("android.hardware.type.watch")) {
321             return true;
322         }
323 
324         return false;
325     }
326 }
327