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