CTS Media Files Precondition

Verify media files are present on the device before running CTS,
and push them to the device if not

bug:23939594
Change-Id: Ie9e64d414e45ada97a9bece36e703e46add5edb7
diff --git a/run_unit_tests.sh b/run_unit_tests.sh
index 882cef8..b6488f0 100755
--- a/run_unit_tests.sh
+++ b/run_unit_tests.sh
@@ -125,3 +125,23 @@
 for CLASS in ${TEST_CLASSES}; do
     java $RDBG_FLAG -cp ${JAR_PATH} ${TF_CONSOLE} run singleCommand host -n --class ${CLASS} "$@"
 done
+
+############### Run precondition tests ###############
+JARS="
+    tradefed-prebuilt\
+    compatibility-host-util\
+    cts-tradefed_v2\
+    compatibility-host-media-preconditions\
+    compatibility-host-media-preconditions-tests"
+JAR_PATH=
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}.jar
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}.jar
+done
+
+TEST_CLASSES="
+    android.mediastress.cts.preconditions.MediaPreparerTest"
+
+for CLASS in ${TEST_CLASSES}; do
+    java $RDBG_FLAG -cp ${JAR_PATH} ${TF_CONSOLE} run singleCommand host -n --class ${CLASS} "$@"
+done
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index ac23ef2..0465bf1 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -26,4 +26,4 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.media.cts" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/tests/mediastress/Android.mk b/tests/tests/mediastress/Android.mk
index afa9dd5..38d8bf6 100644
--- a/tests/tests/mediastress/Android.mk
+++ b/tests/tests/mediastress/Android.mk
@@ -28,6 +28,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsdeviceutil
 
+LOCAL_HOST_SHARED_LIBRARIES := compatibility-device-media-preconditions
+
 LOCAL_JNI_SHARED_LIBRARIES := libctsmediastress_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/mediastress/AndroidTest.xml b/tests/tests/mediastress/AndroidTest.xml
