aboutsummaryrefslogtreecommitdiff
path: root/automated
diff options
context:
space:
mode:
Diffstat (limited to 'automated')
-rw-r--r--automated/android/multinode/connect-to-remote-adb-tcpip-devices.yaml24
-rw-r--r--automated/android/multinode/release-remote-adb-tcpip-devices.yaml20
-rw-r--r--automated/android/multinode/remote-adb-devices-smoke-test.yaml27
-rw-r--r--automated/android/multinode/share-local-device-over-adb-tcpip.yaml36
-rw-r--r--automated/android/multinode/wait-and-keep-local-device-accessible.yaml98
-rw-r--r--automated/android/multinode/wait-for-release-and-reset.yaml21
-rw-r--r--automated/lib/android-multinode-test-lib213
-rw-r--r--automated/lib/android_adb_wrapper.py110
-rwxr-xr-xautomated/lib/android_ui_wifi.py112
9 files changed, 661 insertions, 0 deletions
diff --git a/automated/android/multinode/connect-to-remote-adb-tcpip-devices.yaml b/automated/android/multinode/connect-to-remote-adb-tcpip-devices.yaml
new file mode 100644
index 0000000..3dc2e42
--- /dev/null
+++ b/automated/android/multinode/connect-to-remote-adb-tcpip-devices.yaml
@@ -0,0 +1,24 @@
+metadata:
+ name: connect-to-remote-adb-tcpip-devices
+ format: "Lava-Test-Shell Test Definition 1.0"
+ description: "adb MultiNode setup: connect to remote devices made accessible via adb TCP/IP."
+ maintainer:
+ - karsten@fairphone.com
+ - softwareteam@fairphone.com
+ os:
+ - debian
+ - ubuntu
+ devices:
+ - lxc
+ scope:
+ - functional
+
+params:
+ ADB_CONNECT_TIMEOUT_SECS: "60"
+ DEVICE_WORKER_MAPPING_FILE: "/tmp/deviceWorkerMapping"
+
+run:
+ steps:
+ - . ./automated/lib/sh-test-lib
+ - . ./automated/lib/android-multinode-test-lib
+ - connect_to_remote_adb_tcpip_devices "${ADB_CONNECT_TIMEOUT_SECS}" "${DEVICE_WORKER_MAPPING_FILE}"
diff --git a/automated/android/multinode/release-remote-adb-tcpip-devices.yaml b/automated/android/multinode/release-remote-adb-tcpip-devices.yaml
new file mode 100644
index 0000000..c28c22e
--- /dev/null
+++ b/automated/android/multinode/release-remote-adb-tcpip-devices.yaml
@@ -0,0 +1,20 @@
+metadata:
+ name: release-remote-adb-tcpip-devices
+ format: "Lava-Test-Shell Test Definition 1.0"
+ description: "Disconnect from remote adb devices and cleanup."
+ maintainer:
+ - karsten@fairphone.com
+ - softwareteam@fairphone.com
+ os:
+ - debian
+ - ubuntu
+ devices:
+ - lxc
+ scope:
+ - functional
+
+run:
+ steps:
+ - lava-sync release_dut
+ # Cleanup adb server: LAVA expects only one device connected to adb.
+ - adb kill-server
diff --git a/automated/android/multinode/remote-adb-devices-smoke-test.yaml b/automated/android/multinode/remote-adb-devices-smoke-test.yaml
new file mode 100644
index 0000000..838b866
--- /dev/null
+++ b/automated/android/multinode/remote-adb-devices-smoke-test.yaml
@@ -0,0 +1,27 @@
+metadata:
+ name: remote-adb-devices-smoke-test
+ format: "Lava-Test-Shell Test Definition 1.0"
+ description: "Smoke test demonstrating access to adb devices over TCP/IP."
+ maintainer:
+ - karsten@fairphone.com
+ - softwareteam@fairphone.com
+ os:
+ - debian
+ - ubuntu
+ devices:
+ - lxc
+ scope:
+ - functional
+
+params:
+ DEVICE_WORKER_MAPPING_FILE: "/tmp/deviceWorkerMapping"
+
+run:
+ steps:
+ - device_worker_mapping="$(cat "${DEVICE_WORKER_MAPPING_FILE}")"
+ - |
+ for device_to_worker in ${device_worker_mapping}; do
+ device="$(echo ${device_to_worker} | cut -d';' -f1)"
+ echo "${device}: $(adb -s "${device}" shell service call iphonesubinfo 1 | \
+ grep -oE '(\.[0-9])|([0-9]\.)' | grep -oE '[0-9]' | tr -d '\n')"
+ done
diff --git a/automated/android/multinode/share-local-device-over-adb-tcpip.yaml b/automated/android/multinode/share-local-device-over-adb-tcpip.yaml
new file mode 100644
index 0000000..9ede1df
--- /dev/null
+++ b/automated/android/multinode/share-local-device-over-adb-tcpip.yaml
@@ -0,0 +1,36 @@
+metadata:
+ name: share-local-device-over-adb-tcpip
+ format: "Lava-Test-Shell Test Definition 1.0"
+ description: "adb MultiNode setup: make local device remotely accessible via adb TCP/IP.
+ Handles the device over to a role that responds to the following synchronization steps:
+ - lava-sync start_handover
+ - lava-send dut_address dut_address=${dut_address}
+ - lava-sync finish_handover"
+ maintainer:
+ - karsten@fairphone.com
+ - softwareteam@fairphone.com
+ os:
+ - debian
+ - ubuntu
+ devices:
+ - lxc
+ scope:
+ - functional
+
+params:
+ ADB_PORT: "5555"
+ ADB_TCPIP_ATTEMPTS: "5"
+ TIMEOUT_SECS: "60"
+ RAISE_ON_FAILURE: "true"
+
+run:
+ steps:
+ - . ./automated/lib/sh-test-lib
+ - . ./automated/lib/android-test-lib
+ - . ./automated/lib/android-multinode-test-lib
+ - ret_val=0
+ - share_local_device_over_adb_tcpip "${ADB_TCPIP_ATTEMPTS}" "${TIMEOUT_SECS}" "${ADB_PORT}" || ret_val=$?
+ - |
+ if [ "${ret_val}" -ne 0 -a "${RAISE_ON_FAILURE}" = "true" ]; then
+ lava-test-raise "Could not share device of adb tcpip."
+ fi
diff --git a/automated/android/multinode/wait-and-keep-local-device-accessible.yaml b/automated/android/multinode/wait-and-keep-local-device-accessible.yaml
new file mode 100644
index 0000000..35460d0
--- /dev/null
+++ b/automated/android/multinode/wait-and-keep-local-device-accessible.yaml
@@ -0,0 +1,98 @@
+metadata:
+ name: wait-and-keep-local-device-accessible
+ format: "Lava-Test-Shell Test Definition 1.0"
+ description: "Continuously wait for MultiNode messages from a remote role (master) and make the
+ locally connected device accessible again when it is lost for the remote role."
+ maintainer:
+ - karsten@fairphone.com
+ - softwareteam@fairphone.com
+ os:
+ - debian
+ - ubuntu
+ devices:
+ - lxc
+ scope:
+ - functional
+
+params:
+ ADB_PORT: "5555"
+ BOOT_TIMEOUT_SECS: "900"
+ NETWORK_TIMEOUT_SECS: "300"
+ ADB_TCPIP_ATTEMPTS: "5"
+ ADB_CONNECT_TEST_TIMEOUT_SECS: "60"
+ ANDROID_ENABLE_WIFI: "true"
+
+run:
+ steps:
+ - lava-install-packages --no-install-recommends python3-pip python3-setuptools python3-wheel
+ - pip3 install -q uiautomator
+ - . ./automated/lib/sh-test-lib
+ - . ./automated/lib/android-test-lib
+ - . ./automated/lib/android-multinode-test-lib
+ - lava-test-set start keepAlive
+ - |
+ reconnect_device() {
+ timeout 10 fastboot reboot || true
+
+ local ret_val=0
+ sh -c ". ./automated/lib/sh-test-lib && . ./automated/lib/android-test-lib \
+ && wait_boot_completed \"${BOOT_TIMEOUT_SECS}\"" \
+ || ret_val=$?
+
+ if [ "${ret_val}" -ne 0 ]; then
+ result=false
+ echo "WARNING: Reconnect attempt failed: target did not boot up or is not accessible."
+ return
+ fi
+
+ if [ "${ANDROID_ENABLE_WIFI}" = "true" ]; then
+ ./automated/lib/android_ui_wifi.py -a set_wifi_state on || ret_val=$?
+ if [ "${ret_val}" -ne 0 ]; then
+ echo "WARNING: Cannot ensure that Wi-Fi is enabled in the device settings; UI automation failed."
+ fi
+ fi
+
+ ret_val=0
+ sh -c ". ./automated/lib/sh-test-lib && . ./automated/lib/android-test-lib \
+ && . ./automated/lib/android-multinode-test-lib \
+ && wait_network_connected \"${NETWORK_TIMEOUT_SECS}\" \
+ && open_adb_tcpip_on_local_device \
+ \"${ADB_TCPIP_ATTEMPTS}\" \"${ADB_CONNECT_TEST_TIMEOUT_SECS}\" \"${ADB_PORT}\"" \
+ || ret_val=$?
+
+ if [ "${ret_val}" -ne 0 ]; then
+ result=false
+ echo "WARNING: Reconnect attempt failed."
+ fi
+ }
+ - iteration=1
+ - |
+ while true; do
+ lava-wait master-sync-$(lava-self)-${iteration}
+
+ command="$(cat /tmp/lava_multi_node_cache.txt | grep "command" | sed 's/.*command=//' | grep -v '^$')"
+
+ result="pass"
+
+ case "${command}" in
+ continue)
+ ;;
+ release)
+ break
+ ;;
+ reconnect)
+ echo "Reconnect requested by master."
+ adb kill-server || true
+ adb devices || true
+ reconnect_device
+ ;;
+ *)
+ lava-test-raise "Script error. Unexpected message from master to worker, command=${command}"
+ esac
+
+ lava-send worker-sync-$(lava-self)-${iteration} result=$result
+
+ iteration="$(expr ${iteration} + 1)"
+ done
+ - echo "master released the device."
+ - lava-test-set stop
diff --git a/automated/android/multinode/wait-for-release-and-reset.yaml b/automated/android/multinode/wait-for-release-and-reset.yaml
new file mode 100644
index 0000000..78a52b4
--- /dev/null
+++ b/automated/android/multinode/wait-for-release-and-reset.yaml
@@ -0,0 +1,21 @@
+metadata:
+ name: wait-for-release-and-reset
+ format: "Lava-Test-Shell Test Definition 1.0"
+ description: "Wait until a remote MultiNode role (master) requests to release the device.
+ Then, bring the device back into adb USB state."
+ maintainer:
+ - karsten@fairphone.com
+ - softwareteam@fairphone.com
+ os:
+ - debian
+ - ubuntu
+ devices:
+ - lxc
+ scope:
+ - functional
+
+run:
+ steps:
+ - lava-sync release_dut
+ - adb kill-server || true
+ - adb usb
diff --git a/automated/lib/android-multinode-test-lib b/automated/lib/android-multinode-test-lib
new file mode 100644
index 0000000..d5fe843
--- /dev/null
+++ b/automated/lib/android-multinode-test-lib
@@ -0,0 +1,213 @@
+#!/bin/sh
+
+# Configure adb to accept adb connections via TCP/IP and make sure that the device is actually
+# accessible.
+# This function assumes that the device has a network address. Guards around `adb tcpip` and test
+# connection setups using `adb connect` are used to check if the device is reachable after this
+# call.
+# Globals:
+# dut_address Set to "ip_address:adb_port" by this function
+# Arguments:
+# adb_tcpip_attempts Number of tries for enabling adb TCP/IP mode on the device
+# timeout_secs Timeout for waiting for getting the IP address from the device
+# adb_port Network port to use for adb TCP/IP
+# Returns:
+# 0 only if the device is accessible via adb TCP/IP, 1 otherwise.
+open_adb_tcpip_on_local_device() {
+ [ "$#" -lt 2 -o "$#" -gt 3 ] && \
+ error_fatal "Usage: open_adb_tcpip_on_local_device adb_tcpip_attempts timeout_secs [adb_port]"
+ local adb_tcpip_attempts="$1"
+ local timeout_secs="$2"
+ local adb_port="$3"
+ if [ -z "${adb_port}" ]; then
+ local adb_port=5555 # default port assumed by adb connect
+ fi
+
+ local end=$(( $(date +%s) + timeout_secs ))
+
+ local ret_val=0
+ local ip_address
+ ip_address="$(get_ip_address ${timeout_secs})" || ret_val=$?
+ if [ "${ret_val}" -ne 0 ]; then
+ warn_msg "get_ip_address failed unexpectedly."
+ return 1
+ fi
+ if [ -z "${ip_address}" ]; then
+ warn_msg "Device has no ip address (network not connected?)"
+ return 1
+ fi
+ dut_address="${ip_address}:${adb_port}"
+
+ # adb tcpip may fail with different reasons
+ # (e.g., "error: protocol fault (couldn't read status): Connection reset by peer").
+ # Just hope that it works after a few retries.
+ local adb_tcpip_retry_wait_secs=10
+ local attempt=0
+ while [ "${attempt}" -lt "${adb_tcpip_attempts}" -a "$(date +%s)" -lt "$end" ]; do
+ ret_val=0
+ adb tcpip "${adb_port}" || ret_val=$?
+ if [ "${ret_val}" -eq 0 ]; then
+ break
+ fi
+ info_msg "adb tcpip apparently failed. Retrying in a moment..."
+ sleep "${adb_tcpip_retry_wait_secs}"
+ adb usb || true # In between, make sure to have some default state.
+ done
+
+ if [ "${ret_val}" -ne 0 ]; then
+ warn_msg "Could not prepare the device for adb TCP/IP connections: adb tcpip failed."
+ return 1
+ fi
+
+ # `adb tcpip` sometimes takes some time
+ # (on some builds, up to 10 seconds were observed)
+ local success=false
+ while [ "$(date +%s)" -lt "$end" ]; do
+ if [ $(adb connect "${dut_address}" | grep -c '^connected to ') -eq 1 ]; then
+ success=true
+ break
+ fi
+ sleep 1
+ done
+
+ # Make sure the device is not reserved to the local adb server.
+ adb disconnect "${dut_address}" >/dev/null 2>&1 || true
+
+ if [ "${success}" = false ]; then
+ warn_msg "Could not prepare the device for adb TCP/IP connections: device is not reachable via network."
+ return 1
+ fi
+}
+
+# Make this device accessible via adb TCP/IP and send its address via handshake to a waiting role.
+# NOTE: This function must only be called once per role per test submission, as LAVA does not allow
+# to use the same MultiNode message ID multiple times.
+# One job instance must call connect_to_remote_adb_tcpip_devices to receive the send addresses and
+# complete the handshake.
+# See open_adb_tcpip_on_local_device and connect_to_remote_adb_tcpip_devices
+# Globals:
+# dut_address Set to "ip_address:adb_port" by this function
+# Arguments:
+# adb_tcpip_attempts Number of tries for establishing enabling adb TCP/IP mode on the device
+# timeout_secs Timeout for waiting for getting the IP address from the device
+# adb_port Network port to use for adb TCP/IP
+# Returns:
+# 0 only if the device is accessible via adb TCP/IP, 1 otherwise.
+share_local_device_over_adb_tcpip() {
+ [ "$#" -lt 2 -o "$#" -gt 3 ] && \
+ error_fatal "Usage: share_local_device_over_adb_tcpip adb_tcpip_attempts timeout_secs [adb_port]"
+ local adb_tcpip_attempts="$1"
+ local timeout_secs="$2"
+ local adb_port="$3"
+
+ local ret_val=0
+ open_adb_tcpip_on_local_device "${adb_tcpip_attempts}" "${timeout_secs}" "${adb_port}" || ret_val=$?
+ if [ "${ret_val}" -ne 0 ]; then
+ return "${ret_val}"
+ fi
+
+ lava-sync start_handover
+ lava-send dut_address dut_address="${dut_address}"
+ lava-sync finish_handover
+}
+
+# Counterpart to share_local_device_over_adb_tcpip
+# Wait for other job instances to send their device address, guarded by a handshake for
+# synchronization.
+# Globals:
+# None
+# Arguments:
+# adb_connect_timeout_secs Timeout for waiting for getting the IP address from the device
+# device_worker_mapping_file File to store a mapping between devices and their LAVA worker host
+# in the format 'serial_or_address;worker_host_id'. This file is relevant for following
+# functions to communicate with the devices or workers.
+# Optional: This file will not be created if no path is specified.
+# Returns:
+# 0 on success 1 otherwise.
+connect_to_remote_adb_tcpip_devices() {
+ [ "$#" -lt 1 -o "$#" -gt 2 ] && \
+ error_fatal "Usage: connect_to_remote_adb_tcpip_devices adb_connect_timeout_secs [device_worker_mapping_file]"
+
+ local adb_connect_timeout_secs="$1"
+ local device_worker_mapping_file="$2"
+
+ lava-sync start_handover
+ # For lava-wait-all, all involved nodes must invoke lava-send with the same message id,
+ # otherwise lava-wait-all would lead to a dead lock.
+ # However, only the nodes that make their device accessible (workers) add the value
+ # dut_address="address:port".
+ lava-send dut_address
+ lava-wait-all dut_address
+
+ # The MultiNode cache file might not exist if there is no other worker.
+ local cache_lines
+ local device_worker_mapping=""
+ if [ -f "/tmp/lava_multi_node_cache.txt" ]; then
+ cache_lines="$(cat "/tmp/lava_multi_node_cache.txt" | grep "dut_address" | grep -v '^$' || true)"
+
+ for line in ${cache_lines}; do
+ # <worker_job_id>:dut_address=<dut_address>
+ local dut_address="$(echo "$line"| sed 's/.*dut_address=//')"
+ local worker_host="$(echo "$line" | cut -d: -f1)"
+ device_worker_mapping="${device_worker_mapping}${dut_address};${worker_host}\n"
+ done
+ device_worker_mapping="$(printf "${device_worker_mapping}" | grep -v '^$' || true)"
+ fi
+
+ lava-sync finish_handover
+ # adb is not super reliable, it too often sees connected and authorized devices as "offline"
+ adb kill-server || true
+
+ # Connect to remote devices and wait until they appear online
+
+ for device_to_worker in ${device_worker_mapping}; do
+ local device="$(echo ${device_to_worker} | cut -d';' -f1)"
+ for i in $(seq 5); do
+ local ret_val=0
+ adb connect "${device}" || ret_val="$?"
+ if [ "${ret_val}" -eq 0 ]; then
+ break
+ fi
+ adb disconnect "${device}" || true
+ warn_msg "adb connect failed. Retrying in a minute..."
+ sleep 1m
+ done
+ done
+
+ for device_to_worker in ${device_worker_mapping}; do
+ local device="$(echo ${device_to_worker} | cut -d';' -f1)"
+ if ! timeout "${adb_connect_timeout_secs}" adb -s "${device}" wait-for-device; then
+ warn_msg "adb wait-for-device for ${device} timed out after ${adb_connect_timeout_secs} seconds."
+ return 1
+ fi
+ done
+
+ local num_remote_devices="$(echo "${device_worker_mapping}" | wc -l)"
+ info_msg "All ${num_remote_devices} remote devices are connected and online."
+
+ info_msg "Now adding devices locally connected via USB."
+ local connected_devices
+ local ret_val=0
+ connected_devices="$(adb devices | grep -E '^([:\.[:alnum:]]+)\s+device$' | cut -f1)" || ret_val=$?
+ if [ "${ret_val}" -ne 0 ]; then
+ warn_msg "\"adb devices\" did not exit cleanly. Cannot reliably determine the list of connected devices."
+ fi
+ local remote_only_mapping="${device_worker_mapping}"
+ device_worker_mapping="${device_worker_mapping}\n"
+ for device in ${connected_devices}; do
+ if [ "$(echo "${remote_only_mapping}" | cut -d';' -f1 | grep -xc "${device}")" -eq 0 ]; then
+ device_worker_mapping="${device_worker_mapping}${device};\n"
+ info_msg "Local device: ${device}"
+ fi
+ done
+ device_worker_mapping="$(printf "${device_worker_mapping}" | grep -v '^$')"
+
+ if [ "${device_worker_mapping_file}" ]; then
+ # Make mapping between attached DUTs and worker job ids and accessible to subsequent tests:
+ echo "${device_worker_mapping}" > "${device_worker_mapping_file}"
+ info_msg "Mapping between devices and to worker job ids stored in ${device_worker_mapping_file}:"
+ info_msg "${device_worker_mapping}"
+ else
+ info_msg "NOT storing device to worker job id mapping, empty filename specified."
+ fi
+}
diff --git a/automated/lib/android_adb_wrapper.py b/automated/lib/android_adb_wrapper.py
new file mode 100644
index 0000000..64fcbb1
--- /dev/null
+++ b/automated/lib/android_adb_wrapper.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+
+import re
+import subprocess
+import time
+
+
+ADB_DEVICES_PATTERN = re.compile(r"^([a-z0-9-]+)\s+device$", flags=re.M)
+
+
+class DeviceCommandError(BaseException):
+ """An error happened while sending a command to a device."""
+
+ def __init__(self, serial, command, error_message):
+ self.serial = serial
+ self.command = command
+ self.error_message = error_message
+ message = "Command `{}` failed on {}: {}".format(
+ command, serial, error_message
+ )
+ super(DeviceCommandError, self).__init__(message)
+
+
+def adb(*args, serial=None, raise_on_error=True):
+ """Run ADB command attached to serial.
+
+ Example:
+ >>> process = adb('shell', 'getprop', 'ro.build.fingerprint', serial='aserialnumber')
+ >>> process.returncode
+ 0
+ >>> process.stdout.strip()
+ 'ExampleVendor/Device/version/tags'
+
+ :param *args:
+ List of options to ADB (including command).
+ :param str serial:
+ Identifier for ADB connection to device.
+ :param raise_on_error bool:
+ Whether to raise a DeviceCommandError exception if the return code is
+ less than 0.
+ :returns subprocess.CompletedProcess:
+ Completed process.
+ :raises DeviceCommandError:
+ If the command failed.
+ """
+
+ # Make sure the adb server is started to avoid the infamous "out of date"
+ # message that pollutes stdout.
+ ret = subprocess.run(
+ ["adb", "start-server"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+ if ret.returncode < 0:
+ if raise_on_error:
+ raise DeviceCommandError(
+ serial if serial else "??", str(args), ret.stderr
+ )
+ else:
+ return None
+
+ command = ["adb"]
+ if serial:
+ command += ["-s", serial]
+ if args:
+ command += list(args)
+ ret = subprocess.run(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+
+ if raise_on_error and ret.returncode < 0:
+ raise DeviceCommandError(
+ serial if serial else "??", str(args), ret.stderr
+ )
+
+ return ret
+
+
+def list_devices():
+ """List serial numbers of devices attached to adb.
+
+ Raises:
+ DeviceCommandError: If the underlying adb command failed.
+ """
+ process = adb("devices")
+ return ADB_DEVICES_PATTERN.findall(process.stdout)
+
+
+def unlock(dut):
+ """Wake-up the device and unlock it.
+
+ Raises:
+ DeviceCommandError: If the underlying adb commands failed.
+ """
+ if not dut.info["screenOn"]:
+ adb("shell", "input keyevent KEYCODE_POWER", serial=dut.serial)
+ time.sleep(1)
+
+ # Make sure we are on the home screen.
+ adb("shell", "input keyevent KEYCODE_HOME", serial=dut.serial)
+ # The KEYCODE_MENU input is enough to unlock a "swipe up to unlock"
+ # lockscreen on Android 6, but unfortunately not Android 7. So we use a
+ # swipe up (that depends on the screen resolution) instead.
+ adb("shell", "input touchscreen swipe 930 880 930 380", serial=dut.serial)
+ time.sleep(1)
+ adb("shell", "input keyevent KEYCODE_HOME", serial=dut.serial)
diff --git a/automated/lib/android_ui_wifi.py b/automated/lib/android_ui_wifi.py
new file mode 100755
index 0000000..dd32166
--- /dev/null
+++ b/automated/lib/android_ui_wifi.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+
+from android_adb_wrapper import *
+import argparse
+import sys
+from uiautomator import Device
+
+
+def set_wifi_state(dut, turn_on):
+ """Turn WiFi on or off.
+
+ This checks the current WiFi settings and turns it on or off. It does
+ nothing if the settings are already in the desired state.
+
+ Parameters:
+ dut (Device): The device object.
+ enabled: Boolean, true for on, false for off
+ Raises:
+ DeviceCommandError: If the UI automation fails.
+ """
+ # Open the Wi-Fi settings
+ adb(
+ "shell",
+ ("am start -a android.settings.WIFI_SETTINGS " "--activity-clear-task"),
+ serial=dut.serial,
+ )
+
+ # Check if there is an option to turn WiFi on or off
+ wifi_enabler = dut(
+ text="OFF", resourceId="com.android.settings:id/switch_widget"
+ )
+ wifi_disabler = dut(
+ text="ON", resourceId="com.android.settings:id/switch_widget"
+ )
+
+ if not wifi_enabler.exists and not wifi_disabler.exists:
+ raise DeviceCommandError(
+ dut,
+ "UI: set Wi-Fi state",
+ "Neither switch for turning Wi-Fi on nor for turning it off are present.",
+ )
+ if wifi_enabler.exists and wifi_disabler.exists:
+ raise DeviceCommandError(
+ dut,
+ "UI: set Wi-Fi state",
+ "Unexpected UI: Both, a switch for turning Wi-Fi on and for turning it off are present.",
+ )
+
+ if turn_on:
+ if wifi_enabler.exists:
+ wifi_enabler.click()
+ else:
+ print("Wi-Fi is already enabled.")
+ else:
+ if wifi_disabler.exists:
+ wifi_disabler.click()
+ else:
+ print("Wi-Fi is already disabled.")
+
+ # Leave the settings
+ dut.press.back()
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-a",
+ dest="ACTION",
+ required=True,
+ nargs="+",
+ help="Action to perform. Following action is currently implemented: \
+ set_wifi_state <on|off>",
+ )
+ parser.add_argument(
+ "-s",
+ dest="SERIALS",
+ nargs="+",
+ help="Serial numbers of devices to configure. \
+ If not present, all available devices will be configured.",
+ )
+ args = parser.parse_args()
+
+ if args.ACTION[0] != "set_wifi_state" or args.ACTION[1] not in (
+ "on",
+ "off",
+ ):
+ print(
+ "ERROR: Specified ACTION is not supported: {}".format(args.ACTION),
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ serials = args.SERIALS if args.SERIALS is not None else list_devices()
+
+ for serial in serials:
+ print("Configuring device {}…".format(serial))
+
+ dut = Device(serial)
+ # Work around the not-so-easy Device class
+ dut.serial = serial
+
+ try:
+ unlock(dut)
+
+ set_wifi_state(dut, args.ACTION[1] == "on")
+
+ except DeviceCommandError as e:
+ print("ERROR {}".format(e), file=sys.stderr)
+
+
+if __name__ == "__main__":
+ main()