1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2022 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li# 6*9c5db199SXin Li# Expects to be run in an environment with sudo and no interactive password 7*9c5db199SXin Li# prompt, such as within the Chromium OS development chroot. 8*9c5db199SXin Li 9*9c5db199SXin Liimport logging 10*9c5db199SXin Liimport socket 11*9c5db199SXin Li 12*9c5db199SXin Liimport common 13*9c5db199SXin Li 14*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 15*9c5db199SXin Lifrom autotest_lib.server.hosts import host_info 16*9c5db199SXin Lifrom autotest_lib.server.hosts import attached_device_host 17*9c5db199SXin Lifrom autotest_lib.server.hosts import android_constants 18*9c5db199SXin Lifrom autotest_lib.server.hosts import base_classes 19*9c5db199SXin Li 20*9c5db199SXin Li 21*9c5db199SXin Liclass AndroidHost(base_classes.Host): 22*9c5db199SXin Li """Host class for Android devices""" 23*9c5db199SXin Li PHONE_STATION_LABEL_PREFIX = "associated_hostname" 24*9c5db199SXin Li SERIAL_NUMBER_LABEL_PREFIX = "serial_number" 25*9c5db199SXin Li # adb auth key path on the phone_station. 26*9c5db199SXin Li ADB_KEY_PATH = '/var/lib/android_keys' 27*9c5db199SXin Li 28*9c5db199SXin Li def __init__(self, 29*9c5db199SXin Li hostname, 30*9c5db199SXin Li host_info_store=None, 31*9c5db199SXin Li android_args=None, 32*9c5db199SXin Li *args, 33*9c5db199SXin Li **dargs): 34*9c5db199SXin Li """Construct a AndroidHost object. 35*9c5db199SXin Li 36*9c5db199SXin Li Args: 37*9c5db199SXin Li hostname: Hostname of the Android phone. 38*9c5db199SXin Li host_info_store: Optional host_info.CachingHostInfoStore object 39*9c5db199SXin Li to obtain / update host information. 40*9c5db199SXin Li android_args: Android args for local test run. 41*9c5db199SXin Li """ 42*9c5db199SXin Li self.hostname = hostname 43*9c5db199SXin Li super(AndroidHost, self).__init__(*args, **dargs) 44*9c5db199SXin Li self.host_info_store = (host_info_store 45*9c5db199SXin Li or host_info.InMemoryHostInfoStore()) 46*9c5db199SXin Li self.associated_hostname = None 47*9c5db199SXin Li self.serial_number = None 48*9c5db199SXin Li self.phone_station_ssh_port = None 49*9c5db199SXin Li # For local test, android_args are passed in. 50*9c5db199SXin Li if android_args: 51*9c5db199SXin Li self._read_essential_data_from_args_dict(android_args) 52*9c5db199SXin Li else: 53*9c5db199SXin Li self._read_essential_data_from_host_info_store() 54*9c5db199SXin Li # Since we won't be ssh into an Android device directly, all the 55*9c5db199SXin Li # communication will be handled by run ADB CLI on the phone 56*9c5db199SXin Li # station(chromebox or linux machine) that physically connected 57*9c5db199SXin Li # to the Android devices via USB cable. So we need to setup an 58*9c5db199SXin Li # AttachedDeviceHost for phone station as ssh proxy. 59*9c5db199SXin Li self.phone_station = self._create_phone_station_host_proxy() 60*9c5db199SXin Li self.adb_tcp_mode = False 61*9c5db199SXin Li self.usb_dev_path = None 62*9c5db199SXin Li self.closed = False 63*9c5db199SXin Li 64*9c5db199SXin Li def _create_phone_station_host_proxy(self): 65*9c5db199SXin Li logging.info('Creating host for phone station %s', 66*9c5db199SXin Li self.associated_hostname) 67*9c5db199SXin Li return attached_device_host.AttachedDeviceHost( 68*9c5db199SXin Li hostname=self.associated_hostname, 69*9c5db199SXin Li serial_number=self.serial_number, 70*9c5db199SXin Li phone_station_ssh_port=self.phone_station_ssh_port) 71*9c5db199SXin Li 72*9c5db199SXin Li def _read_essential_data_from_args_dict(self, android_args): 73*9c5db199SXin Li self.associated_hostname = android_args.get( 74*9c5db199SXin Li android_constants.ANDROID_PHONE_STATION_ATTR) 75*9c5db199SXin Li self.phone_station_ssh_port = android_args.get( 76*9c5db199SXin Li android_constants.ANDROID_PHONE_STATION_SSH_PORT_ATTR) 77*9c5db199SXin Li self.serial_number = android_args.get( 78*9c5db199SXin Li android_constants.ANDROID_SERIAL_NUMBER_ATTR) 79*9c5db199SXin Li 80*9c5db199SXin Li def _read_essential_data_from_host_info_store(self): 81*9c5db199SXin Li info = self.host_info_store.get() 82*9c5db199SXin Li self.associated_hostname = info.get_label_value( 83*9c5db199SXin Li self.PHONE_STATION_LABEL_PREFIX) 84*9c5db199SXin Li if not self.associated_hostname: 85*9c5db199SXin Li raise error.AutoservError( 86*9c5db199SXin Li 'Failed to initialize Android host due to' 87*9c5db199SXin Li ' associated_hostname is not found in host_info_store.') 88*9c5db199SXin Li self.serial_number = info.get_label_value( 89*9c5db199SXin Li self.SERIAL_NUMBER_LABEL_PREFIX) 90*9c5db199SXin Li if not self.serial_number: 91*9c5db199SXin Li raise error.AutoservError( 92*9c5db199SXin Li 'Failed to initialize Android host due to' 93*9c5db199SXin Li ' serial_number is not found in host_info_store.') 94*9c5db199SXin Li 95*9c5db199SXin Li def adb_over_tcp(self, port=5555, persist_reboot=False): 96*9c5db199SXin Li """Restart adb server listening on a TCP port. 97*9c5db199SXin Li 98*9c5db199SXin Li Args: 99*9c5db199SXin Li port: Tcp port for adb server to listening on, default value 100*9c5db199SXin Li is 5555 which is the default TCP/IP port for adb. 101*9c5db199SXin Li persist_reboot: True for adb over tcp to continue listening 102*9c5db199SXin Li after the device reboots. 103*9c5db199SXin Li """ 104*9c5db199SXin Li port = str(port) 105*9c5db199SXin Li if persist_reboot: 106*9c5db199SXin Li self.run_adb_command('shell setprop persist.adb.tcp.port %s' % 107*9c5db199SXin Li port) 108*9c5db199SXin Li self.run_adb_command('shell setprop ctl.restart adbd') 109*9c5db199SXin Li self.wait_for_transport_state() 110*9c5db199SXin Li 111*9c5db199SXin Li self.run_adb_command('tcpip %s' % port) 112*9c5db199SXin Li self.adb_tcp_mode = True 113*9c5db199SXin Li 114*9c5db199SXin Li def cache_usb_dev_path(self): 115*9c5db199SXin Li """ 116*9c5db199SXin Li Read and cache usb devpath for the Android device. 117*9c5db199SXin Li """ 118*9c5db199SXin Li cmd = 'adb devices -l | grep %s' % self.serial_number 119*9c5db199SXin Li res = self.phone_station.run(cmd) 120*9c5db199SXin Li for line in res.stdout.strip().split('\n'): 121*9c5db199SXin Li if len(line.split()) > 2 and line.split()[1] == 'device': 122*9c5db199SXin Li self.usb_dev_path = line.split()[2] 123*9c5db199SXin Li logging.info('USB devpath: %s', self.usb_dev_path) 124*9c5db199SXin Li break 125*9c5db199SXin Li if not self.usb_dev_path: 126*9c5db199SXin Li logging.warning( 127*9c5db199SXin Li 'Failed to collect usbdev path of the Android device.') 128*9c5db199SXin Li 129*9c5db199SXin Li def ensure_device_connectivity(self): 130*9c5db199SXin Li """Ensure we can interact with the Android device via adb and 131*9c5db199SXin Li the device is in the expected state. 132*9c5db199SXin Li """ 133*9c5db199SXin Li res = self.run_adb_command('get-state') 134*9c5db199SXin Li state = res.stdout.strip() 135*9c5db199SXin Li logging.info('Android device state from adb: %s', state) 136*9c5db199SXin Li return state == 'device' 137*9c5db199SXin Li 138*9c5db199SXin Li def get_wifi_ip_address(self): 139*9c5db199SXin Li """Get ipv4 address from the Android device""" 140*9c5db199SXin Li res = self.run_adb_command('shell ip route') 141*9c5db199SXin Li # An example response would looks like: "192.168.86.0/24 dev wlan0" 142*9c5db199SXin Li # " proto kernel scope link src 192.168.86.22 \n" 143*9c5db199SXin Li ip_string = res.stdout.strip().split(' ')[-1] 144*9c5db199SXin Li logging.info('Ip address collected from the Android device: %s', 145*9c5db199SXin Li ip_string) 146*9c5db199SXin Li try: 147*9c5db199SXin Li socket.inet_aton(ip_string) 148*9c5db199SXin Li except (OSError, ValueError, socket.error): 149*9c5db199SXin Li raise error.AutoservError( 150*9c5db199SXin Li 'Failed to get ip address from the Android device.') 151*9c5db199SXin Li return ip_string 152*9c5db199SXin Li 153*9c5db199SXin Li def job_start(self): 154*9c5db199SXin Li """This method is called from create_host factory when 155*9c5db199SXin Li construct the host object. We need to override it since actions 156*9c5db199SXin Li like copy /var/log/messages are not applicable on Android devices. 157*9c5db199SXin Li """ 158*9c5db199SXin Li logging.info('Skip standard job_start actions for Android host.') 159*9c5db199SXin Li 160*9c5db199SXin Li def restart_adb_server(self): 161*9c5db199SXin Li """Restart adb server from the phone station""" 162*9c5db199SXin Li self.stop_adb_server() 163*9c5db199SXin Li self.start_adb_server() 164*9c5db199SXin Li 165*9c5db199SXin Li def run_adb_command(self, adb_command, ignore_status=False): 166*9c5db199SXin Li """Run adb command on the Android device. 167*9c5db199SXin Li 168*9c5db199SXin Li Args: 169*9c5db199SXin Li adb_command: adb commands to execute on the Android device. 170*9c5db199SXin Li 171*9c5db199SXin Li Returns: 172*9c5db199SXin Li An autotest_lib.client.common_lib.utils.CmdResult object. 173*9c5db199SXin Li """ 174*9c5db199SXin Li # When use adb to interact with an Android device, we prefer to use 175*9c5db199SXin Li # devpath to distinguish the particular device as the serial number 176*9c5db199SXin Li # is not guaranteed to be unique. 177*9c5db199SXin Li if self.usb_dev_path: 178*9c5db199SXin Li command = 'adb -s %s %s' % (self.usb_dev_path, adb_command) 179*9c5db199SXin Li else: 180*9c5db199SXin Li command = 'adb -s %s %s' % (self.serial_number, adb_command) 181*9c5db199SXin Li return self.phone_station.run(command, ignore_status=ignore_status) 182*9c5db199SXin Li 183*9c5db199SXin Li def wait_for_transport_state(self, transport='usb', state='device'): 184*9c5db199SXin Li """ 185*9c5db199SXin Li Wait for a device to reach a desired state. 186*9c5db199SXin Li 187*9c5db199SXin Li Args: 188*9c5db199SXin Li transport: usb, local, any 189*9c5db199SXin Li state: device, recovery, sideload, bootloader 190*9c5db199SXin Li 191*9c5db199SXin Li """ 192*9c5db199SXin Li self.run_adb_command('wait-for-%s-%s' % (transport, state)) 193*9c5db199SXin Li 194*9c5db199SXin Li def start_adb_server(self): 195*9c5db199SXin Li """Start adb server from the phone station.""" 196*9c5db199SXin Li # Adb home is created upon CrOS login, however on labstation we 197*9c5db199SXin Li # never login so we'll need to ensure the adb home is exist before 198*9c5db199SXin Li # starting adb server. 199*9c5db199SXin Li self.phone_station.run("mkdir -p /run/arc/adb") 200*9c5db199SXin Li self.phone_station.run("ADB_VENDOR_KEYS=%s adb start-server" % 201*9c5db199SXin Li self.ADB_KEY_PATH) 202*9c5db199SXin Li # Logging states of all attached devices. 203*9c5db199SXin Li self.phone_station.run('adb devices') 204*9c5db199SXin Li 205*9c5db199SXin Li def stop_adb_server(self): 206*9c5db199SXin Li """Stop adb server from the phone station.""" 207*9c5db199SXin Li self.phone_station.run("adb kill-server") 208*9c5db199SXin Li 209*9c5db199SXin Li def setup_for_cross_device_tests(self, adb_persist_reboot=False): 210*9c5db199SXin Li """ 211*9c5db199SXin Li Setup the Android phone for Cross Device tests. 212*9c5db199SXin Li 213*9c5db199SXin Li Ensures the phone can connect to its labstation and sets up 214*9c5db199SXin Li adb-over-tcp. 215*9c5db199SXin Li 216*9c5db199SXin Li Returns: 217*9c5db199SXin Li IP Address of Phone. 218*9c5db199SXin Li """ 219*9c5db199SXin Li dut_out = self.phone_station.run('echo True').stdout.strip() 220*9c5db199SXin Li if dut_out != 'True': 221*9c5db199SXin Li raise error.TestError('phone station stdout != True (got: %s)', 222*9c5db199SXin Li dut_out) 223*9c5db199SXin Li 224*9c5db199SXin Li self.restart_adb_server() 225*9c5db199SXin Li self.cache_usb_dev_path() 226*9c5db199SXin Li self.ensure_device_connectivity() 227*9c5db199SXin Li ip_address = self.get_wifi_ip_address() 228*9c5db199SXin Li self.adb_over_tcp(persist_reboot=adb_persist_reboot) 229*9c5db199SXin Li return ip_address 230*9c5db199SXin Li 231*9c5db199SXin Li def close(self): 232*9c5db199SXin Li """Clean up Android host and its phone station proxy host.""" 233*9c5db199SXin Li if self.closed: 234*9c5db199SXin Li logging.debug('Android host %s already closed.', self.hostname) 235*9c5db199SXin Li return 236*9c5db199SXin Li try: 237*9c5db199SXin Li if self.adb_tcp_mode: 238*9c5db199SXin Li # In some rare cases, leave the Android device in adb over tcp 239*9c5db199SXin Li # mode may break USB connection so we want to always reset adb 240*9c5db199SXin Li # to usb mode before teardown. 241*9c5db199SXin Li self.run_adb_command('usb', ignore_status=True) 242*9c5db199SXin Li self.stop_adb_server() 243*9c5db199SXin Li if self.phone_station: 244*9c5db199SXin Li self.phone_station.close() 245*9c5db199SXin Li self.closed = True 246*9c5db199SXin Li finally: 247*9c5db199SXin Li super(AndroidHost, self).close() 248*9c5db199SXin Li 249*9c5db199SXin Li @staticmethod 250*9c5db199SXin Li def get_android_arguments(args_dict): 251*9c5db199SXin Li """Extract android args from `args_dict` and return the result. 252*9c5db199SXin Li 253*9c5db199SXin Li Recommended usage in control file: 254*9c5db199SXin Li args_dict = utils.args_to_dict(args) 255*9c5db199SXin Li android_args = hosts.Android.get_android_arguments(args_dict) 256*9c5db199SXin Li host = hosts.create_host(machine, android_args=android_args) 257*9c5db199SXin Li 258*9c5db199SXin Li Args: 259*9c5db199SXin Li args_dict: A dict of test args. 260*9c5db199SXin Li 261*9c5db199SXin Li Returns: 262*9c5db199SXin Li An dict of android related args. 263*9c5db199SXin Li """ 264*9c5db199SXin Li android_args = { 265*9c5db199SXin Li key: args_dict[key] 266*9c5db199SXin Li for key in android_constants.ALL_ANDROID_ATTRS 267*9c5db199SXin Li if key in args_dict 268*9c5db199SXin Li } 269*9c5db199SXin Li for attr in android_constants.CRITICAL_ANDROID_ATTRS: 270*9c5db199SXin Li if attr not in android_args or not android_args.get(attr): 271*9c5db199SXin Li raise error.AutoservError("Critical attribute %s is missing" 272*9c5db199SXin Li " from android_args." % attr) 273*9c5db199SXin Li return android_args 274