1 /* 2 * Copyright (C) 2015 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 com.android.compatibility.common.tradefed.targetprep; 17 18 import static com.android.tradefed.targetprep.UserHelper.getRunTestsAsUser; 19 20 import com.android.annotations.VisibleForTesting; 21 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 22 import com.android.compatibility.common.tradefed.util.DynamicConfigFileReader; 23 import com.android.ddmlib.IDevice; 24 import com.android.tradefed.build.IBuildInfo; 25 import com.android.tradefed.config.Configuration; 26 import com.android.tradefed.config.IConfiguration; 27 import com.android.tradefed.config.IConfigurationReceiver; 28 import com.android.tradefed.config.IDeviceConfiguration; 29 import com.android.tradefed.config.Option; 30 import com.android.tradefed.config.OptionClass; 31 import com.android.tradefed.dependencies.ExternalDependency; 32 import com.android.tradefed.dependencies.IExternalDependency; 33 import com.android.tradefed.dependencies.connectivity.NetworkDependency; 34 import com.android.tradefed.device.DeviceNotAvailableException; 35 import com.android.tradefed.device.ITestDevice; 36 import com.android.tradefed.device.contentprovider.ContentProviderHandler; 37 import com.android.tradefed.invoker.TestInformation; 38 import com.android.tradefed.log.LogUtil.CLog; 39 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 40 import com.android.tradefed.result.ITestInvocationListener; 41 import com.android.tradefed.result.TestDescription; 42 import com.android.tradefed.result.error.DeviceErrorIdentifier; 43 import com.android.tradefed.result.error.InfraErrorIdentifier; 44 import com.android.tradefed.targetprep.BaseTargetPreparer; 45 import com.android.tradefed.targetprep.BuildError; 46 import com.android.tradefed.targetprep.ITargetPreparer; 47 import com.android.tradefed.targetprep.TargetSetupError; 48 import com.android.tradefed.testtype.AndroidJUnitTest; 49 import com.android.tradefed.util.FileUtil; 50 import com.android.tradefed.util.StreamUtil; 51 import com.android.tradefed.util.ZipUtil; 52 53 import org.xmlpull.v1.XmlPullParserException; 54 55 import java.io.BufferedReader; 56 import java.io.File; 57 import java.io.FileNotFoundException; 58 import java.io.FileReader; 59 import java.io.FileWriter; 60 import java.io.IOException; 61 import java.io.InputStream; 62 import java.net.URL; 63 import java.net.URLConnection; 64 import java.util.HashMap; 65 import java.util.HashSet; 66 import java.util.Set; 67 import java.util.regex.Matcher; 68 import java.util.regex.Pattern; 69 import java.util.zip.ZipFile; 70 71 /** Ensures that the appropriate media files exist on the device */ 72 @OptionClass(alias = "media-preparer") 73 public class MediaPreparer extends BaseTargetPreparer 74 implements IExternalDependency, IConfigurationReceiver { 75 76 @Option( 77 name = "local-media-path", 78 description = 79 "Absolute path of the media files directory, containing" 80 + "'bbb_short' and 'bbb_full' directories" 81 ) 82 private String mLocalMediaPath = null; 83 84 @Option( 85 name = "skip-media-download", 86 description = "Whether to skip the media files precondition" 87 ) 88 private boolean mSkipMediaDownload = false; 89 90 @Option( 91 name = "simple-caching-semantics", 92 description = "Whether to use the original, simple MediaPreparer caching semantics") 93 private boolean mSimpleCachingSemantics = false; 94 95 @Option( 96 name = "media-download-only", 97 description = "Only download media files; do not run instrumentation or copy files") 98 private boolean mMediaDownloadOnly = false; 99 100 @Option( 101 name = "push-all", 102 description = 103 "Push everything downloaded to the device," 104 + " use 'media-folder-name' to specify the destination dir name." 105 ) 106 private boolean mPushAll = false; 107 108 @Option(name = "dynamic-config-module", 109 description = "For a target preparer, the 'module' of the configuration" + 110 " is the test suite.") 111 private String mDynamicConfigModule = "cts"; 112 113 @Option(name = "media-folder-name", 114 description = "The name of local directory into which media" + 115 " files will be downloaded, if option 'local-media-path' is not" + 116 " provided. This directory will live inside the temp directory." + 117 " If option 'push-all' is set, this is also the subdirectory name on device" + 118 " where media files are pushed to") 119 private String mMediaFolderName = MEDIA_FOLDER_NAME; 120 121 @Option(name = "use-legacy-folder-structure", 122 description = "Use legacy folder structure to store big buck bunny clips. When this " + 123 "is set to false, name specified in media-folder-name will be used. Default: true") 124 private boolean mUseLegacyFolderStructure = true; 125 126 /* 127 * The pathnames of the device's directories that hold media files for the tests. 128 * These depend on the device's mount point, which is retrieved in the MediaPreparer's run 129 * method. 130 * 131 * These fields are exposed for unit testing 132 */ 133 protected String mBaseDeviceModuleDir; 134 protected String mBaseDeviceShortDir; 135 protected String mBaseDeviceFullDir; 136 137 /* 138 * Variables set by the MediaPreparerListener during retrieval of maximum media file 139 * resolution. After the MediaPreparerApp has been instrumented on the device: 140 * 141 * testMetrics contains the string representation of the resolution 142 * testFailures contains a stacktrace if retrieval of the resolution was unsuccessful 143 */ 144 protected Resolution mMaxRes = null; 145 protected String mFailureStackTrace = null; 146 147 /* User id that the test is running as. */ 148 private int mUserId = -1; 149 150 /** The module level configuration to check the target preparers. */ 151 private IConfiguration mModuleConfiguration; 152 153 /* 154 * The default name of local directory into which media files will be downloaded, if option 155 * "local-media-path" is not provided. This directory will live inside the temp directory. 156 */ 157 protected static final String MEDIA_FOLDER_NAME = "android-cts-media"; 158 159 /* The key used to retrieve the media files URL from the dynamic configuration */ 160 private static final String MEDIA_FILES_URL_KEY = "media_files_url"; 161 162 /* 163 * Info used to install and uninstall the MediaPreparerApp 164 */ 165 private static final String APP_APK = "CtsMediaPreparerApp.apk"; 166 private static final String APP_PKG_NAME = "android.mediastress.cts.preconditions.app"; 167 168 /* Key to retrieve resolution string in metrics upon MediaPreparerListener.testEnded() */ 169 private static final String RESOLUTION_STRING_KEY = "resolution"; 170 171 protected static final Resolution[] RESOLUTIONS = { 172 new Resolution(176, 144), 173 new Resolution(480, 360), 174 new Resolution(720, 480), 175 new Resolution(1280, 720), 176 new Resolution(1920, 1080) 177 }; 178 179 /** {@inheritDoc} */ 180 @Override getDependencies()181 public Set<ExternalDependency> getDependencies() { 182 Set<ExternalDependency> dependencies = new HashSet<>(); 183 if (!mSkipMediaDownload) { 184 dependencies.add(new NetworkDependency()); 185 } 186 return dependencies; 187 } 188 189 @Override setConfiguration(IConfiguration configuration)190 public void setConfiguration(IConfiguration configuration) { 191 mModuleConfiguration = configuration; 192 } 193 194 /** Helper class for generating and retrieving width-height pairs */ 195 protected static final class Resolution { 196 // regex that matches a resolution string 197 private static final String PATTERN = "(\\d+)x(\\d+)"; 198 // group indices for accessing resolution width and height from a PATTERN-based Matcher 199 private static final int WIDTH_INDEX = 1; 200 private static final int HEIGHT_INDEX = 2; 201 202 private final int width; 203 private final int height; 204 Resolution(int width, int height)205 private Resolution(int width, int height) { 206 this.width = width; 207 this.height = height; 208 } 209 Resolution(String resolution)210 private Resolution(String resolution) { 211 Pattern pattern = Pattern.compile(PATTERN); 212 Matcher matcher = pattern.matcher(resolution); 213 matcher.find(); 214 this.width = Integer.parseInt(matcher.group(WIDTH_INDEX)); 215 this.height = Integer.parseInt(matcher.group(HEIGHT_INDEX)); 216 } 217 218 @Override toString()219 public String toString() { 220 return String.format("%dx%d", width, height); 221 } 222 223 /** Returns the width of the resolution. */ getWidth()224 public int getWidth() { 225 return width; 226 } 227 } 228 getDefaultMediaDir()229 public static File getDefaultMediaDir() { 230 return new File(System.getProperty("java.io.tmpdir"), MEDIA_FOLDER_NAME); 231 } 232 getMediaDir()233 protected File getMediaDir() { 234 return new File(System.getProperty("java.io.tmpdir"), mMediaFolderName); 235 } 236 237 /* 238 * Returns true if all necessary media files exist on the device, and false otherwise. 239 * 240 * This method is exposed for unit testing. 241 */ 242 @VisibleForTesting mediaFilesExistOnDevice(ITestDevice device)243 protected boolean mediaFilesExistOnDevice(ITestDevice device) 244 throws DeviceNotAvailableException { 245 if (mPushAll) { 246 return device.doesFileExist(mBaseDeviceModuleDir, mUserId); 247 } 248 for (Resolution resolution : RESOLUTIONS) { 249 if (resolution.width > mMaxRes.width) { 250 break; // no need to check for resolutions greater than this 251 } 252 String deviceShortFilePath = mBaseDeviceShortDir + resolution.toString(); 253 String deviceFullFilePath = mBaseDeviceFullDir + resolution.toString(); 254 if (!device.doesFileExist(deviceShortFilePath, mUserId) 255 || !device.doesFileExist(deviceFullFilePath, mUserId)) { 256 return false; 257 } 258 } 259 return true; 260 } 261 262 protected static final String TOC_NAME = "contents.toc"; 263 264 /* 265 * After downloading and unzipping the media files, mLocalMediaPath must be the path to the 266 * directory containing 'bbb_short' and 'bbb_full' directories, as it is defined in its 267 * description as an option. 268 * After extraction, this directory exists one level below the the directory 'mediaFolder'. 269 * If the 'mediaFolder' contains anything other than exactly one subdirectory, a 270 * TargetSetupError is thrown. Otherwise, the mLocalMediaPath variable is set to the path of 271 * this subdirectory. 272 */ updateLocalMediaPath(ITestDevice device, File mediaFolder)273 private void updateLocalMediaPath(ITestDevice device, File mediaFolder) 274 throws TargetSetupError { 275 String[] entries = mediaFolder.list(); 276 277 // directory should contain: 278 // -- content subdirectory 279 // -- TOC (if we've run with the new caching semantics) 280 // if we've run new semantics, old semantics should ignore the TOC if present. 281 // 282 if (entries.length == 0) { 283 throw new TargetSetupError( 284 String.format("Unexpectedly empty directory %s", mediaFolder.getAbsolutePath()), 285 device.getDeviceDescriptor()); 286 } else if (entries.length > 2) { 287 throw new TargetSetupError(String.format( 288 "Unexpected contents in directory %s", mediaFolder.getAbsolutePath()), 289 device.getDeviceDescriptor()); 290 } 291 292 // choose the entry that represents the contents to be sent, not the TOC 293 int slot = 0; 294 if (entries[slot].equals(TOC_NAME)) { 295 if (entries.length == 1) { 296 throw new TargetSetupError( 297 String.format( 298 "Missing contents in directory %s", mediaFolder.getAbsolutePath()), 299 device.getDeviceDescriptor()); 300 } 301 slot = 1; 302 } 303 mLocalMediaPath = new File(mediaFolder, entries[slot]).getAbsolutePath(); 304 } 305 generateDirectoryToc(FileWriter myWriter, File myFolder, String leadingPath)306 private void generateDirectoryToc(FileWriter myWriter, File myFolder, String leadingPath) 307 throws IOException { 308 String prefixPath; 309 if (leadingPath.equals("")) { 310 prefixPath = ""; 311 } else { 312 prefixPath = leadingPath + File.separator; 313 } 314 for (String fileName : myFolder.list()) { 315 // list myself 316 myWriter.write(prefixPath + fileName + "\n"); 317 // and recurse if i'm a directory 318 File oneFile = new File(myFolder, fileName); 319 if (oneFile.isDirectory()) { 320 String newLeading = prefixPath + fileName; 321 generateDirectoryToc(myWriter, oneFile, newLeading); 322 } 323 } 324 } 325 326 /* 327 * Copies the media files to the host from a predefined URL. 328 * 329 * Synchronize this method so that multiple shards won't download/extract 330 * this file to the same location on the host. Only an issue in Android O and above, 331 * where MediaPreparer is used for multiple, shardable modules. 332 */ downloadMediaToHost(ITestDevice device, IBuildInfo buildInfo)333 private File downloadMediaToHost(ITestDevice device, IBuildInfo buildInfo) 334 throws TargetSetupError { 335 336 // Make sure the synchronization is on the class and not the object 337 synchronized (MediaPreparer.class) { 338 // Retrieve default directory for storing media files 339 File mediaFolder = getMediaDir(); 340 341 // manage caching the content on the host side 342 // 343 if (mediaFolder.exists() && mediaFolder.list().length > 0) { 344 // Folder has been created and populated by a previous MediaPreparer run. 345 // 346 347 if (mSimpleCachingSemantics) { 348 // old semantics: assumes all necessary media files exist inside 349 CLog.i("old cache semantics: local directory exists, all is well"); 350 return mediaFolder; 351 } 352 353 CLog.i("new cache semantics: verify against a TOC"); 354 // new caching semantics: 355 // verify that the contents are still present. 356 // use the TOC file generated when first downloaded/unpacked. 357 // if TOC or any files are missing -- redownload. 358 // 359 // we're chatty about why we decide to re-download 360 361 boolean passing = true; 362 BufferedReader tocReader = null; 363 try { 364 File tocFile = new File(mediaFolder, TOC_NAME); 365 if (!tocFile.exists()) { 366 passing = false; 367 CLog.i( 368 "missing/inaccessible TOC: " 369 + mediaFolder 370 + File.separator 371 + TOC_NAME); 372 } else { 373 tocReader = new BufferedReader(new FileReader(tocFile)); 374 String line = tocReader.readLine(); 375 while (line != null) { 376 File oneFile = new File(mediaFolder, line); 377 if (!oneFile.exists()) { 378 CLog.i( 379 "missing TOC-listed file: " 380 + mediaFolder 381 + File.separator 382 + line); 383 passing = false; 384 break; 385 } 386 line = tocReader.readLine(); 387 } 388 } 389 } catch (IOException | SecurityException | NullPointerException e) { 390 CLog.i("TOC or contents missing, redownload"); 391 passing = false; 392 } finally { 393 StreamUtil.close(tocReader); 394 } 395 396 if (passing) { 397 CLog.i("Host-cached copy is complete in " + mediaFolder); 398 return mediaFolder; 399 } 400 } 401 402 // uncached (or broken cache), so download again 403 404 mediaFolder.mkdirs(); 405 URL url; 406 try { 407 // Get download URL from dynamic configuration service 408 String mediaUrlString = 409 DynamicConfigFileReader.getValueFromConfig( 410 buildInfo, mDynamicConfigModule, MEDIA_FILES_URL_KEY); 411 url = new URL(mediaUrlString); 412 } catch (IOException | XmlPullParserException e) { 413 throw new TargetSetupError( 414 "Trouble finding media file download location with " 415 + "dynamic configuration", 416 e, 417 device.getDeviceDescriptor()); 418 } 419 File mediaFolderZip = new File(mediaFolder.getAbsolutePath() + ".zip"); 420 FileWriter tocWriter = null; 421 try { 422 CLog.i("Downloading media files from %s", url.toString()); 423 URLConnection conn = url.openConnection(); 424 InputStream in = conn.getInputStream(); 425 mediaFolderZip.createNewFile(); 426 FileUtil.writeToFile(in, mediaFolderZip); 427 CLog.i("Unzipping media files"); 428 ZipUtil.extractZip(new ZipFile(mediaFolderZip), mediaFolder); 429 430 // create the TOC when running the new caching scheme 431 if (!mSimpleCachingSemantics) { 432 // create a TOC, recursively listing all files/directories. 433 // used to verify all files still exist before we re-use a prior copy 434 CLog.i("Generating cache TOC"); 435 File tocFile = new File(mediaFolder, TOC_NAME); 436 tocWriter = new FileWriter(tocFile, /*append*/ false); 437 generateDirectoryToc(tocWriter, mediaFolder, ""); 438 } 439 440 } catch (IOException e) { 441 FileUtil.recursiveDelete(mediaFolder); 442 throw new TargetSetupError( 443 String.format( 444 "Failed to download and open media files on host machine at '%s'." 445 + " These media files are required for compatibility tests.", 446 mediaFolderZip), 447 e, 448 device.getDeviceDescriptor(), 449 /* device side */ false); 450 } finally { 451 FileUtil.deleteFile(mediaFolderZip); 452 StreamUtil.close(tocWriter); 453 } 454 return mediaFolder; 455 } 456 } 457 458 /* 459 * Pushes directories containing media files to the device for all directories that: 460 * - are not already present on the device 461 * - contain video files of a resolution less than or equal to the device's 462 * max video playback resolution 463 * 464 * This method is exposed for unit testing. 465 */ copyMediaFiles(ITestDevice device)466 protected void copyMediaFiles(ITestDevice device) throws DeviceNotAvailableException { 467 if (mPushAll) { 468 copyAll(device); 469 return; 470 } 471 copyVideoFiles(device); 472 } 473 474 // copy video files of a resolution <= the device's maximum video playback resolution copyVideoFiles(ITestDevice device)475 protected void copyVideoFiles(ITestDevice device) throws DeviceNotAvailableException { 476 for (Resolution resolution : RESOLUTIONS) { 477 if (resolution.width > mMaxRes.width) { 478 CLog.i("Media file copying complete"); 479 return; 480 } 481 String deviceShortFilePath = mBaseDeviceShortDir + resolution.toString(); 482 String deviceFullFilePath = mBaseDeviceFullDir + resolution.toString(); 483 if (!device.doesFileExist(deviceShortFilePath, mUserId) 484 || !device.doesFileExist(deviceFullFilePath, mUserId)) { 485 CLog.i("Copying files of resolution %s to device", resolution.toString()); 486 String localShortDirName = "bbb_short/" + resolution.toString(); 487 String localFullDirName = "bbb_full/" + resolution.toString(); 488 File localShortDir = new File(mLocalMediaPath, localShortDirName); 489 File localFullDir = new File(mLocalMediaPath, localFullDirName); 490 // push short directory of given resolution, if not present on device 491 if (!device.doesFileExist(deviceShortFilePath, mUserId)) { 492 device.pushDir(localShortDir, deviceShortFilePath, mUserId); 493 } 494 // push full directory of given resolution, if not present on device 495 if (!device.doesFileExist(deviceFullFilePath, mUserId)) { 496 device.pushDir(localFullDir, deviceFullFilePath, mUserId); 497 } 498 } 499 } 500 } 501 502 // copy everything from the host directory to the device copyAll(ITestDevice device)503 protected void copyAll(ITestDevice device) throws DeviceNotAvailableException { 504 if (!device.doesFileExist(mBaseDeviceModuleDir, mUserId)) { 505 CLog.i("Copying files to device"); 506 device.pushDir(new File(mLocalMediaPath), mBaseDeviceModuleDir, mUserId); 507 } 508 } 509 510 // Initialize directory strings where media files live on device setMountPoint(ITestDevice device)511 protected void setMountPoint(ITestDevice device) { 512 String mountPoint = device.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 513 mBaseDeviceModuleDir = String.format("%s/test/%s/", mountPoint, mMediaFolderName); 514 if (mUseLegacyFolderStructure) { 515 mBaseDeviceShortDir = String.format("%s/test/bbb_short/", mountPoint); 516 mBaseDeviceFullDir = String.format("%s/test/bbb_full/", mountPoint); 517 } else { 518 mBaseDeviceShortDir = String.format("%s/test/%s/bbb_short/", mountPoint, 519 mMediaFolderName); 520 mBaseDeviceFullDir = String.format("%s/test/%s/bbb_full/", mountPoint, 521 mMediaFolderName); 522 } 523 } 524 525 @Override setUp(TestInformation testInfo)526 public void setUp(TestInformation testInfo) 527 throws TargetSetupError, BuildError, DeviceNotAvailableException { 528 ITestDevice device = testInfo.getDevice(); 529 IBuildInfo buildInfo = testInfo.getBuildInfo(); 530 mUserId = getRunTestsAsUser(testInfo); 531 if (mSkipMediaDownload) { 532 CLog.i("Skipping media preparation"); 533 return; // skip this precondition 534 } 535 536 if (!mMediaDownloadOnly) { 537 setMountPoint(device); 538 if (!mPushAll) { 539 setMaxRes(testInfo); // max resolution only applies to video files 540 } 541 if (mediaFilesExistOnDevice(device)) { 542 // if files already on device, do nothing 543 CLog.i("Media files found on the device"); 544 return; 545 } 546 } 547 548 if (mLocalMediaPath == null) { 549 // Option 'local-media-path' has not been defined 550 // Get directory to store media files on this host 551 File mediaFolder = downloadMediaToHost(device, buildInfo); 552 // set mLocalMediaPath to extraction location of media files 553 updateLocalMediaPath(device, mediaFolder); 554 } 555 CLog.i("Media files located on host at: " + mLocalMediaPath); 556 if (!mMediaDownloadOnly) { 557 copyMediaFiles(device); 558 } 559 } 560 561 @VisibleForTesting setUserId(int testUser)562 protected void setUserId(int testUser) { 563 mUserId = testUser; 564 } 565 566 // Initialize maximum resolution of media files to copy 567 @VisibleForTesting setMaxRes(TestInformation testInfo)568 protected void setMaxRes(TestInformation testInfo) 569 throws DeviceNotAvailableException, TargetSetupError { 570 ITestInvocationListener listener = new MediaPreparerListener(); 571 ITestDevice device = testInfo.getDevice(); 572 IBuildInfo buildInfo = testInfo.getBuildInfo(); 573 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo); 574 File apkFile = null; 575 try { 576 apkFile = buildHelper.getTestFile(APP_APK); 577 if (!apkFile.exists()) { 578 // handle both missing tests dir and missing APK in catch block 579 throw new FileNotFoundException(); 580 } 581 } catch (FileNotFoundException e) { 582 throw new TargetSetupError( 583 String.format("Could not find '%s'", APP_APK), 584 InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND); 585 } 586 if (device.getAppPackageInfo(APP_PKG_NAME) != null) { 587 device.uninstallPackage(APP_PKG_NAME); 588 } 589 CLog.i("Instrumenting package %s:", APP_PKG_NAME); 590 // We usually discourage from referencing the content provider utility 591 // but in this case, the helper needs it installed. 592 new ContentProviderHandler(device, mUserId).setUp(); 593 AndroidJUnitTest instrTest = new AndroidJUnitTest(); 594 instrTest.setDevice(device); 595 instrTest.setInstallFile(apkFile); 596 instrTest.setPackageName(APP_PKG_NAME); 597 String moduleName = getDynamicModuleName(); 598 if (moduleName != null) { 599 instrTest.addInstrumentationArg("module-name", moduleName); 600 } 601 // AndroidJUnitTest requires a IConfiguration to work properly, add a stub to this 602 // implementation to avoid an NPE. 603 instrTest.setConfiguration(new Configuration("stub", "stub")); 604 instrTest.run(testInfo, listener); 605 if (mFailureStackTrace != null) { 606 throw new TargetSetupError( 607 String.format( 608 "Retrieving maximum resolution failed with trace:\n%s", 609 mFailureStackTrace), 610 DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); 611 } else if (mMaxRes == null) { 612 throw new TargetSetupError( 613 String.format("Failed to pull resolution capabilities from device"), 614 DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); 615 } 616 } 617 618 /* Special listener for setting MediaPreparer instance variable values */ 619 private class MediaPreparerListener implements ITestInvocationListener { 620 621 @Override testEnded(TestDescription test, HashMap<String, Metric> metrics)622 public void testEnded(TestDescription test, HashMap<String, Metric> metrics) { 623 Metric resMetric = metrics.get(RESOLUTION_STRING_KEY); 624 if (resMetric != null) { 625 mMaxRes = new Resolution(resMetric.getMeasurements().getSingleString()); 626 } 627 } 628 629 @Override testFailed(TestDescription test, String trace)630 public void testFailed(TestDescription test, String trace) { 631 mFailureStackTrace = trace; 632 } 633 } 634 635 @VisibleForTesting getDynamicModuleName()636 protected String getDynamicModuleName() throws TargetSetupError { 637 String moduleName = null; 638 boolean sameDevice = false; 639 for (IDeviceConfiguration deviceConfig : mModuleConfiguration.getDeviceConfig()) { 640 for (ITargetPreparer prep : deviceConfig.getTargetPreparers()) { 641 if (prep instanceof DynamicConfigPusher) { 642 moduleName = ((DynamicConfigPusher) prep).createModuleName(); 643 if (sameDevice) { 644 throw new TargetSetupError( 645 "DynamicConfigPusher needs to be configured before MediaPreparer" 646 + " in your module configuration.", 647 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 648 } 649 } 650 if (prep.equals(this)) { 651 sameDevice = true; 652 if (moduleName != null) { 653 return moduleName; 654 } 655 } 656 } 657 moduleName = null; 658 sameDevice = false; 659 } 660 return null; 661 } 662 } 663