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