Updates script to generate images automatically on emulator

Bug: 64529725
Test: ./cts/hostsidetests/theme/run_theme_capture_device.py Nexus_5_API_27
Change-Id: I38d731ffc899a25422842236e4f974ffc6f2e102
diff --git a/hostsidetests/theme/README b/hostsidetests/theme/README
index 64ca416..4f93e5a 100644
--- a/hostsidetests/theme/README
+++ b/hostsidetests/theme/README
@@ -30,33 +30,42 @@
 I. Generating reference images (CTS maintainers only)
 
 Reference images are typically only generated for new API revisions. To
-generate a new set of reference images, do the following:
+generate a new set of reference images from an emulator, do the following:
 
-  1. Obtain the emulator image for the current platform. You can either build
-     these from scratch (not recommended) via:
+  1. Ensure the Android SDK is installed locally. Either a public or internal
+     distribution is fine. From the console, set the ANDROID_SDK_ROOT env var:
 
-     make PRODUCT-sdk_phone_x86_64-sdk sdk_repo -j32
+     export ANDROID_SDK_ROOT = /path/to/sdk
 
-     Or obtain them from the sdk-repo-linux-system-images-<build-id>.zip
-     artifact from the sdk_phone_x86_64-sdk build server target.
+  2. Obtain an x86_64 emulator image from the build server by using the script
+     available internally at go/emu-dev. This script will install the image in
+     your SDK.
 
-  2. Start one AVD emulator image for each DPI bucket (ldpi, xxxhdpi, etc.)
-     that you wish to generate references images for. Anecdotally, up to three
-     emulators can run reliably at the same time. Confirm that all emulators
-     are connected with:
+  3. Use the SDK's AVD Manager tool to create a single virtual device using the
+     emulator image from step 2. The exact configuration doesn't really matter;
+     you can use Nexus 5 as a template. Name the emulator "theme_emulator".
 
-     adb devices
-
-  3. Set up your build environment for x86_64 and build CTS:
+  4. From the console, set up your build environment for x86_64 and build CTS:
 
      lunch sdk_x86_64-eng && make cts -j32
 
-  2. Generate the reference images. Generation occurs on all devices in
-     parallel. Resulting sets of reference images are automatically saved in
-     assets/<platform>/<dpi>.zip and will overwrite any existing sets. Image
-     generation may be started using:
+  5. Use the reference image script to generate the reference images. The script
+     will automatically start the emulator in the required configurations and
+     install the resulting reference images in assets/<platform>/<dpi>.zip,
+     overwriting any existing images.
 
-     ./cts/hostsidetests/theme/generate_images.sh
+     ./cts/hostsidetests/theme/generate_images.py theme_emulator
+
+You can also generate reference images using a real device. To generate a new set
+of reference images from a real device, do the following:
+
+  1. Connect the device. Verify the device is connected:
+
+     adb devices
+
+  2. Use the reference image script to generate the reference images:
+
+     ./cts/hostsidetests/theme/generate_images.py
 
 A complete collection of reference images for a given API revision must include
 a set for each possible DPI bucket (tvdpi, xxhdpi, etc.) that may be tested.
diff --git a/hostsidetests/theme/android_device.py b/hostsidetests/theme/android_device.py
index 4c7d8d1..7711060 100644
--- a/hostsidetests/theme/android_device.py
+++ b/hostsidetests/theme/android_device.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/python3
 #
 # Copyright (C) 2015 The Android Open Source Project
 #
@@ -17,106 +17,119 @@
 
 import os
 import re
+import subprocess
 import sys
 import threading
-import subprocess
 import time
 
+from subprocess import PIPE
+
+
 # class for running android device from python
 # it will fork the device processor
-class androidDevice(object):
-    def __init__(self, adbDevice):
-        self._adbDevice = adbDevice
+class AndroidDevice(object):
+    def __init__(self, serial):
+        self._serial = serial
 
