blob: 0ac8ecd298f05ebf712f84c56b8dedcee9a563d7 [file] [log] [blame]
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +01001#!/usr/bin/env python3
2
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +01003import pathlib
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +01004import re
5import subprocess
6import sys
7import time
8
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +01009import uiautomator
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010010
11VWS_CREDENTIALS = {"user": "fairphonetesting@gmail.com", "password": "aish3echi:uwaiSh"}
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +010012
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010013
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010014PREBUILTS_PATH = "../../vendor/smartviser/viser/prebuilts/apk"
Borjan Tchakaloffd8445d32018-04-18 18:01:20 +020015
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010016PREBUILT_PROXY_APK_PATTERN = "com.lunarlabs.panda.proxy-latest-sdk{sdk}-{flavour}.apk"
Borjan Tchakaloffd8445d32018-04-18 18:01:20 +020017
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010018PREBUILT_APKS = ["com.smartviser.demogame-latest.apk", "com.lunarlabs.panda-latest.apk"]
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010019
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +020020FAIRPHONE_WIFI_NETWORKS = {
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010021 "Fairphone Guest": {"security": "WPA/WPA2 PSK", "password": "fairwifi"},
22 "Fairphone DEV (2.4 GHz)": {"security": "WPA/WPA2 PSK", "password": "fdev@adm"},
23 "Fairphone DEV (5 GHz)": {"security": "WPA/WPA2 PSK", "password": "fdev@adm"},
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +020024}
25
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010026ADB_DEVICES_PATTERN = re.compile(r"^([a-z0-9-]+)\s+device$", flags=re.M)
Franz-Xaver Geigerb0f55422018-04-17 10:33:37 +020027
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010028
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020029class HostCommandError(BaseException):
30 """An error happened while issuing a command on the host."""
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010031
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020032 def __init__(self, command, error_message):
33 self.command = command
34 self.error_message = error_message
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010035 message = "Command `{}` failed: {}".format(command, error_message)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020036 super(HostCommandError, self).__init__(message)
37
38
39class DeviceCommandError(BaseException):
40 """An error happened while sending a command to a device."""
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010041
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020042 def __init__(self, serial, command, error_message):
43 self.serial = serial
44 self.command = command
45 self.error_message = error_message
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010046 message = "Command `{}` failed on {}: {}".format(command, serial, error_message)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020047 super(DeviceCommandError, self).__init__(message)
48
49
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010050def adb(*args, serial=None, raise_on_error=True):
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010051 """Run ADB command attached to serial.
52
53 Example:
54 >>> process = adb('shell', 'getprop', 'ro.build.fingerprint', serial='cc60c021')
55 >>> process.returncode
56 0
57 >>> process.stdout.strip()
58 'Fairphone/FP2/FP2:6.0.1/FP2-gms-18.02.0/FP2-gms-18.02.0:user/release-keys'
59
60 :param *args:
61 List of options to ADB (including command).
62 :param str serial:
63 Identifier for ADB connection to device.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020064 :param raise_on_error bool:
65 Whether to raise a DeviceCommandError exception if the return code is
66 less than 0.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010067 :returns subprocess.CompletedProcess:
68 Completed process.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020069 :raises DeviceCommandError:
70 If the command failed.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010071 """
72
73 # Make sure the adb server is started to avoid the infamous "out of date"
74 # message that pollutes stdout.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020075 ret = subprocess.run(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010076 ["adb", "start-server"],
77 stdout=subprocess.PIPE,
78 stderr=subprocess.PIPE,
79 universal_newlines=True,
80 )
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020081 if ret.returncode < 0:
82 if raise_on_error:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010083 raise DeviceCommandError(serial if serial else "??", str(args), ret.stderr)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020084 else:
85 return None
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010086
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010087 command = ["adb"]
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010088 if serial:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010089 command += ["-s", serial]
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010090 if args:
91 command += list(args)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020092 ret = subprocess.run(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010093 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
94 )
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010095
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020096 if raise_on_error and ret.returncode < 0:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +010097 raise DeviceCommandError(serial if serial else "??", str(args), ret.stderr)
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +010098
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +020099 return ret
100
101
Franz-Xaver Geigerb0f55422018-04-17 10:33:37 +0200102def list_devices():
103 """List serial numbers of devices attached to adb.
104
105 Raises:
106 DeviceCommandError: If the underlying adb command failed.
107 """
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100108 process = adb("devices")
Franz-Xaver Geigerb0f55422018-04-17 10:33:37 +0200109 return ADB_DEVICES_PATTERN.findall(process.stdout)
110
111
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100112def aapt(*args, raise_on_error=True):
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100113 """Run an AAPT command.
114
115 :param *args:
116 The AAPT command with its options.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200117 :param raise_on_error bool:
118 Whether to raise a DeviceCommandError exception if the return code is
119 less than 0.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100120 :returns subprocess.CompletedProcess:
121 Completed process.
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200122 :raises HostCommandError:
123 If the command failed.
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100124 """
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100125 command = ["aapt"]
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100126 if args:
127 command += list(args)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200128 ret = subprocess.run(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100129 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
130 )
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100131
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200132 if raise_on_error and ret.returncode < 0:
133 raise HostCommandError(str(args), ret.stderr)
134
135 return ret
136
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100137
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100138def configure_wifi_networks(device, networks):
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200139 """Configure Wi-Fi networks.
140
141 The `networks` parameters is a list of networks to configure hashed by
142 their SSID. Each network value should have the following format:
143 - security (str): The security value as can be found in the Wi-Fi
144 settings dialog. Common values are 'None' and 'WPA/WPA2 PSK'.
145 - password (str, optional): The network password if the security is
146 'WPA/WPA2 PSK'.
147
148 Parameters:
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100149 device: The device object.
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200150 networks (dict(dict(str))): The list of networks to configure.
151 Raises:
152 DeviceCommandError: If the UI automation fails.
153 """
154 # Open the Wi-Fi settings
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100155 device.adb(
156 "shell", "am start -a android.settings.WIFI_SETTINGS --activity-clear-task"
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100157 )
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200158
159 # Make sure Wi-Fi is enabled
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100160 wifi_enabler = device.ui(
161 text="OFF", resourceId="com.android.settings:id/switch_widget"
162 )
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200163 if wifi_enabler.exists:
164 wifi_enabler.click()
165
166 # Check for registered networks
167 registered_networks = set()
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100168 device.ui(description="More options").click.wait()
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200169 time.sleep(1)
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100170 saved_networks = device.ui(text="Saved networks")
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200171 if saved_networks.exists:
Borjan Tchakaloff1d83e562018-05-16 18:47:04 +0200172 saved_networks.click.wait()
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200173 for ssid in networks.keys():
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100174 if device.ui(text=ssid).exists:
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200175 registered_networks.add(ssid)
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100176 device.ui.press.back()
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200177
178 missing_networks = networks.keys() - registered_networks
179
180 for ssid in registered_networks:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100181 print("Ignoring `{}` Wi-Fi network, already configured.".format(ssid))
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200182
183 for ssid in missing_networks:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100184 print("Configuring `{}` Wi-Fi network…".format(ssid))
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200185
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100186 device.ui(description="More options").click.wait()
187 device.ui(text="Add network").click.wait()
188 device.ui(resourceId="com.android.settings:id/ssid").set_text(ssid)
189 device.ui(resourceId="com.android.settings:id/security").click()
190 device.ui(text=networks[ssid]["security"]).click()
191 password_field = device.ui(resourceId="com.android.settings:id/password")
Karsten Tauschec20f4f32019-03-04 14:32:04 +0100192 time.sleep(1)
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100193 if "password" in networks[ssid] and networks[ssid]["password"]:
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200194 if not password_field.exists:
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100195 device.ui(text="Cancel").click()
196 raise DeviceCommandError(
197 device.ui, "UI: add Wi-Fi", "missing password field"
198 )
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100199 password_field.set_text(networks[ssid]["password"])
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200200 elif password_field.exists:
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100201 device.ui(text="Cancel").click()
202 raise DeviceCommandError(
203 device.ui, "UI: add Wi-Fi", "missing password data"
204 )
205 save_button = device.ui(text="Save")
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200206 if not save_button.click():
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100207 device.ui(text="Cancel").click()
208 raise DeviceCommandError(
209 device.ui, "UI: add Wi-Fi", "could not save network"
210 )
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200211
212 # Force the Wi-Fi on and off to pick the best network available
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100213 device.ui(text="ON", resourceId="com.android.settings:id/switch_widget").click()
214 device.ui(text="OFF", resourceId="com.android.settings:id/switch_widget").click()
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200215
216 # Leave the settings
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100217 device.ui.press.back()
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200218
219
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100220def disable_privacy_impact_popup(device):
Karsten Tauscheb36529f2019-03-02 14:13:29 +0100221 """Disable Privacy Impact popup on Android 5.
222
223 This simplifies UI automation. Disabling the feature globally is more robust
224 than clicking through the the Privacy Impact screen per app.
225 """
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100226 print("Disabling the Privacy Impact screen…")
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100227 device.adb(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100228 "shell",
229 (
230 "am start -a android.intent.action.MAIN "
231 "com.fairphone.privacyimpact/.PrivacyImpactPreferenceActivity"
232 ),
Karsten Tauscheb36529f2019-03-02 14:13:29 +0100233 )
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100234 disable_privacy_impact_checkbox = device.ui(className="android.widget.CheckBox")
Karsten Tauscheb36529f2019-03-02 14:13:29 +0100235 if not disable_privacy_impact_checkbox.checked:
236 disable_privacy_impact_checkbox.click()
237
238
Borjan Tchakaloffd4ce8072018-05-16 18:20:13 +0200239def get_proxy_apk(android_sdk, flavour):
240 if android_sdk >= 24:
241 return PREBUILT_PROXY_APK_PATTERN.format(sdk=24, flavour=flavour)
242 else:
243 return PREBUILT_PROXY_APK_PATTERN.format(sdk=19, flavour=flavour)
244
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100245
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100246# Prepare the DUT
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100247def prepare_dut(device, scenarios_dir, data_dir, prebuilts_dir):
248 flavour = "gms" if device.is_gms_device() else "sibon"
249 proxy_apk = get_proxy_apk(device.sdk, flavour)
250 prebuilts_dir = pathlib.Path(prebuilts_dir)
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200251
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100252 # Uninstall the smartviser apps
Borjan Tchakaloffd4ce8072018-05-16 18:20:13 +0200253 for app in PREBUILT_APKS + [proxy_apk]:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100254 print("Uninstalling `{}`…".format(app))
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100255 device.uninstall(prebuilts_dir / app)
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100256
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100257 # Copy the scenarios
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100258 print("Pushing scenarios from `{}`…".format(scenarios_dir))
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100259 device.adb("push", scenarios_dir, "/sdcard/viser")
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100260
261 # Copy the scenarios data
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100262 print("Pushing scenarios data from `{}`…".format(data_dir))
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100263 device.adb("push", data_dir, "/sdcard/viser/data")
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100264
Franz-Xaver Geigerb3f7c982018-04-12 09:29:32 +0200265 # Install the smartviser apps (starting with the proxy app)
Borjan Tchakaloffd4ce8072018-05-16 18:20:13 +0200266 for app in [proxy_apk] + PREBUILT_APKS:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100267 print("Installing `{}`…".format(app))
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100268 device.install(prebuilts_dir / app)
Franz-Xaver Geigerb28348e2018-02-19 14:41:40 +0100269
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100270
271# Grant the permissions through the UI
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100272def configure_perms(device):
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100273 # Input the credentials
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100274 device.ui(resourceId="android:id/content").child(text="Username").child(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100275 className="android.widget.EditText"
276 ).set_text(VWS_CREDENTIALS["user"])
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100277 device.ui(resourceId="android:id/content").child(text="Password").child(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100278 className="android.widget.EditText"
279 ).set_text(VWS_CREDENTIALS["password"])
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100280
281 # Sign in
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100282 signin_label = "SIGN IN" if device.sdk >= 24 else "Sign in"
283 device.ui(resourceId="android:id/content").child(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100284 text=signin_label, className="android.widget.Button"
285 ).click()
286
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100287
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100288def configure_settings(device):
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100289 # Set the e-mail account
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100290 device.ui(text="Settings", className="android.widget.TextView").click()
291 device.ui(resourceId="android:id/list").child_by_text(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100292 "User settings", className="android.widget.LinearLayout"
293 ).click()
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100294 device.ui(resourceId="android:id/list").child_by_text(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100295 "Email account", className="android.widget.LinearLayout"
296 ).click()
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100297 prompt = device.ui(resourceId="android:id/content").child_by_text(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100298 "Email account", className="android.widget.LinearLayout"
299 )
300 prompt.child(resourceId="android:id/edit").set_text("fairphone.viser@gmail.com")
301 prompt.child(text="OK", className="android.widget.Button").click()
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100302 device.ui(resourceId="android:id/list").child_by_text(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100303 "Email password", className="android.widget.LinearLayout"
304 ).click()
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100305 device.ui(text="Password :").child(className="android.widget.EditText").set_text(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100306 "fairphoneviser2017"
307 )
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100308 device.ui(description="OK", className="android.widget.TextView").click()
309 device.ui.press.back()
310 device.ui.press.back()
311
312
313class DeviceUnderTest:
314 """An Android device under test."""
315
316 serial: str
317 """The device serial number (adb/fastboot)."""
318 sdk: int
319 """The Android SDK version number."""
320 ui: uiautomator.Device
321 """The UI Automator handle piloting the device."""
322
323 def __init__(self, serial: str):
324 self.serial = serial
325 self.ui = uiautomator.Device(serial)
326
327 # Cache the Android SDK version
328 self.sdk = self.ui.info["sdkInt"]
329
330 def adb(self, *args) -> subprocess.CompletedProcess:
331 """Execute an adb command on this device.
332
333 :returns: The completed process.
334 :raise DeviceCommandError: If the underlying adb command failed.
335 """
336 return adb(*args, serial=self.serial)
337
338 def force_awake(self, always=True) -> None:
339 """Force the device to stay awake.
340
341 :raise DeviceCommandError: If the underlying adb command failed.
342 """
343 self.adb("shell", "svc power stayon {}".format("true" if always else "false"))
344
345 def unlock(self) -> None:
346 """Wake-up the device and unlock it.
347
348 :raise DeviceCommandError: If the underlying adb commands failed.
349 """
350 if not self.ui.info["screenOn"]:
351 self.adb("shell", "input keyevent KEYCODE_POWER")
352 time.sleep(1)
353 # The KEYCODE_MENU input is enough to unlock a "swipe up to unlock"
354 # lockscreen on Android 6, but unfortunately not Android 7. So we use a
355 # swipe up (that depends on the screen resolution) instead.
356 self.adb("shell", "input touchscreen swipe 930 880 930 380")
357 time.sleep(1)
358 self.adb("shell", "input keyevent KEYCODE_HOME")
359
360 def getprop(self, key: str) -> str:
361 """Get a system property.
362
363 Example:
364 >>> self.getprop('ro.build.id')
365 'FP2-gms-18.02.0'
366
367 :param key: Key of property to get.
368 :returns: Value of system property.
369 :raise DeviceCommandError: If the underlying adb command failed.
370 """
371 process = self.adb("shell", "getprop", key)
372 return process.stdout.strip()
373
374 def is_gms_device(self) -> bool:
375 """Whether the device runs GMS or sibon.
376
377 Example:
378 >>> self.is_gms_device()
379 True
380
381 :returns: True if device runs GMS, false otherwise.
382 :raise DeviceCommandError: If the underlying adb command failed.
383 """
384 return self.getprop("ro.build.id").startswith("FP2-gms-") or self.getprop(
385 "ro.build.version.incremental"
386 ).startswith("gms-")
387
388 def uninstall(self, apk: pathlib.Path) -> None:
389 """Uninstall an app from an APK file.
390
391 :raise ValueError: If the package name could not be read from the apk.
392 :raise DeviceCommandError: If the uninstall command failed.
393 """
394 ret = aapt("dump", "badging", str(apk))
395 package = None
396 for line in ret.stdout.splitlines():
397 if line.startswith("package"):
398 for token in line.split(" "):
399 if token.startswith("name="):
400 # Extract the package name out of the token
401 # (name='some.package.name')
402 package = token[6:-1]
403 break
404 if not package:
405 raise ValueError("Could not find package of app `{}`".format(apk.name))
406
407 self.adb("uninstall", package)
408
409 def install(self, apk: pathlib.Path) -> None:
410 """Install an app from an APK file.
411
412 :raise DeviceCommandError: If the install command failed.
413 """
414 command = ["install", "-r"]
415 if self.sdk >= 23:
416 # From Marshmallow onwards, adb has a flag to grant default permissions
417 command.append("-g")
418
419 self.adb(*command, str(apk))
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100420
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100421
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100422def deploy():
423 serials = []
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100424
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100425 if len(sys.argv) > 1:
426 serials.append(sys.argv[1])
427 else:
Franz-Xaver Geigerb0f55422018-04-17 10:33:37 +0200428 serials = list_devices()
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100429
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100430 for serial in serials:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100431 print("Configuring device {}…".format(serial))
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100432
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100433 device = DeviceUnderTest(serial)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200434 try:
435 # Make sure the screen stays on - we're going to use UI automation
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100436 device.force_awake()
437 device.unlock()
Borjan Tchakaloff64ec42b2018-02-07 11:33:15 -0800438
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200439 # Configure common Fairphone Wi-Fi networks
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100440 if device.sdk < 24:
441 configure_wifi_networks(device, FAIRPHONE_WIFI_NETWORKS)
Borjan Tchakaloffd4ce8072018-05-16 18:20:13 +0200442 else:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100443 print(
444 "Uh oh, the device is running Android SDK {} on which we "
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100445 "do not deploy Wi-Fi networks yet.".format(device.sdk)
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100446 )
Borjan Tchakaloff74d962a2018-04-11 17:47:14 +0200447
Karsten Tauscheb36529f2019-03-02 14:13:29 +0100448 # Disable Privacy Impact popup on Android 5.
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100449 if device.sdk <= 22:
450 disable_privacy_impact_popup(device)
Karsten Tauscheb36529f2019-03-02 14:13:29 +0100451
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200452 # Push the scenarios, their data, and install the apps
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100453 prepare_dut(device, "../scenarios", "../scenarios-data", PREBUILTS_PATH)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100454
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200455 # Start the viser app
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100456 device.adb(
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100457 "shell",
458 "monkey",
459 "-p",
460 "com.lunarlabs.panda",
461 "-c",
462 "android.intent.category.LAUNCHER",
463 "1",
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100464 )
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100465
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100466 configure_perms(device)
Borjan Tchakaloffa4bdae12017-11-21 14:58:12 +0100467
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100468 configure_settings(device)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200469 except (HostCommandError, DeviceCommandError) as e:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100470 print("ERROR {}".format(e), file=sys.stderr)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200471 finally:
472 try:
473 # Leave the device alone now
Borjan Tchakaloff1e841d82020-01-24 16:25:50 +0100474 device.force_awake(always=False)
Borjan Tchakaloffde9fa0f2018-04-12 12:05:48 +0200475 except DeviceCommandError as e:
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100476 print("WARNING {}".format(e), file=sys.stderr)
Borjan Tchakaloff64ec42b2018-02-07 11:33:15 -0800477
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100478
Borjan Tchakaloff7d4f6152020-01-24 15:41:00 +0100479if __name__ == "__main__":
Franz-Xaver Geigerd1079e22018-02-19 14:34:15 +0100480 deploy()