index fd434f1..43eae94 100644
--- a/tests/tests/mediastress/AndroidTest.xml
+++ b/tests/tests/mediastress/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Media Stress test cases">
+    <target_preparer class="android.mediastress.cts.preconditions.MediaPreparer" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsMediaStressTestCases.apk" />
@@ -21,4 +22,4 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.mediastress.cts" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/tests/mediastress/preconditions/Android.mk b/tests/tests/mediastress/preconditions/Android.mk
new file mode 100644
index 0000000..5838386
--- /dev/null
+++ b/tests/tests/mediastress/preconditions/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := compatibility-host-util cts-tradefed_v2 tradefed-prebuilt
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-host-media-preconditions
+
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
new file mode 100644
index 0000000..08097f9
--- /dev/null
+++ b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.mediastress.cts.preconditions;
+
+import com.android.compatibility.common.tradefed.targetprep.PreconditionPreparer;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil;
+
+import java.awt.Dimension;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipFile;
+
+/**
+ * Ensures that the appropriate media files exist on the device
+ */
+@OptionClass(alias="media-preparer")
+public class MediaPreparer extends PreconditionPreparer {
+
+    @Option(name = "local-media-path",
+            description = "Absolute path of the media files directory, containing" +
+            "'bbb_short' and 'bbb_full' directories")
+    protected String mLocalMediaPath = null;
+
+    @Option(name = "skip-media-download",
+            description = "Whether to skip the media files precondition")
+    protected boolean mSkipMediaDownload = false;
+
+    /*
+     * The default name of local directory into which media files will be downloaded, if option
+     * "local-media-path" is not provided. This directory will live inside the temp directory.
+     */
+    protected static final String MEDIA_FOLDER_NAME = "android-cts-media";
+
+    /*
+     * The URL from which to download the compressed media files
+     * TODO: Find a way to retrieve this programmatically
+     */
+    private static final String MEDIA_URL_STRING =
+            "https://dl.google.com/dl/android/cts/android-cts-media-1.1.zip";
+
+    /*
+     * The message printed when the maximum video playback resolution cannot be found in the
+     * output of 'dumpsys'. When this is the case, media files of all resolutions must be pushed
+     * to the device.
+     */
+    private static final String MAX_PLAYBACK_RES_FAILURE_MSG =
+            "Unable to parse maximum video playback resolution, pushing all media files";
+
+    private static final String LOG_TAG = MediaPreparer.class.getSimpleName();
+
+    /* Constants identifying resolutions of the media files to be copied */
+    protected static final int RES_176_144 = 0; // 176x144 resolution
+    protected static final int RES_DEFAULT = 1; // default max video playback resolution, 480x360
+    protected static final int RES_720_480 = 2; // 720x480 resolution
+    protected static final int RES_1280_720 = 3; // 1280x720 resolution
+    protected static final int RES_1920_1080 = 4; // 1920x1080 resolution
+
+    protected static final Dimension[] resolutions = { // indices meant to align with constants above
+            new Dimension(176, 144),
+            new Dimension(480, 360),
+            new Dimension(720, 480),
+            new Dimension(1280, 720),
+            new Dimension(1920, 1080)
+    };
+
+    /*
+     * The pathnames of the device's directories that hold media files for the tests.
+     * These depend on the device's mount point, which is retrieved in the MediaPreparer's run
+     * method.
+     *
+     * These fields are exposed for unit testing
+     */
+    protected String baseDeviceShortDir;
+    protected String baseDeviceFullDir;
+
+    /*
+     * Returns a string representation of the dimension
+     * For dimension of width = 480 and height = 360, the resolution string is "480x360"
+     */
+    private static String resolutionString(Dimension resolution) {
+        return String.format("%dx%d", resolution.width, resolution.height);
+    }
+
+    /*
+     * Loops through the predefined maximum video playback resolutions from largest to smallest,
+     * And returns the greatest resolution that is strictly smaller than the width and height
+     * provided in the arguments
+     */
+    private Dimension getMaxVideoPlaybackResolution(int width, int height) {
+        for (int resIndex = resolutions.length - 1; resIndex >= RES_DEFAULT; resIndex--) {
+            Dimension resolution = resolutions[resIndex];
+            if (width >= resolution.width && height >= resolution.height) {
+                return resolution;
+            }
+        }
+        return resolutions[RES_DEFAULT];
+    }
+
+    /*
+     * Returns the maximum video playback resolution of the device, in the form of a Dimension
+     * object. This method parses dumpsys output to find resolutions listed under the
+     * 'mBaseDisplayInfo' field. The value for 'smallest app' is used as an estimate for
+     * maximum video playback resolution, and is rounded down to the nearest dimension in the
+     * resolutions array.
+     *
+     * This method is exposed for unit testing.
+     */
+    protected Dimension getMaxVideoPlaybackResolution(ITestDevice device)
+            throws DeviceNotAvailableException {
+        String dumpsysOutput =
+                device.executeShellCommand("dumpsys display | grep mBaseDisplayInfo");
+        Pattern pattern = Pattern.compile("smallest app (\\d+) x (\\d+)");
+        Matcher matcher = pattern.matcher(dumpsysOutput);
+        if(!matcher.find()) {
+            // could not find resolution in dumpsysOutput, return largest max playback resolution
+            // so that preparer copies all media files
+            CLog.e(MAX_PLAYBACK_RES_FAILURE_MSG);
+            return resolutions[RES_1920_1080];
+        }
+
+        int first;
+        int second;
+        try {
+            first = Integer.parseInt(matcher.group(1));
+            second = Integer.parseInt(matcher.group(2));
+        } catch (NumberFormatException e) {
+            CLog.e(MAX_PLAYBACK_RES_FAILURE_MSG);
+            return resolutions[RES_1920_1080];
+        }
+        // dimensions in dumpsys output seem consistently reversed
+        // here we make note of which dimension is the larger of the two
+        int height = Math.min(first, second);
+        int width = Math.max(first, second);
+        return getMaxVideoPlaybackResolution(width, height);
+    }
+
+    /*
+     * Returns true if all necessary media files exist on the device, and false otherwise.
+     *
+     * This method is exposed for unit testing.
+     */
+    protected boolean mediaFilesExistOnDevice(ITestDevice device, Dimension mvpr)
+            throws DeviceNotAvailableException{
+        int resIndex = RES_176_144;
+        while (resIndex <= RES_1920_1080) {
+            Dimension copiedResolution = resolutions[resIndex];
+            if (copiedResolution.width > mvpr.width || copiedResolution.height > mvpr.height) {
+                break; // we don't need to check for resolutions greater than or equal to this
+            }
+            String resString = resolutionString(copiedResolution);
+            String deviceShortFilePath = baseDeviceShortDir + resString;
+            String deviceFullFilePath = baseDeviceFullDir + resString;
+            if (!device.doesFileExist(deviceShortFilePath) ||
+                    !device.doesFileExist(deviceFullFilePath)) { // media files must be copied
+                return false;
+            }
+            resIndex++;
+        }
+        return true;
+    }
+
+    /* A simple helper to print messages to the tradefed console */
+    private static void printInfo(String info) {
+        LogUtil.printLog(Log.LogLevel.INFO, LOG_TAG, info);
+    }
+
+    /*
+     * After downloading and unzipping the media files, mLocalMediaPath must be the path to the
+     * directory containing 'bbb_short' and 'bbb_full' directories, as it is defined in its
+     * description as an option.
+     * After extraction, this directory exists one level below the the directory 'mediaFolder'.
+     * If the 'mediaFolder' contains anything other than exactly one subdirectory, a
+     * TargetSetupError is thrown. Otherwise, the mLocalMediaPath variable is set to the path of
+     * this subdirectory.
+     */
+    private void updateLocalMediaPath(File mediaFolder) throws TargetSetupError {
+        String[] subDirs = mediaFolder.list();
+        if (subDirs.length != 1) {
+            throw new TargetSetupError(String.format(
+                    "Unexpected contents in directory %s", mLocalMediaPath));
+        }
+        File newMediaFolder = new File(mediaFolder, subDirs[0]);
+        mLocalMediaPath = newMediaFolder.toString();
+    }
+
+    /*
+     * Copies the media files to the host from a predefined URL
+     * Updates mLocalMediaPath to be the pathname of the directory containing bbb_short and
+     * bbb_full media directories.
+     */
+    private void downloadMediaToHost() throws TargetSetupError {
+
+        URL url;
+        try {
+            url = new URL(MEDIA_URL_STRING);
+        } catch (MalformedURLException e) {
+            throw new TargetSetupError(
+                    String.format("Trouble finding android media files at %s", MEDIA_URL_STRING));
+        }
+
+        File mediaFolder = new File(mLocalMediaPath);
+        File mediaFolderZip = new File(mediaFolder.getName() + ".zip");
+        try {
+
+            mediaFolder.mkdirs();
+            mediaFolderZip.createNewFile();
+
+            URLConnection conn = url.openConnection();
+            InputStream in = conn.getInputStream();
+            BufferedOutputStream out =
+                    new BufferedOutputStream(new FileOutputStream(mediaFolderZip));
+            byte[] buffer = new byte[1024];
+            int count;
+            printInfo("Downloading media files to host");
+            while ((count = in.read(buffer)) >= 0) {
+                out.write(buffer, 0, count);
+            }
+            out.flush();
+            out.close();
+            in.close();
+
+            printInfo("Unzipping media files");
+            ZipUtil.extractZip(new ZipFile(mediaFolderZip), mediaFolder);
+
+        } catch (IOException e) {
+            FileUtil.recursiveDelete(mediaFolder);
+            FileUtil.recursiveDelete(mediaFolderZip);
+            throw new TargetSetupError("Failed to open media files on host");
+        }
+    }
+
+    /*
+     * Pushes directories containing media files to the device for all directories that:
+     * - are not already present on the device
+     * - contain video files of a resolution less than or equal to the device's
+     *       max video playback resolution
+     *
+     * This method is exposed for unit testing.
+     */
+    protected void copyMediaFiles(ITestDevice device, Dimension mvpr)
+            throws DeviceNotAvailableException {
+
+        int resIndex = RES_176_144;
+        while (resIndex <= RES_1920_1080) {
+            Dimension copiedResolution = resolutions[resIndex];
+            String resString = resolutionString(copiedResolution);
+            if (copiedResolution.width > mvpr.width || copiedResolution.height > mvpr.height) {
+                printInfo(String.format(
+                        "Device cannot support resolutions %s and larger, media copying complete",
+                        resString));
+                return;
+            }
+            String deviceShortFilePath = baseDeviceShortDir + resString;
+            String deviceFullFilePath = baseDeviceFullDir + resString;
+            if (!device.doesFileExist(deviceShortFilePath) ||
+                    !device.doesFileExist(deviceFullFilePath)) {
+                printInfo(String.format("Copying files of resolution %s to device", resString));
+                String localShortDirName = "bbb_short/" + resString;
+                String localFullDirName = "bbb_full/" + resString;
+                File localShortDir = new File(mLocalMediaPath, localShortDirName);
+                File localFullDir = new File(mLocalMediaPath, localFullDirName);
+                // push short directory of given resolution, if not present on device
+                if(!device.doesFileExist(deviceShortFilePath)) {
+                    device.pushDir(localShortDir, deviceShortFilePath);
+                }
+                // push full directory of given resolution, if not present on device
+                if(!device.doesFileExist(deviceFullFilePath)) {
+                    device.pushDir(localFullDir, deviceFullFilePath);
+                }
+            }
+            resIndex++;
+        }
+    }
+
+    // Initialize directory strings where media files live on device
+    protected void setMountPoint(ITestDevice device) {
+        String mountPoint = device.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
+        baseDeviceShortDir = String.format("%s/test/bbb_short/", mountPoint);
+        baseDeviceFullDir = String.format("%s/test/bbb_full/", mountPoint);
+    }
+
+    @Override
+    public void run(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+            BuildError, DeviceNotAvailableException {
+
+        if (mSkipMediaDownload) {
+            // skip this precondition
+            return;
+        }
+
+        setMountPoint(device);
+        Dimension mvpr = getMaxVideoPlaybackResolution(device);
+        if (mediaFilesExistOnDevice(device, mvpr)) {
+            // if files already on device, do nothing
+            printInfo("Media files found on the device");
+            return;
+        }
+
+        File mediaFolder;
+        if (mLocalMediaPath == null) {
+            // Option 'local-media-path' has not been defined
+            try {
+                // find system's temp directory, create folder MEDIA_FOLDER_NAME inside
+                File tmpFile = File.createTempFile(MEDIA_FOLDER_NAME, null);
+                String tmpDir = tmpFile.getParent();
+                mediaFolder = new File(tmpDir, MEDIA_FOLDER_NAME);
+                // delete temp file used for locating temp directory
+                tmpFile.delete();
+            } catch (IOException e) {
+                throw new TargetSetupError("Unable to create host temp directory for media files");
+            }
+            mLocalMediaPath = mediaFolder.getAbsolutePath();
+            if(!mediaFolder.exists()){
+                // directory has not been created by previous runs of MediaPreparer
+                // download media into mLocalMediaPath
+                downloadMediaToHost();
+            }
+            updateLocalMediaPath(mediaFolder);
+        }
+
+        printInfo(String.format("Media files located on host at: %s", mLocalMediaPath));
+        copyMediaFiles(device, mvpr);
+    }
+
+}
diff --git a/tests/tests/mediastress/preconditions/tests/Android.mk b/tests/tests/mediastress/preconditions/tests/Android.mk
new file mode 100644
index 0000000..67eaf5d
--- /dev/null
+++ b/tests/tests/mediastress/preconditions/tests/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := easymock
+
+LOCAL_JAVA_LIBRARIES := compatibility-host-util cts-tradefed_v2 tradefed-prebuilt compatibility-host-media-preconditions
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-host-media-preconditions-tests
+
+# Tag this module as a cts_v2 test artifact
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
new file mode 100644
index 0000000..54ab025
--- /dev/null
+++ b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediastress.cts.preconditions;
+
+import com.android.ddmlib.IDevice;
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+import java.awt.Dimension;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+
+public class MediaPreparerTest extends TestCase {
+
+    private MediaPreparer mMediaPreparer;
+    private IBuildInfo mMockBuildInfo;
+    private ITestDevice mMockDevice;
+    private OptionSetter mOptionSetter;
+
+    private final Dimension DEFAULT_DIMENSION =
+            MediaPreparer.resolutions[MediaPreparer.RES_DEFAULT];
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mMediaPreparer = new MediaPreparer();
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mMockBuildInfo = new BuildInfo("0", "", "");
+        mOptionSetter = new OptionSetter(mMediaPreparer);
+    }
+
+    public void testSetMountPoint() throws Exception {
+        EasyMock.expect(mMockDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE)).andReturn(
+                "/sdcard").once();
+        EasyMock.replay(mMockDevice);
+        mMediaPreparer.setMountPoint(mMockDevice);
+        assertEquals(mMediaPreparer.baseDeviceShortDir, "/sdcard/test/bbb_short/");
+        assertEquals(mMediaPreparer.baseDeviceFullDir, "/sdcard/test/bbb_full/");
+    }
+
+    public void testCopyMediaFiles() throws Exception {
+        // by jumping directly into copyMediaFiles, the baseDeviceShortDir variable won't be set
+        // thus, the string "null" replaces the variable
+        EasyMock.expect(mMockDevice.doesFileExist("null176x144")).andReturn(true).anyTimes();
+        EasyMock.expect(mMockDevice.doesFileExist("null480x360")).andReturn(true).anyTimes();
+        EasyMock.replay(mMockDevice);
+        mMediaPreparer.copyMediaFiles(mMockDevice, DEFAULT_DIMENSION);
+    }
+
+    public void testMediaFilesExistOnDeviceTrue() throws Exception {
+        // by jumping directly into copyMediaFiles, the baseDeviceShortDir variable won't be set
+        // thus, the string "null" replaces the variable
+        EasyMock.expect(mMockDevice.doesFileExist("null176x144")).andReturn(true).anyTimes();
+        EasyMock.expect(mMockDevice.doesFileExist("null480x360")).andReturn(true).anyTimes();
+        EasyMock.replay(mMockDevice);
+        assertTrue(mMediaPreparer.mediaFilesExistOnDevice(mMockDevice, DEFAULT_DIMENSION));
+    }
+
+    public void testMediaFilesExistOnDeviceFalse() throws Exception {
+        // by jumping directly into copyMediaFiles, the baseDeviceShortDir variable won't be set
+        // thus, the string "null" replaces the variable
+        EasyMock.expect(mMockDevice.doesFileExist("null176x144")).andReturn(false).anyTimes();
+        EasyMock.expect(mMockDevice.doesFileExist("null480x360")).andReturn(true).anyTimes();
+        EasyMock.replay(mMockDevice);
+        assertFalse(mMediaPreparer.mediaFilesExistOnDevice(mMockDevice, DEFAULT_DIMENSION));
+    }
+
+    public void testGetMaxVideoPlaybackResolutionFound() throws Exception {
+        String mockDumpsysOutput = "mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", uniqueId " +
+                "\"local:0\", app 1440 x 2560, real 1440 x 2560, largest app 1440 x 2560, " +
+                "smallest app 360 x 480, mode 1, defaultMode 1, modes [{id=1, width=1440, " +
+                "height=2560, fps=60.0}], rotation 0, density 560 (494.27 x 492.606) dpi, " +
+                "layerStack 0, appVsyncOff 2500000, presDeadline 17666667, type BUILT_IN, state " +
+                "ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}\n";
+        EasyMock.expect(mMockDevice.executeShellCommand(
+                "dumpsys display | grep mBaseDisplayInfo")).andReturn(mockDumpsysOutput).once();
+        EasyMock.replay(mMockDevice);
+        Dimension result = mMediaPreparer.getMaxVideoPlaybackResolution(mMockDevice);
+        assertEquals(result, DEFAULT_DIMENSION);
+    }
+
+    public void testGetMaxVideoPlaybackResolutionNotFound() throws Exception {
+        String mockDumpsysOutput = "incorrect output";
+        EasyMock.expect(mMockDevice.executeShellCommand(
+                "dumpsys display | grep mBaseDisplayInfo")).andReturn(mockDumpsysOutput).once();
+        EasyMock.replay(mMockDevice);
+        Dimension result = mMediaPreparer.getMaxVideoPlaybackResolution(mMockDevice);
+        assertEquals(result, MediaPreparer.resolutions[MediaPreparer.RES_1920_1080]);
+    }
+
+    public void testSkipMediaDownload() throws Exception {
+        mOptionSetter.setOptionValue("skip-media-download", "true");
+        EasyMock.replay();
+        mMediaPreparer.run(mMockDevice, mMockBuildInfo);
+    }
+
+}