blob: 13d4ee7359420c3a91345b032ad875b631d2dffc [file] [log] [blame]
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +01001#!/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 Adamsen21f7f352022-05-30 19:52:43 +02006import argparse
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +02007import os
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +01008import subprocess
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +02009import sys
10import threading
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010011import time
12
Christoffer Quist Adamsen21f7f352022-05-30 19:52:43 +020013from enum import Enum
14
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020015sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
16
Christoffer Quist Adamsen1c07af92022-08-11 13:46:25 +020017import profile_utils
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020018import utils
19
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020020DEVNULL = subprocess.DEVNULL
21
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010022
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020023class ProcessReader(threading.Thread):
24
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020025 def __init__(self, process):
26 threading.Thread.__init__(self)
27 self.lines = []
28 self.process = process
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020029
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020030 def run(self):
31 for line in self.process.stdout:
32 line = line.decode('utf-8').strip()
33 self.lines.append(line)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020034
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020035 def stop(self):
36 self.process.kill()
37
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020038
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010039class ScreenState(Enum):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020040 OFF_LOCKED = 1,
41 OFF_UNLOCKED = 2
42 ON_LOCKED = 3
43 ON_UNLOCKED = 4
Christoffer Adamsen456ea7f2024-07-01 15:00:29 +020044 UNKNOWN = 5
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010045
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020046 def is_off(self):
47 return self == ScreenState.OFF_LOCKED or self == ScreenState.OFF_UNLOCKED
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010048
Christoffer Adamsen456ea7f2024-07-01 15:00:29 +020049 def is_off_or_unknown(self):
50 return self.is_off() or self.is_unknown()
51
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020052 def is_on(self):
53 return self == ScreenState.ON_LOCKED or self == ScreenState.ON_UNLOCKED
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010054
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020055 def is_on_and_locked(self):
56 return self == ScreenState.ON_LOCKED
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010057
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020058 def is_on_and_unlocked(self):
59 return self == ScreenState.ON_UNLOCKED
60
Christoffer Adamsen456ea7f2024-07-01 15:00:29 +020061 def is_on_and_unlocked_or_unknown(self):
62 return self.is_on_and_unlocked() or self.is_unknown()
63
64 def is_on_or_unknown(self):
65 return self.is_on() or self.is_unknown()
66
67 def is_unknown(self):
68 return self == ScreenState.UNKNOWN
69
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010070
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +020071def broadcast(action, component, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020072 print('Sending broadcast %s' % action)
73 cmd = create_adb_cmd('shell am broadcast -a %s %s' % (action, component),
74 device_id)
75 return subprocess.check_output(cmd).decode('utf-8').strip().splitlines()
76
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +020077
Christoffer Quist Adamsendab3b202022-08-15 09:36:28 +020078def build_apks_from_bundle(bundle, output, overwrite=False):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020079 print('Building %s' % bundle)
80 cmd = [
81 'java', '-jar', utils.BUNDLETOOL_JAR, 'build-apks',
82 '--bundle=%s' % bundle,
83 '--output=%s' % output
84 ]
85 if overwrite:
86 cmd.append('--overwrite')
87 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
88
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +020089
Christoffer Quist Adamsened805c52022-08-08 14:46:29 +020090def capture_screen(target, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020091 print('Taking screenshot to %s' % target)
92 tmp = '/sdcard/screencap.png'
93 cmd = create_adb_cmd('shell screencap -p %s' % tmp, device_id)
94 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
95 pull(tmp, target, device_id)
96
Christoffer Quist Adamsened805c52022-08-08 14:46:29 +020097
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +010098def create_adb_cmd(arguments, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020099 assert isinstance(arguments, list) or isinstance(arguments, str)
100 cmd = ['adb']
101 if device_id is not None:
102 cmd.append('-s')
103 cmd.append(device_id)
104 cmd.extend(
105 arguments if isinstance(arguments, list) else arguments.split(' '))
106 return cmd
107
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100108
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100109def capture_app_profile_data(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200110 ps_cmd = create_adb_cmd('shell ps -o NAME', device_id)
111 stdout = subprocess.check_output(ps_cmd).decode('utf-8').strip()
112 killed_any = False
113 for process_name in stdout.splitlines():
114 if process_name.startswith(app_id):
115 print('Flushing profile for process %s' % process_name)
116 killall_cmd = create_adb_cmd(
117 'shell killall -s SIGUSR1 %s' % process_name, device_id)
118 killall_result = subprocess.run(killall_cmd, capture_output=True)
119 stdout = killall_result.stdout.decode('utf-8')
120 stderr = killall_result.stderr.decode('utf-8')
121 if killall_result.returncode == 0:
122 killed_any = True
123 else:
124 print('Error: stdout: %s, stderr: %s' % (stdout, stderr))
125 time.sleep(5)
126 assert killed_any, 'Expected to find at least one process'
127
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100128
129def check_app_has_profile_data(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200130 profile_path = get_profile_path(app_id)
131 cmd = create_adb_cmd(
132 'shell du /data/misc/profiles/cur/0/%s/primary.prof' % app_id,
133 device_id)
134 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
135 size_str = stdout[:stdout.index('\t')]
136 assert size_str.isdigit()
137 size = int(size_str)
138 if size == 4:
139 raise ValueError('Expected size of profile at %s to be > 4K' %
140 profile_path)
141
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100142
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200143def clear_logcat(device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200144 cmd = create_adb_cmd('logcat -c', device_id)
145 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
146
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200147
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100148def clear_profile_data(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200149 cmd = create_adb_cmd('shell cmd package compile --reset %s' % app_id,
150 device_id)
151 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
152
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100153
154def drop_caches(device_id=None):
Christoffer Adamsen01fd21a2024-10-14 12:16:52 +0200155 # On older devices this used to be achieved using:
156 # adb shell echo 3 > /proc/sys/vm/drop_caches
157 # This does not work on user devices, however.
158 cmd = create_adb_cmd(['shell', 'setprop', 'perf.drop_caches', '3'],
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200159 device_id)
160 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
161
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100162
Christoffer Quist Adamsen1a459b72022-05-11 12:09:03 +0200163def ensure_screen_on(device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200164 if get_screen_state(device_id).is_off():
165 toggle_screen(device_id)
Christoffer Adamsen456ea7f2024-07-01 15:00:29 +0200166 assert get_screen_state(device_id).is_on_or_unknown()
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200167
Christoffer Quist Adamsen1a459b72022-05-11 12:09:03 +0200168
169def ensure_screen_off(device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200170 if get_screen_state(device_id).is_on():
171 toggle_screen(device_id)
Christoffer Adamsen456ea7f2024-07-01 15:00:29 +0200172 assert get_screen_state(device_id).is_off_or_unknown()
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200173
Christoffer Quist Adamsen1a459b72022-05-11 12:09:03 +0200174
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100175def force_compilation(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200176 print('Applying AOT (full)')
177 cmd = create_adb_cmd('shell cmd package compile -m speed -f %s' % app_id,
178 device_id)
179 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
180
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100181
182def force_profile_compilation(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200183 print('Applying AOT (profile)')
184 cmd = create_adb_cmd(
185 'shell cmd package compile -m speed-profile -f %s' % app_id, device_id)
186 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
187
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100188
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100189def get_apk_path(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200190 cmd = create_adb_cmd('shell pm path %s' % app_id, device_id)
191 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
192 if not stdout.startswith('package:'):
193 raise ValueError('Expected stdout to start with "package:", was: %s' %
194 stdout)
195 apk_path = stdout[len('package:'):]
196 if not apk_path.endswith('.apk'):
197 raise ValueError('Expected stdout to end with ".apk", was: %s' % stdout)
198 return apk_path
199
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100200
Christoffer Quist Adamsen85f77482022-08-18 14:31:41 +0200201def get_component_name(app_id, activity):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200202 if activity.startswith(app_id):
203 return '%s/.%s' % (app_id, activity[len(app_id) + 1:])
204 else:
205 return '%s/%s' % (app_id, activity)
206
Christoffer Quist Adamsen85f77482022-08-18 14:31:41 +0200207
Christoffer Quist Adamsen558db202022-08-18 14:32:06 +0200208def get_meminfo(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200209 cmd = create_adb_cmd('shell dumpsys meminfo -s %s' % app_id, device_id)
210 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
211 for line in stdout.splitlines():
212 if 'TOTAL PSS: ' in line:
213 elements = [s for s in line.replace('TOTAL ', 'TOTAL_').split()]
214 assert elements[0] == 'TOTAL_PSS:', elements[0]
215 assert elements[1].isdigit()
216 assert elements[2] == 'TOTAL_RSS:'
217 assert elements[3].isdigit()
218 return {
219 'total_pss': int(elements[1]),
220 'total_rss': int(elements[3])
221 }
222 raise ValueError('Unexpected stdout: %s' % stdout)
223
Christoffer Quist Adamsen558db202022-08-18 14:32:06 +0200224
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200225def get_profile_data(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200226 with utils.TempDir() as temp:
227 source = get_profile_path(app_id)
228 target = os.path.join(temp, 'primary.prof')
229 pull(source, target, device_id)
230 with open(target, 'rb') as f:
231 return f.read()
232
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200233
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100234def get_profile_path(app_id):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200235 return '/data/misc/profiles/cur/0/%s/primary.prof' % app_id
236
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100237
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100238def get_minor_major_page_faults(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200239 pid = get_pid(app_id, device_id)
240 cmd = create_adb_cmd('shell ps -p %i -o MINFL,MAJFL' % pid, device_id)
241 stdout = subprocess.check_output(cmd).decode('utf-8')
242 lines_it = iter(stdout.splitlines())
243 first_line = next(lines_it)
244 assert first_line == ' MINFL MAJFL'
245 second_line = next(lines_it)
246 minfl, majfl = second_line.split()
247 assert minfl.isdigit()
248 assert majfl.isdigit()
249 return (int(minfl), int(majfl))
250
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100251
252def get_pid(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200253 cmd = create_adb_cmd('shell pidof %s' % app_id, device_id)
254 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
255 assert stdout.isdigit()
256 pid = int(stdout)
257 return pid
258
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100259
260def get_screen_state(device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200261 cmd = create_adb_cmd('shell dumpsys nfc', device_id)
Christoffer Adamsen456ea7f2024-07-01 15:00:29 +0200262 process_result = subprocess.run(cmd, capture_output=True)
263 stderr = process_result.stderr.decode('utf-8')
264 if "Can't find service: nfc" in stderr:
265 return ScreenState.UNKNOWN
266 stdout = process_result.stdout.decode('utf-8').strip()
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200267 screen_state_value = None
268 for line in stdout.splitlines():
269 if line.startswith('mScreenState='):
270 value_start_index = len('mScreenState=')
271 screen_state_value = line[value_start_index:]
272 if screen_state_value is None:
273 raise ValueError(
274 'Expected to find mScreenState in: adb shell dumpsys nfc')
275 if not hasattr(ScreenState, screen_state_value):
276 raise ValueError(
277 'Expected mScreenState to be a value of ScreenState, was: %s' %
278 screen_state_value)
279 return ScreenState[screen_state_value]
280
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100281
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100282def get_classes_and_methods_from_app_profile(app_id, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200283 apk_path = get_apk_path(app_id, device_id)
284 profile_path = get_profile_path(app_id)
285 cmd = create_adb_cmd(
286 'shell profman --dump-classes-and-methods'
287 ' --profile-file=%s --apk=%s --dex-location=%s' %
288 (profile_path, apk_path, apk_path), device_id)
289 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
290 lines = stdout.splitlines()
291 return profile_utils.parse_art_profile(lines)
292
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100293
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100294def get_screen_off_timeout(device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200295 cmd = create_adb_cmd('shell settings get system screen_off_timeout',
296 device_id)
297 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
298 assert stdout.isdigit()
299 screen_off_timeout = int(stdout)
300 return screen_off_timeout
301
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100302
Christoffer Quist Adamsen2d973412023-08-18 14:57:10 +0200303def grant(app_id, permission, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200304 cmd = create_adb_cmd('shell pm grant %s %s' % (app_id, permission),
305 device_id)
306 subprocess.check_call(cmd)
307
Christoffer Quist Adamsen2d973412023-08-18 14:57:10 +0200308
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100309def install(apk, device_id=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200310 print('Installing %s' % apk)
311 cmd = create_adb_cmd('install %s' % apk, device_id)
312 stdout = subprocess.check_output(cmd).decode('utf-8')
313 assert 'Success' in stdout
314
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100315
Christoffer Quist Adamsen6e965502022-08-15 10:21:07 +0200316def install_apks(apks, device_id=None, max_attempts=3):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200317 print('Installing %s' % apks)
318 cmd = [
319 'java', '-jar', utils.BUNDLETOOL_JAR, 'install-apks',
320 '--apks=%s' % apks
321 ]
322 if device_id is not None:
323 cmd.append('--device-id=%s' % device_id)
324 for i in range(max_attempts):
325 process_result = subprocess.run(cmd, capture_output=True)
326 stdout = process_result.stdout.decode('utf-8')
327 stderr = process_result.stderr.decode('utf-8')
328 if process_result.returncode == 0:
329 return
330 print('Failed to install %s' % apks)
331 print('Stdout: %s' % stdout)
332 print('Stderr: %s' % stderr)
333 print('Retrying...')
334 raise Exception('Unable to install apks in %s attempts' % max_attempts)
335
336
337def install_bundle(bundle, device_id=None):
338 print('Installing %s' % bundle)
339 with utils.TempDir() as temp:
340 apks = os.path.join(temp, 'Bundle.apks')
341 build_apks_from_bundle(bundle, apks)
342 install_apks(apks, device_id)
343
344
345def install_profile_using_adb(app_id, host_profile_path, device_id=None):
346 device_profile_path = get_profile_path(app_id)
347 cmd = create_adb_cmd('push %s %s' %
348 (host_profile_path, device_profile_path))
349 subprocess.check_call(cmd)
350 stop_app(app_id, device_id)
351 force_profile_compilation(app_id, device_id)
352
353
354def install_profile_using_profileinstaller(app_id, device_id=None):
355 # This assumes that the profileinstaller library has been added to the app,
356 # https://developer.android.com/jetpack/androidx/releases/profileinstaller.
357 action = 'androidx.profileinstaller.action.INSTALL_PROFILE'
358 component = '%s/androidx.profileinstaller.ProfileInstallReceiver' % app_id
359 stdout = broadcast(action, component, device_id)
360 assert len(stdout) == 2
361 assert stdout[0] == ('Broadcasting: Intent { act=%s flg=0x400000 cmp=%s }' %
362 (action, component))
363 assert stdout[1] == 'Broadcast completed: result=1', stdout[1]
364 stop_app(app_id, device_id)
365 force_profile_compilation(app_id, device_id)
366
367
368def issue_key_event(key_event, device_id=None, sleep_in_seconds=1):
369 cmd = create_adb_cmd('shell input keyevent %s' % key_event, device_id)
370 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
371 assert len(stdout) == 0
372 time.sleep(sleep_in_seconds)
373
374
375def launch_activity(app_id,
376 activity,
377 device_id=None,
378 intent_data_uri=None,
379 wait_for_activity_to_launch=False):
380 args = ['shell', 'am', 'start', '-n', '%s/%s' % (app_id, activity)]
381 if intent_data_uri:
382 args.extend(['-d', intent_data_uri])
383 if wait_for_activity_to_launch:
384 args.append('-W')
385 cmd = create_adb_cmd(args, device_id)
386 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
387 assert stdout.startswith('Starting: Intent {')
388 expected_component = 'cmp=%s' % get_component_name(app_id, activity)
389 assert expected_component in stdout, \
390 'was %s, expected %s' % (stdout, expected_component)
391 lines = stdout.splitlines()
392 result = {}
393 for line in lines:
394 if line.startswith('TotalTime: '):
395 total_time_str = line.removeprefix('TotalTime: ')
396 assert total_time_str.isdigit()
397 result['total_time'] = int(total_time_str)
398 assert not wait_for_activity_to_launch or 'total_time' in result, lines
399 return result
400
401
402def navigate_to_home_screen(device_id=None):
403 cmd = create_adb_cmd('shell input keyevent KEYCODE_HOME', device_id)
404 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
405
406
407def prepare_for_interaction_with_device(device_id=None, device_pin=None):
408 # Increase screen off timeout to avoid device screen turns off.
409 twenty_four_hours_in_millis = 24 * 60 * 60 * 1000
410 previous_screen_off_timeout = get_screen_off_timeout(device_id)
411 set_screen_off_timeout(twenty_four_hours_in_millis, device_id)
412
413 # Unlock device.
414 unlock(device_id, device_pin)
415
416 teardown_options = {
417 'previous_screen_off_timeout': previous_screen_off_timeout
418 }
419 return teardown_options
420
421
422def pull(source, target, device_id=None):
423 cmd = create_adb_cmd('pull %s %s' % (source, target), device_id)
424 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
425
426
427def root(device_id=None):
428 cmd = create_adb_cmd('root', device_id)
429 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
430
431
432def set_screen_off_timeout(screen_off_timeout_in_millis, device_id=None):
433 cmd = create_adb_cmd(
434 'shell settings put system screen_off_timeout %i' %
435 screen_off_timeout_in_millis, device_id)
436 stdout = subprocess.check_output(cmd).decode('utf-8').strip()
437 assert len(stdout) == 0
438
439
440def start_logcat(device_id=None, format=None, filter=None, silent=False):
441 args = ['logcat']
442 if format:
443 args.extend(['--format', format])
444 if silent:
445 args.append('-s')
446 if filter:
447 args.append(filter)
448 cmd = create_adb_cmd(args, device_id)
449 logcat_process = subprocess.Popen(cmd,
450 bufsize=1024 * 1024,
451 stdout=subprocess.PIPE,
452 stderr=subprocess.PIPE)
453 reader = ProcessReader(logcat_process)
454 reader.start()
455 return reader
456
457
458def stop_logcat(logcat_reader):
459 logcat_reader.stop()
460 logcat_reader.join()
461 return logcat_reader.lines
462
463
464def stop_app(app_id, device_id=None):
465 print('Shutting down %s' % app_id)
466 cmd = create_adb_cmd('shell am force-stop %s' % app_id, device_id)
467 subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
468
469
470def teardown_after_interaction_with_device(teardown_options, device_id=None):
471 # Reset screen off timeout.
472 set_screen_off_timeout(teardown_options['previous_screen_off_timeout'],
473 device_id)
474
475
476def toggle_screen(device_id=None):
477 issue_key_event('KEYCODE_POWER', device_id)
478
479
480def uninstall(app_id, device_id=None):
481 print('Uninstalling %s' % app_id)
482 cmd = create_adb_cmd('uninstall %s' % app_id, device_id)
Christoffer Quist Adamsen6e965502022-08-15 10:21:07 +0200483 process_result = subprocess.run(cmd, capture_output=True)
484 stdout = process_result.stdout.decode('utf-8')
485 stderr = process_result.stderr.decode('utf-8')
486 if process_result.returncode == 0:
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200487 assert 'Success' in stdout
488 elif stdout.startswith('cmd: Failure calling service package: Broken pipe'):
489 assert app_id == 'com.google.android.youtube'
490 print('Waiting after broken pipe')
491 time.sleep(15)
492 else:
493 expected_error = (
494 'java.lang.IllegalArgumentException: Unknown package: %s' % app_id)
495 assert 'Failure [DELETE_FAILED_INTERNAL_ERROR]' in stdout \
496 or expected_error in stderr, \
497 'stdout: %s, stderr: %s' % (stdout, stderr)
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200498
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100499
500def unlock(device_id=None, device_pin=None):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200501 ensure_screen_on(device_id)
Christoffer Quist Adamsenfad33a02022-03-14 14:45:09 +0100502 screen_state = get_screen_state(device_id)
Christoffer Adamsen456ea7f2024-07-01 15:00:29 +0200503 if screen_state.is_unknown():
504 return
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200505 assert screen_state.is_on(), 'was %s' % screen_state
506 if screen_state.is_on_and_locked():
507 if device_pin is not None:
508 raise NotImplementedError(
509 'Device unlocking with pin not implemented')
510 issue_key_event('KEYCODE_MENU', device_id)
511 screen_state = get_screen_state(device_id)
512 assert screen_state.is_on_and_unlocked(), 'was %s' % screen_state
513
Christoffer Quist Adamsen21f7f352022-05-30 19:52:43 +0200514
515def parse_options(argv):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200516 result = argparse.ArgumentParser(description='Run adb utils.')
517 result.add_argument('--capture-screen', help='Capture screen to given file')
518 result.add_argument('--device-id', help='Device id (e.g., emulator-5554).')
519 result.add_argument('--device-pin', help='Device pin code (e.g., 1234)')
520 result.add_argument('--ensure-screen-off',
521 help='Ensure screen off',
522 action='store_true',
523 default=False)
524 result.add_argument('--get-screen-state',
525 help='Get screen state',
526 action='store_true',
527 default=False)
528 result.add_argument('--unlock',
529 help='Unlock device',
530 action='store_true',
531 default=False)
532 options, args = result.parse_known_args(argv)
533 return options, args
534
Christoffer Quist Adamsen21f7f352022-05-30 19:52:43 +0200535
536def main(argv):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200537 (options, args) = parse_options(argv)
538 if options.capture_screen:
539 capture_screen(options.capture_screen, options.device_id)
540 if options.ensure_screen_off:
541 ensure_screen_off(options.device_id)
542 elif options.get_screen_state:
543 print(get_screen_state(options.device_id))
544 elif options.unlock:
545 unlock(options.device_id, options.device_pin)
Christoffer Quist Adamsen21f7f352022-05-30 19:52:43 +0200546
547
548if __name__ == '__main__':
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200549 sys.exit(main(sys.argv[1:]))