• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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