1#!/usr/bin/python3
2#
3# Copyright (C) 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import os
19import sys
20import tempfile
21import threading
22import time
23import traceback
24
25from android_device import *
26from avd import *
27from queue import Queue, Empty
28
29
30# This dict should contain one entry for every density listed in CDD 7.1.1.3.
31CTS_THEME_dict = {
32    120: "ldpi",
33    160: "mdpi",
34    213: "tvdpi",
35    240: "hdpi",
36    260: "260dpi",
37    280: "280dpi",
38    300: "300dpi",
39    320: "xhdpi",
40    340: "340dpi",
41    360: "360dpi",
42    400: "400dpi",
43    420: "420dpi",
44    440: "440dpi",
45    480: "xxhdpi",
46    560: "560dpi",
47    640: "xxxhdpi",
48}
49
50OUT_FILE = "/sdcard/cts-theme-assets.zip"
51
52
53class ParallelExecutor(threading.Thread):
54    def __init__(self, tasks, setup, q):
55        threading.Thread.__init__(self)
56        self._q = q
57        self._tasks = tasks
58        self._setup = setup
59        self._result = 0
60
61    def run(self):
62        try:
63            while True:
64                config = self._q.get(block=True, timeout=2)
65                for t in self._tasks:
66                    try:
67                        if t(self._setup, config):
68                            self._result += 1
69                    except KeyboardInterrupt:
70                        raise
71                    except:
72                        print("Failed to execute thread:", sys.exc_info()[0])
73                        traceback.print_exc()
74                self._q.task_done()
75        except KeyboardInterrupt:
76            raise
77        except Empty:
78            pass
79
80    def get_result(self):
81        return self._result
82
83
84# pass a function with number of instances to be executed in parallel
85# each thread continues until config q is empty.
86def execute_parallel(tasks, setup, q, num_threads):
87    result = 0
88    threads = []
89    for i in range(num_threads):
90        t = ParallelExecutor(tasks, setup, q)
91        t.start()
92        threads.append(t)
93    for t in threads:
94        t.join()
95        result += t.get_result()
96    return result
97
98
99def print_adb_result(device, out, err):
100    print("device: " + device)
101    if out is not None:
102        print("out:\n" + out)
103    if err is not None:
104        print("err:\n" + err)
105
106
107def do_capture(setup, device_serial):
108    (themeApkPath, out_path) = setup
109
110    device = AndroidDevice(device_serial)
111
112    version = device.get_version_codename()
113    if version == "REL":
114        version = str(device.get_version_sdk())
115
116    density = device.get_density()
117
118    if CTS_THEME_dict[density]:
119        density_bucket = CTS_THEME_dict[density]
120    else:
121        density_bucket = str(density) + "dpi"
122
123    out_file = os.path.join(out_path, os.path.join(version, "%s.zip" % density_bucket))
124
125    device.uninstall_package('android.theme.app')
126
127    (out, err, success) = device.install_apk(themeApkPath)
128    if not success:
129        print("Failed to install APK on " + device_serial)
130        print_adb_result(device_serial, out, err)
131        return False
132
133    print("Generating images on " + device_serial + "...")
134    try:
135        (out, err) = device.run_instrumentation_test(
136            "android.theme.app/android.support.test.runner.AndroidJUnitRunner")
137    except KeyboardInterrupt:
138        raise
139    except:
140        (out, err) = device.run_instrumentation_test(
141            "android.theme.app/android.test.InstrumentationTestRunner")
142
143    # Detect test failure and abort.
144    if "FAILURES!!!" in out.split():
145        print_adb_result(device_serial, out, err)
146        return False
147
148    # Make sure that the run is complete by checking the process itself
149    print("Waiting for " + device_serial + "...")
150    wait_time = 0
151    while device.is_process_alive("android.theme.app"):
152        time.sleep(1)
153        wait_time = wait_time + 1
154        if wait_time > 180:
155            print("Timed out")
156            break
157
158    time.sleep(10)
159
160    print("Pulling images from " + device_serial + " to " + out_file)
161    device.run_adb_command("pull " + OUT_FILE + " " + out_file)
162    device.run_adb_command("shell rm -rf " + OUT_FILE)
163    return True
164
165
166def get_emulator_path():
167    if 'ANDROID_SDK_ROOT' not in os.environ:
168        print('Environment variable ANDROID_SDK_ROOT must point to your Android SDK root.')
169        sys.exit(1)
170
171    sdk_path = os.environ['ANDROID_SDK_ROOT']
172    if not os.path.isdir(sdk_path):
173        print("Failed to find Android SDK at ANDROID_SDK_ROOT: %s" % sdk_path)
174        sys.exit(1)
175
176    emu_path = os.path.join(os.path.join(sdk_path, 'tools'), 'emulator')
177    if not os.path.isfile(emu_path):
178        print("Failed to find emulator within ANDROID_SDK_ROOT: %s" % sdk_path)
179        sys.exit(1)
180
181    return emu_path
182
183
184def start_emulator(name, density):
185    if name == "local":
186        emu_path = ""
187    else:
188        emu_path = get_emulator_path()
189
190    # Start emulator for 560dpi, normal screen size.
191    test_avd = AVD(name, emu_path)
192    test_avd.configure_screen(density, 360, 640)
193    test_avd.start()
194    try:
195        test_avd_device = test_avd.get_device()
196        test_avd_device.wait_for_device()
197        test_avd_device.wait_for_boot_complete()
198        return test_avd
199    except:
200        test_avd.stop()
201        return None
202
203
204def main(argv):
205    if 'ANDROID_BUILD_TOP' not in os.environ or 'ANDROID_HOST_OUT' not in os.environ:
206        print('Missing environment variables. Did you run build/envsetup.sh and lunch?')
207        sys.exit(1)
208
209    theme_apk = os.path.join(os.environ['ANDROID_HOST_OUT'],
210                             'cts/android-cts/testcases/CtsThemeDeviceApp.apk')
211    if not os.path.isfile(theme_apk):
212        print('Couldn\'t find test APK. Did you run make cts?')
213        sys.exit(1)
214
215    out_path = os.path.join(os.environ['ANDROID_BUILD_TOP'],
216                            'cts/hostsidetests/theme/assets')
217    os.system("mkdir -p %s" % out_path)
218
219    if len(argv) is 2:
220        for density in CTS_THEME_dict.keys():
221            emulator = start_emulator(argv[1], density)
222            result = do_capture(setup=(theme_apk, out_path), device_serial=emulator.get_serial())
223            emulator.stop()
224            if result:
225                print("Generated reference images for %ddpi" % density)
226            else:
227                print("Failed to generate reference images for %ddpi" % density)
228                break
229    else:
230        tasks = [do_capture]
231        setup = (theme_apk, out_path)
232
233        devices = enumerate_android_devices()
234
235        if len(devices) > 0:
236            device_queue = Queue()
237            for device in devices:
238                device_queue.put(device)
239
240            result = execute_parallel(tasks, setup, device_queue, len(devices))
241
242            if result > 0:
243                print('Generated reference images for %(count)d devices' % {"count": result})
244            else:
245                print('Failed to generate reference images')
246        else:
247            print('No devices found')
248
249
250if __name__ == '__main__':
251    main(sys.argv)
252