blob: 437548d64453748d9ce6a52a73673b369e630561 [file] [log] [blame]
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +01001#!/usr/bin/env python3
2
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +01003import os
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +01004import time
5from uiautomator import Device
6import sys
7import subprocess
8import pathlib
9
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010010
11PREBUILTS_PATH = '../../vendor/smartviser/viser/prebuilts/apk'
12
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +020013PREBUILT_PROXY_APKS = {
14 'gms': 'com.lunarlabs.panda.proxy.apk',
15 'sibon': 'com.lunarlabs.panda.proxy-sibon.apk',
16}
17
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010018PREBUILT_APKS = [
19 'com.smartviser.demogame.apk',
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010020 'com.lunarlabs.panda.apk',
21]
22
23
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020024class HostCommandError(BaseException):
25 """An error happened while issuing a command on the host."""
26 def __init__(self, command, error_message):
27 self.command = command
28 self.error_message = error_message
29 message = 'Command `{}` failed: {}'.format(command, error_message)
30 super(HostCommandError, self).__init__(message)
31
32
33class DeviceCommandError(BaseException):
34 """An error happened while sending a command to a device."""
35 def __init__(self, serial, command, error_message):
36 self.serial = serial
37 self.command = command
38 self.error_message = error_message
39 message = 'Command `{}` failed on {}: {}'.format(
40 command, serial, error_message)
41 super(DeviceCommandError, self).__init__(message)
42
43
44def adb(*args, serial = None, raise_on_error = True):
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010045 """Run ADB command attached to serial.
46
47 Example:
48 >>> process = adb('shell', 'getprop', 'ro.build.fingerprint', serial='cc60c021')
49 >>> process.returncode
50 0
51 >>> process.stdout.strip()
52 'Fairphone/FP2/FP2:6.0.1/FP2-gms-18.02.0/FP2-gms-18.02.0:user/release-keys'
53
54 :param *args:
55 List of options to ADB (including command).
56 :param str serial:
57 Identifier for ADB connection to device.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020058 :param raise_on_error bool:
59 Whether to raise a DeviceCommandError exception if the return code is
60 less than 0.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010061 :returns subprocess.CompletedProcess:
62 Completed process.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020063 :raises DeviceCommandError:
64 If the command failed.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010065 """
66
67 # Make sure the adb server is started to avoid the infamous "out of date"
68 # message that pollutes stdout.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020069 ret = subprocess.run(
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010070 ['adb', 'start-server'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
71 universal_newlines=True)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020072 if ret.returncode < 0:
73 if raise_on_error:
74 raise DeviceCommandError(
75 serial if serial else '??', str(args), ret.stderr)
76 else:
77 return None
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010078
79 command = ['adb']
80 if serial:
81 command += ['-s', serial]
82 if args:
83 command += list(args)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020084 ret = subprocess.run(
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010085 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
86 universal_newlines=True)
87
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020088 if raise_on_error and ret.returncode < 0:
89 raise DeviceCommandError(
90 serial if serial else '??', str(args), ret.stderr)
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010091
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020092 return ret
93
94
95def aapt(*args, raise_on_error = True):
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010096 """Run an AAPT command.
97
98 :param *args:
99 The AAPT command with its options.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200100 :param raise_on_error bool:
101 Whether to raise a DeviceCommandError exception if the return code is
102 less than 0.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100103 :returns subprocess.CompletedProcess:
104 Completed process.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200105 :raises HostCommandError:
106 If the command failed.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100107 """
108 command = ['aapt']
109 if args:
110 command += list(args)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200111 ret = subprocess.run(
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100112 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
113 universal_newlines=True)
114
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200115 if raise_on_error and ret.returncode < 0:
116 raise HostCommandError(str(args), ret.stderr)
117
118 return ret
119
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100120
Borjan Tchakaloff64ec42b2018-02-07 11:33:15 -0800121def force_awake(serial, always=True):
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200122 """Force the device to stay awake.
123
124 Raises:
125 DeviceCommandError: If the underlying adb command failed.
126 """
127 adb('shell', 'svc power stayon {}'.format(
Borjan Tchakaloff64ec42b2018-02-07 11:33:15 -0800128 'true' if always else 'false'), serial=serial)
129
130
131def unlock(serial):
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200132 """Wake-up the device and unlock it.
133
134 Raises:
135 DeviceCommandError: If the underlying adb commands failed.
136 """
Borjan Tchakaloff64ec42b2018-02-07 11:33:15 -0800137 adb('shell', 'input keyevent KEYCODE_POWER', serial=serial)
138 time.sleep(1)
139 adb('shell', 'input keyevent KEYCODE_MENU', serial=serial)
140 time.sleep(1)
141 adb('shell', 'input keyevent KEYCODE_HOME', serial=serial)
142
143
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200144def getprop(serial, key):
145 """Get system property of device.
146
147 Example:
148 >>> getprop('167eb6e8', 'ro.build.id')
149 'FP2-gms-18.02.0'
150
151 :param str serial:
152 Identifier for ADB connection to device.
153 :param str key:
154 Key of property to get.
155 :returns str:
156 Value of system property.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200157 :raise DeviceCommandError: If the underlying adb command failed.
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200158 """
159 process = adb('shell', 'getprop', key, serial=serial)
160 return process.stdout.strip()
161
162
163def is_gms_device(serial):
164 """Test if device runs GMS or sibon.
165
166 Example:
167 >>> is_gms_device('167eb6e8')
168 True
169
170 :param str serial:
171 Identifier for ADB connection to device.
172 :returns bool:
173 True if device runs GMS, false otherwise.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200174 :raise DeviceCommandError: If the underlying adb command failed.
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200175 """
176 return getprop(serial, 'ro.build.id').startswith('FP2-gms-')
177
178
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100179def uninstall_apk(serial, filename, prebuilts_dir):
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200180 """Uninstall apk from prebuilts_dir on device.
181
182 Raises:
183 ValueError: If the package name could not be read from the apk.
184 DeviceCommandError: If the uninstall command failed.
185 """
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100186 ret = aapt('dump', 'badging', '{}/{}'.format(prebuilts_dir, filename))
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200187 package = None
188 for line in ret.stdout.splitlines():
189 if line.startswith('package'):
190 for token in line.split(' '):
191 if token.startswith('name='):
192 # Extract the package name out of the token
193 # (name='some.package.name')
194 package = token[6:-1]
195 break
196 if not package:
197 raise ValueError('Could not find package of app `{}`'.format(
198 filename))
199
200 adb('uninstall', package, serial=serial)
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100201
202
203def install_apk(serial, filename, prebuilts_dir):
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200204 """Install apk from prebuilts_dir on device.
205
206 Raises:
207 DeviceCommandError: If the install command failed.
208 """
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100209 path = os.path.join(prebuilts_dir, filename)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200210 adb('install', '-r', path, serial=serial)
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100211
212
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100213# Prepare the DUT
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100214def prepare_dut(serial, scenarios_dir, data_dir, prebuilts_dir):
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200215 flavour = 'gms' if is_gms_device(serial) else 'sibon'
216
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100217 # Uninstall the smartviser apps
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200218 for app in PREBUILT_APKS + [PREBUILT_PROXY_APKS[flavour]]:
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200219 print('Uninstalling `{}`…'.format(app))
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100220 uninstall_apk(serial, app, prebuilts_dir)
221
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100222 # Copy the scenarios
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200223 print('Pushing scenarios from `{}`…'.format(scenarios_dir))
224 adb('push', scenarios_dir, '/sdcard/viser', serial=serial)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100225
226 # Copy the scenarios data
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200227 print('Pushing scenarios data from `{}`…'.format(data_dir))
228 adb('push', data_dir, '/sdcard/viser/data', serial=serial)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100229
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200230 # Install the smartviser apps (starting with the proxy app)
231 for app in [PREBUILT_PROXY_APKS[flavour]] + PREBUILT_APKS:
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200232 print('Installing `{}`…'.format(app))
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100233 install_apk(serial, app, prebuilts_dir)
234
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100235
236# Grant the permissions through the UI
237def configure_perms(dut):
Borjan Tchakaloff6dad85d2018-04-11 13:55:25 +0200238 dut() \
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100239 .child_by_text('You need to grant access for the Permissions Group '
240 'Calendar Camera Contacts Microphone SMS Storage Telephone Location',
Borjan Tchakaloff6dad85d2018-04-11 13:55:25 +0200241 resourceId='android:id/content') \
242 .child(text='OK', className='android.widget.Button') \
243 .click()
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100244
245 # Accept any permission that is asked for
246 prompt = dut(resourceId='com.android.packageinstaller:id/permission_allow_button',
247 className='android.widget.Button')
248 while prompt.exists:
249 prompt.click()
250
251 # Input the credentials
252 dut(resourceId='android:id/content') \
253 .child(text='Username') \
254 .child(className='android.widget.EditText') \
255 .set_text('borjan@fairphone.com')
256 dut(resourceId='android:id/content') \
257 .child(text='Password') \
258 .child(className='android.widget.EditText') \
259 .set_text('Rickisalso1niceguy!')
260
261 # Sign in
262 dut(resourceId='android:id/content') \
263 .child(text='Sign in', className='android.widget.Button') \
264 .click()
265
266def configure_sms(dut):
267 # TODO wait for the connection to be established and time-out
268 prompt = dut(resourceId='android:id/content') \
269 .child(text='Viser must be your SMS app to send messages')
270 while not prompt.exists:
271 time.sleep(1)
272
273 # Make viser the default SMS app
274 dut(resourceId='android:id/content') \
275 .child_by_text('Viser must be your SMS app to send messages',
276 className='android.widget.LinearLayout') \
277 .child(text='OK', className='android.widget.Button') \
278 .click()
279
280 dut(resourceId='android:id/content') \
281 .child_by_text('Change SMS app?', className='android.widget.LinearLayout') \
282 .child(text='Yes', className='android.widget.Button') \
283 .click()
284
285def configure_settings(dut):
286 # Set the e-mail account
287 dut(text='Settings', className='android.widget.TextView') \
288 .click()
289 dut(resourceId='android:id/list') \
290 .child_by_text('User settings', className='android.widget.LinearLayout') \
291 .click()
292 dut(resourceId='android:id/list') \
293 .child_by_text('Email account', className='android.widget.LinearLayout') \
294 .click()
295 prompt = dut(resourceId='android:id/content') \
296 .child_by_text('Email account', className='android.widget.LinearLayout')
297 prompt.child(resourceId='android:id/edit') \
298 .set_text('fairphone.viser@gmail.com')
299 prompt.child(text='OK', className='android.widget.Button') \
300 .click()
301 dut(resourceId='android:id/list') \
302 .child_by_text('Email password', className='android.widget.LinearLayout') \
303 .click()
304 dut(text='Password :') \
305 .child(className='android.widget.EditText') \
306 .set_text('fairphoneviser2017')
307 dut(description='OK', className='android.widget.TextView') \
308 .click()
309 dut.press.back()
310 dut.press.back()
311
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100312
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100313def deploy():
314 serials = []
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100315
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100316 if len(sys.argv) > 1:
317 serials.append(sys.argv[1])
318 else:
319 # List devices attached to adb
320 ret = subprocess.run(
321 "adb devices | grep -E '^[a-z0-9-]+\s+device$' | awk '{{print $1}}'",
322 shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
323 universal_newlines=True)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100324
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100325 if 0 < ret.returncode:
326 # TODO handle the error
327 raise Exception('Failed to list adb devices')
328 elif ret.stdout:
329 for line in ret.stdout.splitlines():
330 serial = line.strip()
331 if serial:
332 serials.append(serial)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100333
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100334 for serial in serials:
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200335 print('Configuring device {}…'.format(serial))
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100336
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100337 dut = Device(serial)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100338
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200339 try:
340 # Make sure the screen stays on - we're going to use UI automation
341 force_awake(serial)
342 unlock(serial)
Borjan Tchakaloff64ec42b2018-02-07 11:33:15 -0800343
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200344 # Push the scenarios, their data, and install the apps
345 prepare_dut(
346 serial, '../scenarios', '../scenarios-data', PREBUILTS_PATH)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100347
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200348 # Start the viser app
349 adb(
Franz-Xaver Geiger223ae752018-02-19 14:54:08 +0100350 'shell', 'monkey', '-p', 'com.lunarlabs.panda', '-c',
351 'android.intent.category.LAUNCHER', '1', serial=serial)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100352
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200353 configure_perms(dut)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100354
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200355 # TODO DO NOT DO THE FOLLOWING IF NO SIM CARD IS IN THE DUT
356 # time.sleep(10)
357 # configure_sms(dut)
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100358
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200359 configure_settings(dut)
360 except (HostCommandError, DeviceCommandError) as e:
361 print('ERROR {}'.format(e), file=sys.stderr)
362 finally:
363 try:
364 # Leave the device alone now
365 force_awake(serial, always=False)
366 except DeviceCommandError as e:
367 print('WARNING {}'.format(e), file=sys.stderr)
Borjan Tchakaloff64ec42b2018-02-07 11:33:15 -0800368
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100369
370if __name__ == '__main__':
371 deploy()