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