forked from S2-group/android-runner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Device.py
232 lines (195 loc) · 9.78 KB
/
Device.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import logging
import os.path as op
import re
import time
from . import Adb
from .Adb import AdbError
from .util import ConfigError, makedirs
from . import Tests
import subprocess
class Device:
LOGCAT_BUFFER_SIZE_MIN = 64
LOGCAT_BUFFER_SIZE_MAX = 262144 # 256MB = 256 * 1024KB = 262144KB
LOGCAT_BUFFER_SIZE_DEFAULT = 57344 # 56MB = 56 * 1024KB = 57344KB since Nexus 5X has limit of 56MB.
def __init__(self, name, device_id, settings):
self.logger = logging.getLogger(self.__class__.__name__)
self.name = name
self.id = device_id
self.root_unplug = settings.get('root_disable_charging', False)
self.root_unplug_value = settings.get('charging_disabled_value', None)
self.root_unplug_file = settings.get('usb_charging_disabled_file', None)
self.root_plug_value = None
self.power_device = settings.get('power_device', None)
self.device_settings_reqs = settings.get('device_settings_reqs', None)
if self.power_device:
subprocess.call([self.power_device["py_path"], self.power_device["script_path"], self.power_device["vout"], self.power_device["serial_num"]])
Adb.connect(device_id)
# Set logcat buffer size for the device based on logcat_buffer_size set in the config file. If it is not
# defined use the default value.
self.logcat_buffer_size = settings.get('logcat_buffer_size', Device.LOGCAT_BUFFER_SIZE_DEFAULT)
@property
def logcat_buffer_size(self):
return self._logcat_buffer_size
@logcat_buffer_size.setter
def logcat_buffer_size(self, size):
""" Sets the logcat buffer size for the current device.
Parameters
----------
size : int
The size of the logcat buffer in KiloBytes (1024 bytes).
Minimum size is 64KB
Maximum size is 256MB (262144KB as 256 * 1024). While the maximum size differs from device to device, 256MB
is considered to be the upper limit.
Returns
-------
None
"""
if not isinstance(size, int):
raise ConfigError("Given logcat buffer size needs to be an integer.")
if not Device.LOGCAT_BUFFER_SIZE_MIN <= size <= Device.LOGCAT_BUFFER_SIZE_MAX:
raise ConfigError(f"Given logcat buffer size is {size}KB. It should be"
f" between {Device.LOGCAT_BUFFER_SIZE_MIN}KB"
f" and {Device.LOGCAT_BUFFER_SIZE_MAX}KB (inclusive).")
self.logger.info(f'Setting logcat buffer size to {size}K')
self._logcat_buffer_size = size
# Please note that logcat uses K to refer to KB.
Adb.shell(self.id, f'logcat -G {self._logcat_buffer_size}K')
def configure_settings_device(self, app, enable=True):
if self.device_settings_reqs is not None:
settings_for_app = self.device_settings_reqs.get(app, None)
if settings_for_app is not None:
num_settings = len(settings_for_app)
for setting in range(num_settings):
self.logger.info('Enabling ' + str(settings_for_app[setting])) if enable else self.logger.info('Disabling ' + str(settings_for_app[setting]))
Adb.configure_settings(self.id, settings_for_app[setting], enable)
def get_version(self):
"""Returns the Android version"""
return Adb.shell(self.id, 'getprop ro.build.version.release')
def get_api_level(self):
"""Returns the Android API level as a number"""
return Adb.shell(self.id, 'getprop ro.build.version.sdk')
def is_installed(self, apps):
"""Returns a boolean if a package is installed"""
return {app: app in self.get_app_list() for app in apps}
def get_app_list(self):
"""Returns a list of installed packages on the system"""
return Adb.list_apps(self.id)
def install(self, apk):
"""Check if the file exists, and then install the package"""
if not op.isfile(apk):
raise AdbError("%s is not found" % apk)
Adb.install(self.id, apk)
def uninstall(self, name):
"""Uninstalls the package on the device"""
Adb.uninstall(self.id, name)
def su_unplug(self, restart):
"""Root unplugs the device"""
self.root_plug_value = Adb.shell_su(self.id, 'cat %s' % self.root_unplug_file)
if 'su: not found' in self.root_plug_value:
raise AdbError("%s %s: is not rooted" % (self.id, self.name))
if 'No such file or directory' in self.root_plug_value:
raise ConfigError('%s %s: the root unplug file seems to be invalid' % (self.id, self.name))
if restart:
self.check_plug_value()
Adb.shell_su(self.id, 'echo %s > %s' % (self.root_unplug_value, self.root_unplug_file))
def check_plug_value(self):
"""Checks the root plug value for validity, if it's not valid it tries to make it valid"""
if isinstance(self.root_unplug_value, (int, int)):
try:
self.root_plug_value = int(self.root_plug_value)
except ValueError:
logging.info('Error setting root plug value, check manually after experiment if charging is enabled')
if self.root_plug_value == self.root_unplug_value:
try:
self.root_plug_value = abs(self.root_plug_value - 1)
except TypeError:
if 'enabled' in self.root_plug_value:
self.root_plug_value = 'disabled'
elif 'disabled' in self.root_plug_value:
self.root_plug_value = 'enabled'
def unplug(self, restart):
"""Makes the device to think it is unplugged, so the Doze mode can be activated"""
if self.root_unplug:
self.su_unplug(restart)
self.logger.info('Root unpluged')
else:
self.logger.info('Default unplug')
if int(self.get_api_level()) < 23:
# API level < 23, 4.4.3+ tested, WARNING: hardcoding
Adb.shell(self.id, 'dumpsys battery set usb 0')
# Adb.shell(self.id, 'dumpsys battery set ac 0')
# Adb.shell(self.id, 'dumpsys battery set wireless 0')
else:
# API level 23+ (Android 6.0+)
Adb.shell(self.id, 'dumpsys battery unplug')
def su_plug(self):
"""Reset the power status of the device if root unpluged"""
self.logger.info('Root pluged, please check if device is charging')
Adb.shell_su(self.id, 'echo %s > %s' % (self.root_plug_value, self.root_unplug_file))
def plug(self):
"""Reset the power status of the device"""
# if self.get_api_level() < 23:
# API level < 23, 4.4.3+ tested, WARNING: hardcoding
# reset only restarts auto-update
# Adb.shell(self.id, 'dumpsys battery set usb 1')
# API level 23+ (Android 6.0+)
if self.root_unplug:
self.su_plug()
Adb.shell(self.id, 'dumpsys battery reset')
def current_activity(self):
"""Newer Android 10 does not have mCurrentFocus and mFocusedApp. Different approach to get the current activity"""
recent_activity = Adb.shell(self.id,'dumpsys activity recents | grep "Recent #0" | cut -d "=" -f2 | grep -o -p "[a-z|.]*"')
if recent_activity:
result = recent_activity
self.logger.debug('Current activity: %s' % result)
return result
else:
self.logger.error(f'Results from dumpsys: {recent_activity}')
raise AdbError('Could not parse activity from dumpsys')
def launch_package(self, package):
"""Launches a package by name without activity, returns instantly"""
# https://stackoverflow.com/a/25398877
result = Adb.shell(self.id, 'monkey -p {} 1'.format(package))
if 'monkey aborted' in result:
raise AdbError('Could not launch "{}"'.format(package))
def launch_activity(self, package, activity, action='', data_uri='', from_scratch=False, force_stop=False):
"""Launches an activity using 'am start', returns instantly"""
# https://developer.android.com/studio/command-line/adb.html#am
# https://developer.android.com/studio/command-line/adb.html#IntentSpec
# https://stackoverflow.com/a/3229077
cmd = 'am start'
if force_stop:
cmd += ' -S'
if action:
cmd += ' -a %s' % action
cmd += ' -n %s/%s' % (package, activity)
if data_uri:
cmd += ' -d %s' % data_uri
# https://android.stackexchange.com/a/113919
if from_scratch:
cmd += ' --activity-clear-task'
return Adb.shell(self.id, cmd)
def force_stop(self, name):
"""Force stop an app by package name"""
Adb.shell(self.id, 'am force-stop %s' % name)
def clear_app_data(self, name):
"""Clears the data of an app by package name"""
Adb.clear_app_data(self.id, name)
def logcat_to_file(self, path):
"""Dumps the last x lines of logcat into a file specified by path"""
makedirs(path)
with open(op.join(path, '%s_%s.txt' % (self.id, time.strftime('%Y.%m.%d_%H%M%S'))), 'w+') as f:
f.write(Adb.logcat(self.id))
def logcat_regex(self, regex):
return Adb.logcat(self.id, regex=regex)
def push(self, local, remote):
"""Pushes a file from the computer to the device"""
return Adb.push(self.id, local, remote)
def pull(self, remote, local):
"""Pulls a file from the device to the computer"""
return Adb.pull(self.id, remote, local)
def shell(self, cmd):
"""Runs the device shell with command specified by cmd"""
return Adb.shell(self.id, cmd)
def __str__(self):
return '%s (%s, Android %s, API level %s)' % (self.name, self.id, self.get_version(), self.get_api_level())