Integrate HAL client into AndroidDevice class.
Clean up AndroidDevice class.
Reorg existing mirror modules.
Create a manager class for HAL-level mirror objects.
Integrate the manager class in AndroidDevice.
Bug=29119971
Change-Id: I3d5d2b55128193385977de982af2244b595c2a95
diff --git a/utils/python/controllers/android_device.py b/utils/python/controllers/android_device.py
index 01a2368..e7c33ca 100644
--- a/utils/python/controllers/android_device.py
+++ b/utils/python/controllers/android_device.py
@@ -17,6 +17,7 @@
from builtins import str
from builtins import open
+import logging
import os
import time
import traceback
@@ -24,10 +25,11 @@
from vts.runners.host import logger as vts_logger
from vts.runners.host import signals
from vts.runners.host import utils
-from vts.runners.utils.pythoncontrollers import adb
-from vts.runners.utils.pythoncontrollers import android
-from vts.runners.utils.pythoncontrollers import event_dispatcher
-from vts.runners.utils.pythoncontrollers import fastboot
+from vts.utils.python.controllers import adb
+from vts.utils.python.controllers import event_dispatcher
+from vts.utils.python.controllers import fastboot
+
+from vts.utils.python.mirror import hal_mirror
VTS_CONTROLLER_CONFIG_NAME = "AndroidDevice"
VTS_CONTROLLER_REFERENCE_NAME = "android_devices"
@@ -43,19 +45,19 @@
pass
-def create(configs, logger):
+def create(configs):
if not configs:
raise AndroidDeviceError(ANDROID_DEVICE_EMPTY_CONFIG_MSG)
elif configs == ANDROID_DEVICE_PICK_ALL_TOKEN:
- ads = get_all_instances(logger=logger)
+ ads = get_all_instances()
elif not isinstance(configs, list):
raise AndroidDeviceError(ANDROID_DEVICE_NOT_LIST_CONFIG_MSG)
elif isinstance(configs[0], str):
# Configs is a list of serials.
- ads = get_instances(configs, logger)
+ ads = get_instances(configs)
else:
# Configs is a list of dicts.
- ads = get_instances_with_configs(configs, logger)
+ ads = get_instances_with_configs(configs)
connected_ads = list_adb_devices()
for ad in ads:
if ad.serial not in connected_ads:
@@ -63,16 +65,6 @@
("Android device %s is specified in config"
" but is not attached.") % ad.serial)
ad.startAdbLogcat()
- try:
- ad.getSl4aClient()
- ad.ed.start()
- except:
- # This exception is logged here to help with debugging under py2,
- # because "exception raised while processing another exception" is
- # only printed under py3.
- msg = "Failed to start sl4a on %s" % ad.serial
- logger.exception(msg)
- raise AndroidDeviceError(msg)
return ads
@@ -128,23 +120,22 @@
return _parse_device_list(out, "fastboot")
-def get_instances(serials, logger=None):
+def get_instances(serials):
"""Create AndroidDevice instances from a list of serials.
Args:
serials: A list of android device serials.
- logger: A logger to be passed to each instance.
Returns:
A list of AndroidDevice objects.
"""
results = []
for s in serials:
- results.append(AndroidDevice(s, logger=logger))
+ results.append(AndroidDevice(s))
return results
-def get_instances_with_configs(configs, logger=None):
+def get_instances_with_configs(configs):
"""Create AndroidDevice instances from a list of json configs.
Each config should have the required key-value pair "serial".
@@ -152,7 +143,6 @@
Args:
configs: A list of dicts each representing the configuration of one
android device.
- logger: A logger to be passed to each instance.
Returns:
A list of AndroidDevice objects.
@@ -164,18 +154,17 @@
except KeyError:
raise AndroidDeviceError(('Required value "serial" is missing in '
'AndroidDevice config %s.') % c)
- ad = AndroidDevice(serial, logger=logger)
+ ad = AndroidDevice(serial)
ad.loadConfig(c)
results.append(ad)
return results
-def get_all_instances(include_fastboot=False, logger=None):
+def get_all_instances(include_fastboot=False):
"""Create AndroidDevice instances for all attached android devices.
Args:
include_fastboot: Whether to include devices in bootloader mode or not.
- logger: A logger to be passed to each instance.
Returns:
A list of AndroidDevice objects each representing an android device
@@ -183,8 +172,8 @@
"""
if include_fastboot:
serial_list = list_adb_devices() + list_fastboot_devices()
- return get_instances(serial_list, logger=logger)
- return get_instances(list_adb_devices(), logger=logger)
+ return get_instances(serial_list)
+ return get_instances(list_adb_devices())
def filter_devices(ads, func):
@@ -267,7 +256,7 @@
utils.concurrent_exec(take_br, args)
-class AndroidDevice:
+class AndroidDevice(object):
"""Class representing an android device.
Each object of this class represents one Android device in ACTS, including
@@ -277,11 +266,8 @@
Attributes:
serial: A string that's the serial number of the Androi device.
- h_port: An integer that's the port number for adb port forwarding used
- on the computer the Android device is connected
d_port: An integer that's the port number used on the Android device
for adb port forwarding.
- log: A LoggerProxy object used for the class's internal logging.
log_path: A string that is the path where all logs collected on this
android device should be stored.
adb_logcat_process: A process that collects the adb logcat.
@@ -292,29 +278,25 @@
via fastboot.
"""
- def __init__(self,
- serial="",
- host_port=None,
- device_port=8080,
- logger=None):
+ def __init__(self, serial="", device_port=5001):
self.serial = serial
- self.h_port = host_port
- self.d_port = device_port
+ self.device_port = device_port
self.log = logging.getLogger()
- lp = self.log.log_path
- self.log_path = os.path.join(lp, "AndroidDevice%s" % serial)
- self._droid_sessions = {}
- self._event_dispatchers = {}
+ base_log_path = getattr(self.log, "log_path", "/tmp/logs/")
+ self.log_path = os.path.join(base_log_path, "AndroidDevice%s" % serial)
self.adb_logcat_process = None
self.adb_logcat_file_path = None
self.adb = adb.AdbProxy(serial)
self.fastboot = fastboot.FastbootProxy(serial)
if not self.isBootloaderMode:
self.rootAdb()
+ self.host_port = adb.get_available_host_port()
+ self.adb.tcp_forward(self.host_port, self.device_port)
+ self.hal = hal_mirror.HalMirror(self.host_port)
def __del__(self):
- if self.h_port:
- self.adb.forward("--remove tcp:%d" % self.h_port)
+ if self.host_port:
+ self.adb.forward("--remove tcp:%s" % self.host_port)
if self.adb_logcat_process:
self.stopAdbLogcat()
@@ -355,53 +337,6 @@
return model
@property
- def droid(self):
- """The first sl4a session initiated on this device. None if there isn't
- one.
- """
- try:
- session_id = sorted(self._droid_sessions)[0]
- return self._droid_sessions[session_id][0]
- except IndexError:
- return None
-
- @property
- def ed(self):
- """The first event_dispatcher instance created on this device. None if
- there isn't one.
- """
- try:
- session_id = sorted(self._event_dispatchers)[0]
- return self._event_dispatchers[session_id]
- except IndexError:
- return None
-
- @property
- def droids(self):
- """A list of the active sl4a sessions on this device.
-
- If multiple connections exist for the same session, only one connection
- is listed.
- """
- keys = sorted(self._droid_sessions)
- results = []
- for k in keys:
- results.append(self._droid_sessions[k][0])
- return results
-
- @property
- def eds(self):
- """A list of the event_dispatcher objects on this device.
-
- The indexing of the list matches that of the droids property.
- """
- keys = sorted(self._event_dispatchers)
- results = []
- for k in keys:
- results.append(self._event_dispatchers[k])
- return results
-
- @property
def isAdbLogcatOn(self):
"""Whether there is an ongoing adb logcat collection.
"""
@@ -421,9 +356,9 @@
"""
for k, v in config.items():
if hasattr(self, k):
- raise AndroidDeviceError(("Attempting to set existing "
- "attribute %s on %s") %
- (k, self.serial))
+ raise AndroidDeviceError(
+ "Attempting to set existing attribute %s on %s" %
+ (k, self.serial))
setattr(self, k, v)
def rootAdb(self):
@@ -435,138 +370,24 @@
self.adb.remount()
self.adb.wait_for_device()
- def getSl4aClient(self, handle_event=True):
- """Create an sl4a connection to the device.
-
- Return the connection handler 'droid'. By default, another connection
- on the same session is made for EventDispatcher, and the dispatcher is
- returned to the caller as well.
- If sl4a server is not started on the device, try to start it.
-
- Args:
- handle_event: True if this droid session will need to handle
- events.
-
- Returns:
- droid: Android object used to communicate with sl4a on the android
- device.
- ed: An optional EventDispatcher to organize events for this droid.
-
- Examples:
- Don't need event handling:
- >>> ad = AndroidDevice()
- >>> droid = ad.getSl4aClient(False)
-
- Need event handling:
- >>> ad = AndroidDevice()
- >>> droid, ed = ad.getSl4aClient()
- """
- if not self.h_port or not adb.is_port_available(self.h_port):
- self.h_port = adb.get_available_host_port()
- self.adb.tcp_forward(self.h_port, self.d_port)
- try:
- droid = self.start_new_session()
- except:
- self.adb.start_sl4a()
- droid = self.start_new_session()
- if handle_event:
- ed = self.getSl4aEventDispatcher(droid)
- return droid, ed
- return droid
-
- def getSl4aEventDispatcher(self, droid):
- """Return an EventDispatcher for an sl4a session
-
- Args:
- droid: Session to create EventDispatcher for.
-
- Returns:
- ed: An EventDispatcher for specified session.
- """
- ed_key = self.serial + str(droid.uid)
- if ed_key in self._event_dispatchers:
- if self._event_dispatchers[ed_key] is None:
- raise AndroidDeviceError("EventDispatcher Key Empty")
- self.log.debug("Returning existing key %s for event dispatcher!",
- ed_key)
- return self._event_dispatchers[ed_key]
- event_droid = self.add_new_connection_to_session(droid.uid)
- ed = event_dispatcher.EventDispatcher(event_droid)
- self._event_dispatchers[ed_key] = ed
- return ed
-
- def _is_timestamp_in_range(self, target, begin_time, end_time):
- low = vts_logger.logLineTimestampComparator(begin_time, target) <= 0
- high = vts_logger.logLineTimestampComparator(end_time, target) >= 0
- return low and high
-
- def takeAdbLogExcerpt(self, tag, begin_time):
- """Takes an excerpt of the adb logcat log from a certain time point to
- current time.
-
- Args:
- tag: An identifier of the time period, usualy the name of a test.
- begin_time: Logline format timestamp of the beginning of the time
- period.
- """
- if not self.adb_logcat_file_path:
- raise AndroidDeviceError(
- ("Attempting to cat adb log when none has"
- " been collected on Android device %s.") % self.serial)
- end_time = vts_logger.getLogLineTimestamp()
- self.log.debug("Extracting adb log from logcat.")
- adb_excerpt_path = os.path.join(self.log_path, "AdbLogExcerpts")
- utils.create_dir(adb_excerpt_path)
- f_name = os.path.basename(self.adb_logcat_file_path)
- out_name = f_name.replace("adblog,", "").replace(".txt", "")
- out_name = ",{},{}.txt".format(begin_time, out_name)
- tag_len = utils.MAX_FILENAME_LEN - len(out_name)
- tag = tag[:tag_len]
- out_name = tag + out_name
- full_adblog_path = os.path.join(adb_excerpt_path, out_name)
- with open(full_adblog_path, 'w', encoding='utf-8') as out:
- in_file = self.adb_logcat_file_path
- with open(in_file, 'r', encoding='utf-8', errors='replace') as f:
- in_range = False
- while True:
- line = None
- try:
- line = f.readline()
- if not line:
- break
- except:
- continue
- line_time = line[:vts_logger.log_line_timestamp_len]
- if not vts_logger.isValidLogLineTimestamp(line_time):
- continue
- if self._is_timestamp_in_range(line_time, begin_time,
- end_time):
- in_range = True
- if not line.endswith('\n'):
- line += '\n'
- out.write(line)
- else:
- if in_range:
- break
-
def startAdbLogcat(self):
"""Starts a standing adb logcat collection in separate subprocesses and
save the logcat in a file.
"""
if self.isAdbLogcatOn:
- raise AndroidDeviceError(("Android device {} already has an adb "
+ raise AndroidDeviceError(("Android device %s already has an adb "
"logcat thread going on. Cannot start "
- "another one.").format(self.serial))
+ "another one.") % self.serial)
# Disable adb log spam filter.
self.adb.shell("logpersist.start")
- f_name = "adblog,{},{}.txt".format(self.model, self.serial)
+ f_name = "adblog,%s,%s.txt" % (self.model, self.serial)
utils.create_dir(self.log_path)
logcat_file_path = os.path.join(self.log_path, f_name)
try:
extra_params = self.adb_logcat_param
except AttributeError:
extra_params = "-b all"
- cmd = "adb -s {} logcat -v threadtime {} >> {}".format(
+ cmd = "adb -s %s logcat -v threadtime %s >> %s" % (
self.serial, extra_params, logcat_file_path)
self.adb_logcat_process = utils.start_standing_subprocess(cmd)
self.adb_logcat_file_path = logcat_file_path
@@ -575,9 +396,9 @@
"""Stops the adb logcat collection subprocess.
"""
if not self.isAdbLogcatOn:
- raise AndroidDeviceError(("Android device {} does not have an "
- "ongoing adb logcat collection.").format(
- self.serial))
+ raise AndroidDeviceError(
+ "Android device %s does not have an ongoing adb logcat collection."
+ % self.serial)
utils.stop_standing_subprocess(self.adb_logcat_process)
self.adb_logcat_process = None
@@ -590,113 +411,14 @@
"""
br_path = os.path.join(self.log_path, "BugReports")
utils.create_dir(br_path)
- base_name = ",{},{}.txt".format(begin_time, self.serial)
+ base_name = ",%s,%s.txt" % (begin_time, self.serial)
test_name_len = utils.MAX_FILENAME_LEN - len(base_name)
out_name = test_name[:test_name_len] + base_name
full_out_path = os.path.join(br_path, out_name.replace(' ', '\ '))
self.log.info("Taking bugreport for %s on %s", test_name, self.serial)
- self.adb.bugreport(" > {}".format(full_out_path))
+ self.adb.bugreport(" > %s" % full_out_path)
self.log.info("Bugreport for %s taken at %s", test_name, full_out_path)
- def start_new_session(self):
- """Start a new session in sl4a.
-
- Also caches the droid in a dict with its uid being the key.
-
- Returns:
- An Android object used to communicate with sl4a on the android
- device.
-
- Raises:
- SL4AException: Something is wrong with sl4a and it returned an
- existing uid to a new session.
- """
- droid = android.Android(port=self.h_port)
- if droid.uid in self._droid_sessions:
- raise android.SL4AException(("SL4A returned an existing uid for a "
- "new session. Abort."))
- self._droid_sessions[droid.uid] = [droid]
- return droid
-
- def add_new_connection_to_session(self, session_id):
- """Create a new connection to an existing sl4a session.
-
- Args:
- session_id: UID of the sl4a session to add connection to.
-
- Returns:
- An Android object used to communicate with sl4a on the android
- device.
-
- Raises:
- AndroidDeviceError: Raised if the session it's trying to connect to
- does not exist.
- """
- if session_id not in self._droid_sessions:
- raise AndroidDeviceError("Session %d doesn't exist." % session_id)
- droid = android.Android(cmd='continue',
- uid=session_id,
- port=self.h_port)
- return droid
-
- def closeOneSl4aSession(self, session_id):
- """Terminate a session in sl4a.
-
- Send terminate signal to sl4a server; stop dispatcher associated with
- the session. Clear corresponding droids and dispatchers from cache.
-
- Args:
- session_id: UID of the sl4a session to terminate.
- """
- if self._droid_sessions and (session_id in self._droid_sessions):
- for droid in self._droid_sessions[session_id]:
- droid.closeSl4aSession()
- droid.close()
- del self._droid_sessions[session_id]
- ed_key = self.serial + str(session_id)
- if ed_key in self._event_dispatchers:
- self._event_dispatchers[ed_key].clean_up()
- del self._event_dispatchers[ed_key]
-
- def closeAllSl4aSession(self):
- """Terminate all sl4a sessions on the AndroidDevice instance.
-
- Terminate all sessions and clear caches.
- """
- if self._droid_sessions:
- session_ids = list(self._droid_sessions.keys())
- for session_id in session_ids:
- try:
- self.closeOneSl4aSession(session_id)
- except:
- msg = "Failed to terminate session %d." % session_id
- self.log.exception(msg)
- self.log.error(traceback.format_exc())
- if self.h_port:
- self.adb.forward("--remove tcp:%d" % self.h_port)
- self.h_port = None
-
- def runIperfClient(self, server_host, extra_args=""):
- """Start iperf client on the device.
-
- Return status as true if iperf client start successfully.
- And data flow information as results.
-
- Args:
- server_host: Address of the iperf server.
- extra_args: A string representing extra arguments for iperf client,
- e.g. "-i 1 -t 30".
-
- Returns:
- status: true if iperf client start successfully.
- results: results have data flow information
- """
- out = self.adb.shell("iperf3 -c {} {}".format(server_host, extra_args))
- clean_out = str(out, 'utf-8').strip().split('\n')
- if "error" in clean_out[0].lower():
- return False, clean_out
- return True, clean_out
-
@utils.timeout(15 * 60)
def waitForBootCompletion(self):
"""Waits for Android framework to broadcast ACTION_BOOT_COMPLETED.
@@ -717,22 +439,11 @@
time.sleep(5)
def reboot(self):
- """Reboots the device.
-
- Terminate all sl4a sessions, reboot the device, wait for device to
- complete booting, and restart an sl4a session.
-
- This is a blocking method.
+ """Reboots the device and wait for device to complete booting.
This is probably going to print some error messages in console. Only
use if there's no other option.
- Example:
- droid, ed = ad.reboot()
-
- Returns:
- An sl4a session with an event_dispatcher.
-
Raises:
AndroidDeviceError is raised if waiting for completion timed
out.
@@ -743,12 +454,9 @@
has_adb_log = self.isAdbLogcatOn
if has_adb_log:
self.stopAdbLogcat()
- self.closeAllSl4aSession()
self.adb.reboot()
self.waitForBootCompletion()
self.rootAdb()
- droid, ed = self.getSl4aClient()
- ed.start()
if has_adb_log:
self.startAdbLogcat()
- return droid, ed
+