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