-    def runAdbCommand(self, cmd):
-        self.waitForAdbDevice()
-        adbCmd = "adb -s %s %s" %(self._adbDevice, cmd)
-        print adbCmd
-        adbProcess = subprocess.Popen(adbCmd.split(" "), bufsize = -1, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
-        return adbProcess.communicate()
+    def run_adb_command(self, cmd, timeout=None):
+        adb_cmd = "adb -s %s %s" % (self._serial, cmd)
+        print(adb_cmd)
 
-    def runShellCommand(self, cmd):
-        return self.runAdbCommand("shell " + cmd)
+        adb_process = subprocess.Popen(args=adb_cmd.split(), bufsize=-1, stderr=PIPE, stdout=PIPE)
+        (out, err) = adb_process.communicate(timeout=timeout)
+        return out.decode('utf-8').strip(), err.decode('utf-8').strip()
 
-    def waitForAdbDevice(self):
-        print "waitForAdbDevice"
-        os.system("adb -s %s wait-for-device" %self._adbDevice)
+    def run_shell_command(self, cmd):
+        return self.run_adb_command("shell %s" % cmd)
 
-    def waitForBootComplete(self, timeout = 240):
+    def wait_for_device(self, timeout=30):
+        return self.run_adb_command('wait-for-device', timeout)
+
+    def wait_for_prop(self, key, value, timeout=30):
         boot_complete = False
         attempts = 0
-        wait_period = 5
+        wait_period = 1
         while not boot_complete and (attempts*wait_period) < timeout:
-            (output, err) = self.runShellCommand("getprop dev.bootcomplete")
-            output = output.strip()
-            if output == "1":
+            (out, err) = self.run_shell_command("getprop %s" % key)
+            if out == value:
                 boot_complete = True
             else:
                 time.sleep(wait_period)
                 attempts += 1
         if not boot_complete:
-            print "***boot not complete within timeout. will proceed to the next step"
+            print("%s not set to %s within timeout!" % (key, value))
         return boot_complete
 
-    def installApk(self, apkPath):
-        (out, err) = self.runAdbCommand("install -r -d -g " + apkPath)
-        result = err.split()
-        return (out, err, "Success" in result)
+    def wait_for_service(self, name, timeout=30):
+        service_found = False
+        attempts = 0
+        wait_period = 1
+        while not service_found and (attempts*wait_period) < timeout:
+            (output, err) = self.run_shell_command("service check %s" % name)
+            if 'not found' not in output:
+                service_found = True
+            else:
+                time.sleep(wait_period)
+                attempts += 1
+        if not service_found:
+            print("Service '%s' not found within timeout!" % name)
+        return service_found
 
-    def uninstallApk(self, package):
-        (out, err) = self.runAdbCommand("uninstall " + package)
-        result = err.split()
+    def wait_for_boot_complete(self, timeout=60):
+        return self.wait_for_prop('dev.bootcomplete', '1', timeout)
+
+    def install_apk(self, apk_path):
+        self.wait_for_service('package')
+        (out, err) = self.run_adb_command("install -r -d -g %s" % apk_path)
+        result = out.split()
+        return out, err, "Success" in result
+
+    def uninstall_package(self, package):
+        self.wait_for_service('package')
+        (out, err) = self.run_adb_command("uninstall %s" % package)
+        result = out.split()
         return "Success" in result
 
-    def runInstrumentationTest(self, option):
-        return self.runShellCommand("am instrument -w --no-window-animation " + option)
+    def run_instrumentation_test(self, option):
+        self.wait_for_service('activity')
+        return self.run_shell_command("am instrument -w --no-window-animation %s" % option)
 
-    def isProcessAlive(self, processName):
-        (out, err) = self.runShellCommand("ps")
+    def is_process_alive(self, process_name):
+        (out, err) = self.run_shell_command("ps")
         names = out.split()
         # very lazy implementation as it does not filter out things like uid
         # should work mostly unless processName is too simple to overlap with
         # uid. So only use name like com.android.xyz
-        return processName in names
+        return process_name in names
 
-    def getVersionSdkInt(self):
-        return int(self.runShellCommand("getprop ro.build.version.sdk")[0])
+    def get_version_sdk(self):
+        return int(self.run_shell_command("getprop ro.build.version.sdk")[0])
 
-    def getVersionCodename(self):
-        return self.runShellCommand("getprop ro.build.version.codename")[0].strip()
+    def get_version_codename(self):
+        return self.run_shell_command("getprop ro.build.version.codename")[0].strip()
 
-    def getDensity(self):
-        if "emulator" in self._adbDevice:
-          return int(self.runShellCommand("getprop qemu.sf.lcd_density")[0])
+    def get_density(self):
+        if "emulator" in self._serial:
+            return int(self.run_shell_command("getprop qemu.sf.lcd_density")[0])
         else:
-          return int(self.runShellCommand("getprop ro.sf.lcd_density")[0])
+            return int(self.run_shell_command("getprop ro.sf.lcd_density")[0])
 
-    def getSdkLevel(self):
-        return int(self.runShellCommand("getprop ro.build.version.sdk")[0])
+    def get_orientation(self):
+        return int(self.run_shell_command("dumpsys | grep SurfaceOrientation")[0].split()[1])
 
-    def getOrientation(self):
-        return int(self.runShellCommand("dumpsys | grep SurfaceOrientation")[0].split()[1])
 
-    # Running dumpsys on the emulator currently yields a SIGSEGV, so don't do it.
-    #
-    #def getHWType(self):
-    #    (output, err) = self.runShellCommand("dumpsys | grep android.hardware.type")
-    #    output = output.strip()
-    #    return output
-
-def runAdbDevices():
+def enumerate_android_devices(require_prefix=''):
     devices = subprocess.check_output(["adb", "devices"])
-    devices = devices.split('\n')[1:]
+    if not devices:
+        return []
 
-    deviceSerial = []
+    devices = devices.decode('UTF-8').split('\n')[1:]
+    device_list = []
 
     for device in devices:
-        if device is not "":
+        if device is not "" and device.startswith(require_prefix):
             info = device.split('\t')
             if info[1] == "device":
-                deviceSerial.append(info[0])
+                device_list.append(info[0])
 
-    return deviceSerial
-
-if __name__ == '__main__':
-    main(sys.argv)
+    return device_list
diff --git a/hostsidetests/theme/avd.py b/hostsidetests/theme/avd.py
new file mode 100644
index 0000000..4c444a21
--- /dev/null
+++ b/hostsidetests/theme/avd.py
@@ -0,0 +1,81 @@
+#!/usr/bin/python3
+#
+# Copyright (C) 2017 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.
+#
+
+import functools
+import math
+import socket
+import subprocess
+import sys
+import tempfile
+
+from android_device import *
+
+
+def find_free_port():
+    s = socket.socket()
+    s.bind(('', 0))
+    return int(s.getsockname()[1])
+
+
+class AVD(object):
+    def __init__(self, name, emu_path):
+        self._name = name
+        self._emu_path = emu_path
+        self._opts = ''
+        self._adb_name = None
+        self._emu_proc = None
+
+    def start(self):
+        if self._emu_proc:
+            raise Exception('Emulator already running')
+
+        port_adb = find_free_port()
+        port_tty = find_free_port()
+        # -no-window might be useful here
+        emu_cmd = "%s -avd %s %s-ports %d,%d" \
+                  % (self._emu_path, self._name, self._opts, port_adb, port_tty)
+        print(emu_cmd)
+
+        emu_proc = subprocess.Popen(emu_cmd.split(" "), bufsize=-1, stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+
+        # The emulator ought to be starting now.
+        self._adb_name = "emulator-%d" % (port_tty - 1)
+        self._emu_proc = emu_proc
+
+    def stop(self):
+        if not self._emu_proc:
+            raise Exception('Emulator not currently running')
+        self._emu_proc.kill()
+        (out, err) = self._emu_proc.communicate()
+        self._emu_proc = None
+        return out, err
+
+    def get_serial(self):
+        if not self._emu_proc:
+            raise Exception('Emulator not currently running')
+        return self._adb_name
+
+    def get_device(self):
+        if not self._emu_proc:
+            raise Exception('Emulator not currently running')
+        return AndroidDevice(self._adb_name)
+
+    def configure_screen(self, density, width_dp, height_dp):
+        width_px = int(math.ceil(width_dp * density / 1600) * 10)
+        height_px = int(math.ceil(height_dp * density / 1600) * 10)
+        self._opts = "-prop qemu.sf.lcd_density=%d -skin %dx%d " % (density, width_px, height_px)
diff --git a/hostsidetests/theme/generate_images.py b/hostsidetests/theme/generate_images.py
new file mode 100755
index 0000000..125a0b0
--- /dev/null
+++ b/hostsidetests/theme/generate_images.py
@@ -0,0 +1,238 @@
+#!/usr/bin/python3
+#
+# 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.
+#
+
+import os
+import sys
+import tempfile
+import threading
+import time
+import traceback
+
+from android_device import *
+from avd import *
+from queue import Queue, Empty
+
+
+CTS_THEME_dict = {
+    120: "ldpi",
+    160: "mdpi",
+    213: "tvdpi",
+    240: "hdpi",
+    320: "xhdpi",
+    360: "360dpi",
+    420: "420dpi",
+    480: "xxhdpi",
+    560: "560dpi",
+    640: "xxxhdpi",
+}
+
+OUT_FILE = "/sdcard/cts-theme-assets.zip"
+
+
+class ParallelExecutor(threading.Thread):
+    def __init__(self, tasks, q):
+        threading.Thread.__init__(self)
+        self._q = q
+        self._tasks = tasks
+        self._setup = setup
+        self._result = 0
+
+    def run(self):
+        try:
+            while True:
+                config = q.get(block=True, timeout=2)
+                for t in self._tasks:
+                    try:
+                        if t(self._setup, config):
+                            self._result += 1
+                    except KeyboardInterrupt:
+                        raise
+                    except:
+                        print("Failed to execute thread:", sys.exc_info()[0])
+                        traceback.print_exc()
+                q.task_done()
+        except KeyboardInterrupt:
+            raise
+        except Empty:
+            pass
+
+    def get_result(self):
+        return self._result
+
+
+# pass a function with number of instances to be executed in parallel
+# each thread continues until config q is empty.
+def execute_parallel(tasks, setup, q, num_threads):
+    result = 0
+    threads = []
+    for i in range(num_threads):
+        t = ParallelExecutor(tasks, q)
+        t.start()
+        threads.append(t)
+    for t in threads:
+        t.join()
+        result += t.get_result()
+    return result
+
+
+def print_adb_result(device, out, err):
+    print("device: " + device)
+    if out is not None:
+        print("out:\n" + out)
+    if err is not None:
+        print("err:\n" + err)
+
+
+def do_capture(setup, device_serial):
+    (themeApkPath, out_path) = setup
+
+    device = AndroidDevice(device_serial)
+
+    version = device.get_version_codename()
+    if version == "REL":
+        version = str(device.get_version_sdk())
+
+    density = device.get_density()
+
+    if CTS_THEME_dict[density]:
+        density_bucket = CTS_THEME_dict[density]
+    else:
+        density_bucket = str(density) + "dpi"
+
+    out_file = os.path.join(out_path, os.path.join(version, "%s.zip" % density_bucket))
+
+    device.uninstall_package('android.theme.app')
+
+    (out, err, success) = device.install_apk(themeApkPath)
+    if not success:
+        print("Failed to install APK on " + device_serial)
+        print_adb_result(device_serial, out, err)
+        return False
+
+    print("Generating images on " + device_serial + "...")
+    try:
+        (out, err) = device.run_instrumentation_test(
+            "android.theme.app/android.support.test.runner.AndroidJUnitRunner")
+    except KeyboardInterrupt:
+        raise
+    except:
+        (out, err) = device.run_instrumentation_test(
+            "android.theme.app/android.test.InstrumentationTestRunner")
+
+    # Detect test failure and abort.
+    if "FAILURES!!!" in out.split():
+        print_adb_result(device_serial, out, err)
+        return False
+
+    # Make sure that the run is complete by checking the process itself
+    print("Waiting for " + device_serial + "...")
+    wait_time = 0
+    while device.is_process_alive("android.theme.app"):
+        time.sleep(1)
+        wait_time = wait_time + 1
+        if wait_time > 180:
+            print("Timed out")
+            break
+
+    time.sleep(10)
+
+    print("Pulling images from " + device_serial + " to " + out_file)
+    device.run_adb_command("pull " + OUT_FILE + " " + out_file)
+    device.run_adb_command("shell rm -rf " + OUT_FILE)
+    return True
+
+
+def get_emulator_path():
+    if 'ANDROID_SDK_ROOT' not in os.environ:
+        print('Environment variable ANDROID_SDK_ROOT must point to your Android SDK root.')
+        sys.exit(1)
+
+    sdk_path = os.environ['ANDROID_SDK_ROOT']
+    if not os.path.isdir(sdk_path):
+        print("Failed to find Android SDK at ANDROID_SDK_ROOT: %s" % sdk_path)
+        sys.exit(1)
+
+    emu_path = os.path.join(os.path.join(sdk_path, 'tools'), 'emulator')
+    if not os.path.isfile(emu_path):
+        print("Failed to find emulator within ANDROID_SDK_ROOT: %s" % sdk_path)
+        sys.exit(1)
+
+    return emu_path
+
+
+def start_emulator(name, density):
+    emu_path = get_emulator_path()
+
+    # Start emulator for 560dpi, normal screen size.
+    test_avd = AVD(name, emu_path)
+    test_avd.configure_screen(density, 360, 640)
+    test_avd.start()
+    try:
+        test_avd_device = test_avd.get_device()
+        test_avd_device.wait_for_device()
+        test_avd_device.wait_for_boot_complete()
+        return test_avd
+    except:
+        test_avd.stop()
+        return None
+
+
+def main(argv):
+    if 'ANDROID_BUILD_TOP' not in os.environ or 'ANDROID_HOST_OUT' not in os.environ:
+        print('Missing environment variables. Did you run build/envsetup.sh and lunch?')
+        sys.exit(1)
+
+    theme_apk = os.path.join(os.environ['ANDROID_HOST_OUT'],
+                             'cts/android-cts/testcases/CtsThemeDeviceApp.apk')
+    if not os.path.isfile(theme_apk):
+        print('Couldn\'t find test APK. Did you run make cts?')
+        sys.exit(1)
+
+    out_path = os.path.join(os.environ['ANDROID_BUILD_TOP'],
+                            'cts/hostsidetests/theme/assets')
+    os.system("mkdir -p %s" % out_path)
+
+    if len(argv) is 2:
+        for density in CTS_THEME_dict.keys():
+            emulator = start_emulator(argv[1], density)
+            result = do_capture(setup=(theme_apk, out_path), device_serial=emulator.get_serial())
+            emulator.stop()
+            if result:
+                print("Generated reference images for %ddpi" % density)
+            else:
+                print("Failed to generate reference images for %ddpi" % density)
+                break
+    else:
+        tasks = [do_capture]
+        setup = (theme_apk, out_path)
+
+        devices = enumerate_android_devices('emulator')
+
+        device_queue = Queue()
+        for device in devices:
+            device_queue.put(device)
+
+        result = execute_parallel(tasks, setup, device_queue, len(devices))
+
+        if result > 0:
+            print('Generated reference images for %(count)d devices' % {"count": result})
+        else:
+            print('Failed to generate reference images')
+
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/hostsidetests/theme/generate_images.sh b/hostsidetests/theme/generate_images.sh
deleted file mode 100755
index 129efc8..0000000
--- a/hostsidetests/theme/generate_images.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/sh
-# 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.
-
-# This script is used to generate reference images for the CTS theme tests.
-# See the accompanying README file for more information.
-
-# retry <command> <tries> <message> <delay>
-function retry {
-  RETRY="0"
-  while true; do
-    if (("$RETRY" >= "$2")); then
-      echo $OUTPUT
-      exit
-    fi
-
-    OUTPUT=`$1 |& grep error`
-
-    if [ -z "$OUTPUT" ]; then
-      break
-    fi
-
-    echo $3
-    sleep $4
-    RETRY=$[$RETRY + 1]
-  done
-}
-
-themeApkPath="$ANDROID_HOST_OUT/cts/android-cts/testcases/CtsThemeDeviceApp.apk"
-outDir="$ANDROID_BUILD_TOP/cts/hostsidetests/theme/assets"
-exe="$ANDROID_BUILD_TOP/cts/hostsidetests/theme/run_theme_capture_device.py"
-
-if [ -z "$ANDROID_BUILD_TOP" ]; then
-  echo "Missing environment variables. Did you run build/envsetup.sh and lunch?"
-  exit
-fi
-
-if [ ! -e "$themeApkPath" ]; then
-  echo "Couldn't find test APK. Did you run make cts?"
-  exit
-fi
-
-adb devices
-python $exe $themeApkPath $outDir
diff --git a/hostsidetests/theme/run_theme_capture_device.py b/hostsidetests/theme/run_theme_capture_device.py
deleted file mode 100755
index d37864f..0000000
--- a/hostsidetests/theme/run_theme_capture_device.py
+++ /dev/null
@@ -1,180 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-#
-
-import os
-import sys
-import threading
-import time
-import traceback
-import Queue
-sys.path.append(sys.path[0])
-from android_device import *
-
-CTS_THEME_dict = {
-    120 : "ldpi",
-    160 : "mdpi",
-    240 : "hdpi",
-    320 : "xhdpi",
-    480 : "xxhdpi",
-    640 : "xxxhdpi",
-}
-
-OUT_FILE = "/sdcard/cts-theme-assets.zip"
-
-# pass a function with number of instances to be executed in parallel
-# each thread continues until config q is empty.
-def executeParallel(tasks, setup, q, numberThreads):
-    class ParallelExecutor(threading.Thread):
-        def __init__(self, tasks, q):
-            threading.Thread.__init__(self)
-            self._q = q
-            self._tasks = tasks
-            self._setup = setup
-            self._result = 0
-
-        def run(self):
-            try:
-                while True:
-                    config = q.get(block=True, timeout=2)
-                    for t in self._tasks:
-                        try:
-                            if t(self._setup, config):
-                                self._result += 1
-                        except KeyboardInterrupt:
-                            raise
-                        except:
-                            print "Failed to execute thread:", sys.exc_info()[0]
-                            traceback.print_exc()
-                    q.task_done()
-            except KeyboardInterrupt:
-                raise
-            except Queue.Empty:
-                pass
-
-        def getResult(self):
-            return self._result
-
-    result = 0;
-    threads = []
-    for i in range(numberThreads):
-        t = ParallelExecutor(tasks, q)
-        t.start()
-        threads.append(t)
-    for t in threads:
-        t.join()
-        result += t.getResult()
-    return result;
-
-def printAdbResult(device, out, err):
-    print "device: " + device
-    if out is not None:
-        print "out:\n" + out
-    if err is not None:
-        print "err:\n" + err
-
-def getResDir(outPath, resName):
-    resDir = outPath + "/" + resName
-    return resDir
-
-def doCapturing(setup, deviceSerial):
-    (themeApkPath, outPath) = setup
-
-    print "Found device: " + deviceSerial
-    device = androidDevice(deviceSerial)
-
-    version = device.getVersionCodename()
-    if version == "REL":
-        version = str(device.getVersionSdkInt())
-
-    density = device.getDensity()
-
-    # Reference images generated for tv should not be categorized by density
-    # rather by tv type. This is because TV uses leanback-specific material
-    # themes.
-    if CTS_THEME_dict.has_key(density):
-        densityBucket = CTS_THEME_dict[density]
-    else:
-        densityBucket = str(density) + "dpi"
-
-    resName = os.path.join(version, densityBucket)
-
-    device.uninstallApk("android.theme.app")
-
-    (out, err, success) = device.installApk(themeApkPath)
-    if not success:
-        print "Failed to install APK on " + deviceSerial
-        printAdbResult(deviceSerial, out, err)
-        return False
-
-    print "Generating images on " + deviceSerial + "..."
-    try:
-        (out, err) = device.runInstrumentationTest("android.theme.app/android.support.test.runner.AndroidJUnitRunner")
-    except KeyboardInterrupt:
-        raise
-    except:
-        (out, err) = device.runInstrumentationTest("android.theme.app/android.test.InstrumentationTestRunner")
-
-    # Detect test failure and abort.
-    if "FAILURES!!!" in out.split():
-        printAdbResult(deviceSerial, out, err)
-        return False
-
-    # Make sure that the run is complete by checking the process itself
-    print "Waiting for " + deviceSerial + "..."
-    waitTime = 0
-    while device.isProcessAlive("android.theme.app"):
-        time.sleep(1)
-        waitTime = waitTime + 1
-        if waitTime > 180:
-            print "Timed out"
-            break
-
-    time.sleep(10)
-    resDir = getResDir(outPath, resName)
-
-    print "Pulling images from " + deviceSerial + " to " + resDir + ".zip"
-    device.runAdbCommand("pull " + OUT_FILE + " " + resDir + ".zip")
-    device.runAdbCommand("shell rm -rf " + OUT_FILE)
-    return True
-
-def main(argv):
-    if len(argv) < 3:
-        print "run_theme_capture_device.py themeApkPath outDir"
-        sys.exit(1)
-    themeApkPath = argv[1]
-    outPath = os.path.abspath(argv[2])
-    os.system("mkdir -p " + outPath)
-
-    tasks = []
-    tasks.append(doCapturing)
-
-    devices = runAdbDevices();
-    numberThreads = len(devices)
-
-    configQ = Queue.Queue()
-    for device in devices:
-        configQ.put(device)
-    setup = (themeApkPath, outPath)
-    result = executeParallel(tasks, setup, configQ, numberThreads)
-
-    if result > 0:
-        print 'Generated reference images for %(count)d devices' % {"count": result}
-    else:
-        print 'Failed to generate reference images'
-
-if __name__ == '__main__':
-    main(sys.argv)