Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
Christoffer Quist Adamsen | 21f7f35 | 2022-05-30 19:52:43 +0200 | [diff] [blame] | 6 | import argparse |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 7 | import os |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 8 | import subprocess |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 9 | import sys |
| 10 | import threading |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 11 | import time |
| 12 | |
Christoffer Quist Adamsen | 21f7f35 | 2022-05-30 19:52:43 +0200 | [diff] [blame] | 13 | from enum import Enum |
| 14 | |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 15 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| 16 | |
Christoffer Quist Adamsen | 1c07af9 | 2022-08-11 13:46:25 +0200 | [diff] [blame^] | 17 | import profile_utils |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 18 | import utils |
| 19 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 20 | DEVNULL=subprocess.DEVNULL |
| 21 | |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 22 | class ProcessReader(threading.Thread): |
| 23 | |
| 24 | def __init__(self, process): |
| 25 | threading.Thread.__init__(self) |
| 26 | self.lines = [] |
| 27 | self.process = process |
| 28 | |
| 29 | def run(self): |
| 30 | for line in self.process.stdout: |
| 31 | line = line.decode('utf-8').strip() |
| 32 | self.lines.append(line) |
| 33 | |
| 34 | def stop(self): |
| 35 | self.process.kill() |
| 36 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 37 | class ScreenState(Enum): |
| 38 | OFF_LOCKED = 1, |
| 39 | OFF_UNLOCKED = 2 |
| 40 | ON_LOCKED = 3 |
| 41 | ON_UNLOCKED = 4 |
| 42 | |
| 43 | def is_off(self): |
| 44 | return self == ScreenState.OFF_LOCKED or self == ScreenState.OFF_UNLOCKED |
| 45 | |
| 46 | def is_on(self): |
| 47 | return self == ScreenState.ON_LOCKED or self == ScreenState.ON_UNLOCKED |
| 48 | |
| 49 | def is_on_and_locked(self): |
| 50 | return self == ScreenState.ON_LOCKED |
| 51 | |
| 52 | def is_on_and_unlocked(self): |
| 53 | return self == ScreenState.ON_UNLOCKED |
| 54 | |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 55 | def broadcast(action, component, device_id=None): |
| 56 | print('Sending broadcast %s' % action) |
| 57 | cmd = create_adb_cmd('shell am broadcast -a %s %s' % (action, component), device_id) |
| 58 | return subprocess.check_output(cmd).decode('utf-8').strip().splitlines() |
| 59 | |
Christoffer Quist Adamsen | dab3b20 | 2022-08-15 09:36:28 +0200 | [diff] [blame] | 60 | def build_apks_from_bundle(bundle, output, overwrite=False): |
Christoffer Quist Adamsen | 4d9fc51 | 2022-08-11 19:59:44 +0200 | [diff] [blame] | 61 | print('Building %s' % bundle) |
| 62 | cmd = [ |
| 63 | 'java', '-jar', utils.BUNDLETOOL_JAR, |
| 64 | 'build-apks', |
| 65 | '--bundle=%s' % bundle, |
| 66 | '--output=%s' % output] |
Christoffer Quist Adamsen | dab3b20 | 2022-08-15 09:36:28 +0200 | [diff] [blame] | 67 | if overwrite: |
| 68 | cmd.append('--overwrite') |
Christoffer Quist Adamsen | 4d9fc51 | 2022-08-11 19:59:44 +0200 | [diff] [blame] | 69 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 70 | |
Christoffer Quist Adamsen | ed805c5 | 2022-08-08 14:46:29 +0200 | [diff] [blame] | 71 | def capture_screen(target, device_id=None): |
| 72 | print('Taking screenshot to %s' % target) |
| 73 | tmp = '/sdcard/screencap.png' |
| 74 | cmd = create_adb_cmd('shell screencap -p %s' % tmp, device_id) |
| 75 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 76 | pull(tmp, target, device_id) |
| 77 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 78 | def create_adb_cmd(arguments, device_id=None): |
| 79 | assert isinstance(arguments, list) or isinstance(arguments, str) |
| 80 | cmd = ['adb'] |
| 81 | if device_id is not None: |
| 82 | cmd.append('-s') |
| 83 | cmd.append(device_id) |
| 84 | cmd.extend(arguments if isinstance(arguments, list) else arguments.split(' ')) |
| 85 | return cmd |
| 86 | |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 87 | def capture_app_profile_data(app_id, device_id=None): |
| 88 | cmd = create_adb_cmd( |
| 89 | 'shell killall -s SIGUSR1 %s' % app_id, device_id) |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 90 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 91 | time.sleep(5) |
| 92 | |
| 93 | def check_app_has_profile_data(app_id, device_id=None): |
| 94 | profile_path = get_profile_path(app_id) |
| 95 | cmd = create_adb_cmd( |
| 96 | 'shell du /data/misc/profiles/cur/0/%s/primary.prof' % app_id, |
| 97 | device_id) |
| 98 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 99 | size_str = stdout[:stdout.index('\t')] |
| 100 | assert size_str.isdigit() |
| 101 | size = int(size_str) |
| 102 | if size == 4: |
| 103 | raise ValueError('Expected size of profile at %s to be > 4K' % profile_path) |
| 104 | |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 105 | def clear_logcat(device_id=None): |
| 106 | cmd = create_adb_cmd('logcat -c', device_id) |
| 107 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 108 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 109 | def clear_profile_data(app_id, device_id=None): |
| 110 | cmd = create_adb_cmd( |
| 111 | 'shell cmd package compile --reset %s' % app_id, device_id) |
| 112 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 113 | |
| 114 | def drop_caches(device_id=None): |
| 115 | cmd = create_adb_cmd( |
| 116 | ['shell', 'echo 3 > /proc/sys/vm/drop_caches'], device_id) |
| 117 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 118 | |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 119 | def ensure_screen_on(device_id=None): |
| 120 | if get_screen_state(device_id).is_off(): |
| 121 | toggle_screen(device_id) |
Christoffer Quist Adamsen | 7a0014f | 2022-05-19 08:43:00 +0200 | [diff] [blame] | 122 | assert get_screen_state(device_id).is_on() |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 123 | |
| 124 | def ensure_screen_off(device_id=None): |
| 125 | if get_screen_state(device_id).is_on(): |
| 126 | toggle_screen(device_id) |
Christoffer Quist Adamsen | 7a0014f | 2022-05-19 08:43:00 +0200 | [diff] [blame] | 127 | assert get_screen_state(device_id).is_off() |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 128 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 129 | def force_compilation(app_id, device_id=None): |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 130 | print('Applying AOT (full)') |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 131 | cmd = create_adb_cmd( |
| 132 | 'shell cmd package compile -m speed -f %s' % app_id, device_id) |
| 133 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 134 | |
| 135 | def force_profile_compilation(app_id, device_id=None): |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 136 | print('Applying AOT (profile)') |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 137 | cmd = create_adb_cmd( |
| 138 | 'shell cmd package compile -m speed-profile -f %s' % app_id, device_id) |
| 139 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 140 | |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 141 | def get_apk_path(app_id, device_id=None): |
| 142 | cmd = create_adb_cmd('shell pm path %s' % app_id, device_id) |
| 143 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 144 | if not stdout.startswith('package:'): |
| 145 | raise ValueError( |
| 146 | 'Expected stdout to start with "package:", was: %s' % stdout) |
| 147 | apk_path = stdout[len('package:'):] |
| 148 | if not apk_path.endswith('.apk'): |
| 149 | raise ValueError( |
| 150 | 'Expected stdout to end with ".apk", was: %s' % stdout) |
| 151 | return apk_path |
| 152 | |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 153 | def get_profile_data(app_id, device_id=None): |
| 154 | with utils.TempDir() as temp: |
| 155 | source = get_profile_path(app_id) |
| 156 | target = os.path.join(temp, 'primary.prof') |
Christoffer Quist Adamsen | ed805c5 | 2022-08-08 14:46:29 +0200 | [diff] [blame] | 157 | pull(source, target, device_id) |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 158 | with open(target, 'rb') as f: |
| 159 | return f.read() |
| 160 | |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 161 | def get_profile_path(app_id): |
| 162 | return '/data/misc/profiles/cur/0/%s/primary.prof' % app_id |
| 163 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 164 | def get_minor_major_page_faults(app_id, device_id=None): |
| 165 | pid = get_pid(app_id, device_id) |
Christoffer Quist Adamsen | 2d5d963 | 2022-04-06 14:48:30 +0200 | [diff] [blame] | 166 | cmd = create_adb_cmd('shell ps -p %i -o MINFL,MAJFL' % pid, device_id) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 167 | stdout = subprocess.check_output(cmd).decode('utf-8') |
| 168 | lines_it = iter(stdout.splitlines()) |
| 169 | first_line = next(lines_it) |
| 170 | assert first_line == ' MINFL MAJFL' |
| 171 | second_line = next(lines_it) |
| 172 | minfl, majfl = second_line.split() |
| 173 | assert minfl.isdigit() |
| 174 | assert majfl.isdigit() |
| 175 | return (int(minfl), int(majfl)) |
| 176 | |
| 177 | def get_pid(app_id, device_id=None): |
| 178 | cmd = create_adb_cmd('shell pidof %s' % app_id, device_id) |
| 179 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 180 | assert stdout.isdigit() |
| 181 | pid = int(stdout) |
| 182 | return pid |
| 183 | |
| 184 | def get_screen_state(device_id=None): |
| 185 | cmd = create_adb_cmd('shell dumpsys nfc', device_id) |
| 186 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 187 | screen_state_value = None |
| 188 | for line in stdout.splitlines(): |
| 189 | if line.startswith('mScreenState='): |
| 190 | value_start_index = len('mScreenState=') |
| 191 | screen_state_value=line[value_start_index:] |
| 192 | if screen_state_value is None: |
| 193 | raise ValueError('Expected to find mScreenState in: adb shell dumpsys nfc') |
| 194 | if not hasattr(ScreenState, screen_state_value): |
| 195 | raise ValueError( |
| 196 | 'Expected mScreenState to be a value of ScreenState, was: %s' |
| 197 | % screen_state_value) |
| 198 | return ScreenState[screen_state_value] |
| 199 | |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 200 | def get_classes_and_methods_from_app_profile(app_id, device_id=None): |
| 201 | apk_path = get_apk_path(app_id, device_id) |
| 202 | profile_path = get_profile_path(app_id) |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 203 | cmd = create_adb_cmd( |
| 204 | 'shell profman --dump-classes-and-methods' |
| 205 | ' --profile-file=%s --apk=%s --dex-location=%s' |
Christoffer Quist Adamsen | 2d5d963 | 2022-04-06 14:48:30 +0200 | [diff] [blame] | 206 | % (profile_path, apk_path, apk_path), device_id) |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 207 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 208 | lines = stdout.splitlines() |
Christoffer Quist Adamsen | 1c07af9 | 2022-08-11 13:46:25 +0200 | [diff] [blame^] | 209 | return profile_utils.parse_art_profile(lines) |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 210 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 211 | def get_screen_off_timeout(device_id=None): |
| 212 | cmd = create_adb_cmd( |
| 213 | 'shell settings get system screen_off_timeout', device_id) |
| 214 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 215 | assert stdout.isdigit() |
| 216 | screen_off_timeout = int(stdout) |
| 217 | return screen_off_timeout |
| 218 | |
| 219 | def install(apk, device_id=None): |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 220 | print('Installing %s' % apk) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 221 | cmd = create_adb_cmd('install %s' % apk, device_id) |
| 222 | stdout = subprocess.check_output(cmd).decode('utf-8') |
| 223 | assert 'Success' in stdout |
| 224 | |
Christoffer Quist Adamsen | 6e96550 | 2022-08-15 10:21:07 +0200 | [diff] [blame] | 225 | def install_apks(apks, device_id=None, max_attempts=3): |
Christoffer Quist Adamsen | 4d9fc51 | 2022-08-11 19:59:44 +0200 | [diff] [blame] | 226 | print('Installing %s' % apks) |
| 227 | cmd = [ |
| 228 | 'java', '-jar', utils.BUNDLETOOL_JAR, |
| 229 | 'install-apks', |
| 230 | '--apks=%s' % apks] |
| 231 | if device_id is not None: |
| 232 | cmd.append('--device-id=%s' % device_id) |
Christoffer Quist Adamsen | 6e96550 | 2022-08-15 10:21:07 +0200 | [diff] [blame] | 233 | for i in range(max_attempts): |
| 234 | process_result = subprocess.run(cmd, capture_output=True) |
| 235 | stdout = process_result.stdout.decode('utf-8') |
| 236 | stderr = process_result.stderr.decode('utf-8') |
| 237 | if process_result.returncode == 0: |
| 238 | return |
| 239 | print('Failed to install %s' % apks) |
| 240 | print('Stdout: %s' % stdout) |
| 241 | print('Stderr: %s' % stderr) |
| 242 | print('Retrying...') |
| 243 | raise Exception('Unable to install apks in %s attempts' % max_attempts) |
Christoffer Quist Adamsen | 4d9fc51 | 2022-08-11 19:59:44 +0200 | [diff] [blame] | 244 | |
| 245 | def install_bundle(bundle, device_id=None): |
| 246 | print('Installing %s' % bundle) |
| 247 | with utils.TempDir() as temp: |
| 248 | apks = os.path.join(temp, 'Bundle.apks') |
| 249 | build_apks_from_bundle(bundle, apks) |
| 250 | install_apks(apks, device_id) |
| 251 | |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 252 | def install_profile(app_id, device_id=None): |
| 253 | # This assumes that the profileinstaller library has been added to the app, |
| 254 | # https://developer.android.com/jetpack/androidx/releases/profileinstaller. |
| 255 | action = 'androidx.profileinstaller.action.INSTALL_PROFILE' |
| 256 | component = '%s/androidx.profileinstaller.ProfileInstallReceiver' % app_id |
| 257 | stdout = broadcast(action, component, device_id) |
| 258 | assert len(stdout) == 2 |
| 259 | assert stdout[0] == ('Broadcasting: Intent { act=%s flg=0x400000 cmp=%s }' % (action, component)) |
| 260 | assert stdout[1] == 'Broadcast completed: result=1', stdout[1] |
| 261 | stop_app(app_id, device_id) |
| 262 | force_profile_compilation(app_id, device_id) |
| 263 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 264 | def issue_key_event(key_event, device_id=None, sleep_in_seconds=1): |
| 265 | cmd = create_adb_cmd('shell input keyevent %s' % key_event, device_id) |
| 266 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 267 | assert len(stdout) == 0 |
| 268 | time.sleep(sleep_in_seconds) |
| 269 | |
Christoffer Quist Adamsen | ea09105 | 2022-03-16 13:28:20 +0100 | [diff] [blame] | 270 | def launch_activity( |
| 271 | app_id, activity, device_id=None, wait_for_activity_to_launch=False): |
| 272 | args = ['shell', 'am', 'start', '-n', '%s/%s' % (app_id, activity)] |
| 273 | if wait_for_activity_to_launch: |
| 274 | args.append('-W') |
| 275 | cmd = create_adb_cmd(args, device_id) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 276 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
Christoffer Quist Adamsen | 13e4313 | 2022-08-05 11:31:56 +0200 | [diff] [blame] | 277 | if activity.startswith(app_id): |
| 278 | expected_stdout = ( |
| 279 | 'Starting: Intent { cmp=%s/.%s }' % (app_id, activity[len(app_id)+1:])) |
| 280 | else: |
| 281 | expected_stdout = 'Starting: Intent { cmp=%s/%s }' % (app_id, activity) |
| 282 | assert stdout.startswith(expected_stdout), 'was %s, expected %s' % (stdout, expected_stdout) |
Christoffer Quist Adamsen | ea09105 | 2022-03-16 13:28:20 +0100 | [diff] [blame] | 283 | lines = stdout.splitlines() |
| 284 | result = {} |
| 285 | for line in lines: |
| 286 | if line.startswith('TotalTime: '): |
| 287 | total_time_str = line.removeprefix('TotalTime: ') |
| 288 | assert total_time_str.isdigit() |
| 289 | result['total_time'] = int(total_time_str) |
Christoffer Quist Adamsen | 6e96550 | 2022-08-15 10:21:07 +0200 | [diff] [blame] | 290 | assert not wait_for_activity_to_launch or 'total_time' in result, lines |
Christoffer Quist Adamsen | ea09105 | 2022-03-16 13:28:20 +0100 | [diff] [blame] | 291 | return result |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 292 | |
Christoffer Quist Adamsen | 1786a59 | 2022-04-21 13:13:04 +0200 | [diff] [blame] | 293 | def navigate_to_home_screen(device_id=None): |
| 294 | cmd = create_adb_cmd('shell input keyevent KEYCODE_HOME', device_id) |
| 295 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 296 | |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 297 | def prepare_for_interaction_with_device(device_id=None, device_pin=None): |
| 298 | # Increase screen off timeout to avoid device screen turns off. |
| 299 | twenty_four_hours_in_millis = 24 * 60 * 60 * 1000 |
| 300 | previous_screen_off_timeout = get_screen_off_timeout(device_id) |
| 301 | set_screen_off_timeout(twenty_four_hours_in_millis, device_id) |
| 302 | |
| 303 | # Unlock device. |
| 304 | unlock(device_id, device_pin) |
| 305 | |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 306 | teardown_options = { |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 307 | 'previous_screen_off_timeout': previous_screen_off_timeout |
| 308 | } |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 309 | return teardown_options |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 310 | |
Christoffer Quist Adamsen | ed805c5 | 2022-08-08 14:46:29 +0200 | [diff] [blame] | 311 | def pull(source, target, device_id=None): |
| 312 | cmd = create_adb_cmd('pull %s %s' % (source, target), device_id) |
| 313 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 314 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 315 | def root(device_id=None): |
| 316 | cmd = create_adb_cmd('root', device_id) |
| 317 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 318 | |
| 319 | def set_screen_off_timeout(screen_off_timeout_in_millis, device_id=None): |
| 320 | cmd = create_adb_cmd( |
| 321 | 'shell settings put system screen_off_timeout %i' |
| 322 | % screen_off_timeout_in_millis, |
| 323 | device_id) |
| 324 | stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| 325 | assert len(stdout) == 0 |
| 326 | |
Christoffer Quist Adamsen | c6b19de | 2022-04-21 13:07:53 +0200 | [diff] [blame] | 327 | def start_logcat(device_id=None, format=None, filter=None): |
| 328 | args = ['logcat'] |
| 329 | if format: |
| 330 | args.extend(['--format', format]) |
| 331 | if filter: |
| 332 | args.append(filter) |
| 333 | cmd = create_adb_cmd(args, device_id) |
| 334 | logcat_process = subprocess.Popen( |
| 335 | cmd, bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 336 | reader = ProcessReader(logcat_process) |
| 337 | reader.start() |
| 338 | return reader |
| 339 | |
| 340 | def stop_logcat(logcat_reader): |
| 341 | logcat_reader.stop() |
| 342 | logcat_reader.join() |
| 343 | return logcat_reader.lines |
| 344 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 345 | def stop_app(app_id, device_id=None): |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 346 | print('Shutting down %s' % app_id) |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 347 | cmd = create_adb_cmd('shell am force-stop %s' % app_id, device_id) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 348 | subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL) |
| 349 | |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 350 | def teardown_after_interaction_with_device(teardown_options, device_id=None): |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 351 | # Reset screen off timeout. |
| 352 | set_screen_off_timeout( |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 353 | teardown_options['previous_screen_off_timeout'], |
Christoffer Quist Adamsen | 2771c36 | 2022-03-14 15:36:43 +0100 | [diff] [blame] | 354 | device_id) |
| 355 | |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 356 | def toggle_screen(device_id=None): |
| 357 | issue_key_event('KEYCODE_POWER', device_id) |
| 358 | |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 359 | def uninstall(app_id, device_id=None): |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 360 | print('Uninstalling %s' % app_id) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 361 | cmd = create_adb_cmd('uninstall %s' % app_id, device_id) |
| 362 | process_result = subprocess.run(cmd, capture_output=True) |
| 363 | stdout = process_result.stdout.decode('utf-8') |
| 364 | stderr = process_result.stderr.decode('utf-8') |
| 365 | if process_result.returncode == 0: |
| 366 | assert 'Success' in stdout |
Christoffer Quist Adamsen | 2a87a6e | 2022-08-05 11:32:11 +0200 | [diff] [blame] | 367 | elif stdout.startswith('cmd: Failure calling service package: Broken pipe'): |
| 368 | assert app_id == 'com.google.android.youtube' |
| 369 | print('Waiting after broken pipe') |
| 370 | time.sleep(15) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 371 | else: |
| 372 | expected_error = ( |
| 373 | 'java.lang.IllegalArgumentException: Unknown package: %s' % app_id) |
Christoffer Quist Adamsen | 3f7e428 | 2022-04-21 13:12:31 +0200 | [diff] [blame] | 374 | assert 'Failure [DELETE_FAILED_INTERNAL_ERROR]' in stdout \ |
Christoffer Quist Adamsen | 13e4313 | 2022-08-05 11:31:56 +0200 | [diff] [blame] | 375 | or expected_error in stderr, \ |
| 376 | 'stdout: %s, stderr: %s' % (stdout, stderr) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 377 | |
| 378 | def unlock(device_id=None, device_pin=None): |
Christoffer Quist Adamsen | 1a459b7 | 2022-05-11 12:09:03 +0200 | [diff] [blame] | 379 | ensure_screen_on(device_id) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 380 | screen_state = get_screen_state(device_id) |
Christoffer Quist Adamsen | fad33a0 | 2022-03-14 14:45:09 +0100 | [diff] [blame] | 381 | assert screen_state.is_on(), 'was %s' % screen_state |
| 382 | if screen_state.is_on_and_locked(): |
| 383 | if device_pin is not None: |
| 384 | raise NotImplementedError('Device unlocking with pin not implemented') |
| 385 | issue_key_event('KEYCODE_MENU', device_id) |
| 386 | screen_state = get_screen_state(device_id) |
| 387 | assert screen_state.is_on_and_unlocked(), 'was %s' % screen_state |
Christoffer Quist Adamsen | 21f7f35 | 2022-05-30 19:52:43 +0200 | [diff] [blame] | 388 | |
| 389 | def parse_options(argv): |
| 390 | result = argparse.ArgumentParser(description='Run adb utils.') |
Christoffer Quist Adamsen | ed805c5 | 2022-08-08 14:46:29 +0200 | [diff] [blame] | 391 | result.add_argument('--capture-screen', |
| 392 | help='Capture screen to given file') |
Christoffer Quist Adamsen | 21f7f35 | 2022-05-30 19:52:43 +0200 | [diff] [blame] | 393 | result.add_argument('--device-id', |
| 394 | help='Device id (e.g., emulator-5554).') |
| 395 | result.add_argument('--device-pin', |
| 396 | help='Device pin code (e.g., 1234)') |
| 397 | result.add_argument('--ensure-screen-off', |
| 398 | help='Ensure screen off', |
| 399 | action='store_true', |
| 400 | default=False) |
| 401 | result.add_argument('--get-screen-state', |
| 402 | help='Get screen state', |
| 403 | action='store_true', |
| 404 | default=False) |
| 405 | result.add_argument('--unlock', |
| 406 | help='Unlock device', |
| 407 | action='store_true', |
| 408 | default=False) |
| 409 | options, args = result.parse_known_args(argv) |
| 410 | return options, args |
| 411 | |
| 412 | def main(argv): |
| 413 | (options, args) = parse_options(argv) |
Christoffer Quist Adamsen | ed805c5 | 2022-08-08 14:46:29 +0200 | [diff] [blame] | 414 | if options.capture_screen: |
| 415 | capture_screen(options.capture_screen, options.device_id) |
Christoffer Quist Adamsen | 21f7f35 | 2022-05-30 19:52:43 +0200 | [diff] [blame] | 416 | if options.ensure_screen_off: |
| 417 | ensure_screen_off(options.device_id) |
| 418 | elif options.get_screen_state: |
| 419 | print(get_screen_state(options.device_id)) |
| 420 | elif options.unlock: |
| 421 | unlock(options.device_id, options.device_pin) |
| 422 | |
| 423 | |
| 424 | if __name__ == '__main__': |
| 425 | sys.exit(main(sys.argv[1:])) |