blob: 434bbd82cab7df601689cfcb4cf77bc3b9c6b368 [file] [log] [blame]
# Copyright 2019 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A tool that help to run adb to check device status."""
import re
import subprocess
from acloud import errors
from acloud.internal import constants
from acloud.internal.lib import utils
_ADB_CONNECT = "connect"
_ADB_DEVICE = "devices"
_ADB_DISCONNECT = "disconnect"
_ADB_STATUS_DEVICE = "device"
_ADB_STATUS_DEVICE_ARGS = "-l"
_RE_ADB_DEVICE_INFO = (r"%s\s*(?P<adb_status>[\S]+)? ?"
r"(usb:(?P<usb>[\S]+))? ?"
r"(product:(?P<product>[\S]+))? ?"
r"(model:(?P<model>[\S]+))? ?"
r"(device:(?P<device>[\S]+))? ?"
r"(transport_id:(?P<transport_id>[\S]+))? ?")
_DEVICE_ATTRIBUTES = ["adb_status", "usb", "product", "model", "device", "transport_id"]
_MAX_RETRIES_ON_WAIT_ADB_GONE = 5
#KEY_CODE 82 = KEY_MENU
_UNLOCK_SCREEN_KEYEVENT = ("%(adb_bin)s -s %(device_serial)s "
"shell input keyevent 82")
_WAIT_ADB_RETRY_BACKOFF_FACTOR = 1.5
_WAIT_ADB_SLEEP_MULTIPLIER = 2
class AdbTools(object):
"""Adb tools.
Attributes:
_adb_command: String, combine adb commands then execute it.
_adb_port: Integer, Specified adb port to establish connection.
_device_address: String, the device's host and port for adb to connect
to. For example, adb connect 127.0.0.1:5555.
_device_serial: String, adb device's serial number. The value can be
different from _device_address. For example,
adb -s emulator-5554 shell.
_device_information: Dict, will be added to adb information include usb,
product model, device and transport_id
"""
def __init__(self, adb_port=None, device_serial=""):
"""Initialize.
Args:
adb_port: String of adb port number.
device_serial: String, adb device's serial number.
"""
self._adb_command = ""
self._adb_port = adb_port
self._device_address = ""
self._device_serial = ""
self._SetDeviceSerial(device_serial)
self._device_information = {}
self._CheckAdb()
self._GetAdbInformation()
def _SetDeviceSerial(self, device_serial):
"""Set device serial and address.
Args:
device_serial: String, the device's serial number. If this
argument is empty, the serial number is set to the
network address.
"""
self._device_address = ("127.0.0.1:%s" % self._adb_port if
self._adb_port else "")
self._device_serial = (device_serial if device_serial else
self._device_address)
def _CheckAdb(self):
"""Find adb bin path.
Raises:
errors.NoExecuteCmd: Can't find the execute adb bin.
"""
self._adb_command = utils.FindExecutable(constants.ADB_BIN)
if not self._adb_command:
raise errors.NoExecuteCmd("Can't find the adb command.")
def GetAdbConnectionStatus(self):
"""Get Adb connect status.
Check if self._adb_port is null (ssh tunnel is broken).
Returns:
String, the result of adb connection.
"""
if not self._adb_port:
return None
return self._device_information["adb_status"]
def _GetAdbInformation(self):
"""Get Adb connect information.
1. Check adb devices command to get the connection information.
2. Gather information include usb, product model, device and transport_id
when the attached field is device.
e.g.
Case 1:
List of devices attached
127.0.0.1:48451 device product:aosp_cf model:Cuttlefish device:vsoc_x86 transport_id:147
_device_information = {"adb_status":"device",
"usb":None,
"product":"aosp_cf",
"model":"Cuttlefish",
"device":"vsoc_x86",
"transport_id":"147"}
Case 2:
List of devices attached
127.0.0.1:48451 offline
_device_information = {"adb_status":"offline",
"usb":None,
"product":None,
"model":None,
"device":None,
"transport_id":None}
Case 3:
List of devices attached
_device_information = {"adb_status":None,
"usb":None,
"product":None,
"model":None,
"device":None,
"transport_id":None}
"""
adb_cmd = [self._adb_command, _ADB_DEVICE, _ADB_STATUS_DEVICE_ARGS]
device_info = subprocess.check_output(adb_cmd)
self._device_information = {
attribute: None for attribute in _DEVICE_ATTRIBUTES}
for device in device_info.splitlines():
match = re.match(_RE_ADB_DEVICE_INFO % self._device_serial, device)
if match:
self._device_information = {
attribute: match.group(attribute) if match.group(attribute)
else None for attribute in _DEVICE_ATTRIBUTES}
def IsAdbConnectionAlive(self):
"""Check devices connect alive.
Returns:
Boolean, True if adb status is device. False otherwise.
"""
return self.GetAdbConnectionStatus() == _ADB_STATUS_DEVICE
def IsAdbConnected(self):
"""Check devices connected or not.
If adb connected and the status is device or offline, return True.
If there is no any connection, return False.
Returns:
Boolean, True if adb status not none. False otherwise.
"""
return self.GetAdbConnectionStatus() is not None
def _DisconnectAndRaiseError(self):
"""Disconnect adb.
Disconnect from the device's network address if it shows up in adb
devices. For example, adb disconnect 127.0.0.1:5555.
Raises:
errors.WaitForAdbDieError: adb is alive after disconnect adb.
"""
try:
if self.IsAdbConnected():
adb_disconnect_args = [self._adb_command,
_ADB_DISCONNECT,
self._device_address]
subprocess.check_call(adb_disconnect_args)
# check adb device status
self._GetAdbInformation()
if self.IsAdbConnected():
raise errors.AdbDisconnectFailed(
"adb disconnect failed, device is still connected and "
"has status: [%s]" % self.GetAdbConnectionStatus())
except subprocess.CalledProcessError:
utils.PrintColorString("Failed to adb disconnect %s" %
self._device_address,
utils.TextColors.FAIL)
def DisconnectAdb(self, retry=False):
"""Retry to disconnect adb.
When retry=True, this method will retry to disconnect adb until adb
device is completely gone.
Args:
retry: Boolean, True to retry disconnect on error.
"""
retry_count = _MAX_RETRIES_ON_WAIT_ADB_GONE if retry else 0
# Wait for adb device is reset and gone.
utils.RetryExceptionType(exception_types=errors.AdbDisconnectFailed,
max_retries=retry_count,
functor=self._DisconnectAndRaiseError,
sleep_multiplier=_WAIT_ADB_SLEEP_MULTIPLIER,
retry_backoff_factor=
_WAIT_ADB_RETRY_BACKOFF_FACTOR)
def ConnectAdb(self):
"""Connect adb.
Connect adb to the device's network address if the connection is not
alive. For example, adb connect 127.0.0.1:5555.
"""
try:
if not self.IsAdbConnectionAlive():
adb_connect_args = [self._adb_command,
_ADB_CONNECT,
self._device_address]
subprocess.check_call(adb_connect_args)
except subprocess.CalledProcessError:
utils.PrintColorString("Failed to adb connect %s" %
self._device_address,
utils.TextColors.FAIL)
def AutoUnlockScreen(self):
"""Auto unlock screen.
Auto unlock screen after invoke vnc client.
"""
try:
adb_unlock_args = _UNLOCK_SCREEN_KEYEVENT % {
"adb_bin": self._adb_command,
"device_serial": self._device_serial}
subprocess.check_call(adb_unlock_args.split())
except subprocess.CalledProcessError:
utils.PrintColorString("Failed to unlock screen."
"(adb_port: %s)" % self._adb_port,
utils.TextColors.WARNING)
def EmuCommand(self, *args):
"""Send an emulator command to the device.
Args:
args: List of strings, the emulator command.
Returns:
Integer, the return code of the adb command.
The return code is 0 if adb successfully sends the command to
emulator. It is irrelevant to the result of the command.
"""
adb_cmd = [self._adb_command, "-s", self._device_serial, "emu"]
adb_cmd.extend(args)
proc = subprocess.Popen(adb_cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.communicate()
return proc.returncode
@property
def device_information(self):
"""Return the device information."""
return self._device_information