Snap for 8055317 from 9b6a25306351ed6bcfad8c7651d6e2359ab44d73 to sc-v2-release

Change-Id: Ib6205299d91b8dfbe46b6617e8621ec5e7eb1869
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index e5f568f..3a35bf3 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -47,6 +47,10 @@
 ACTS_CONTROLLER_REFERENCE_NAME = "android_devices"
 
 ANDROID_DEVICE_PICK_ALL_TOKEN = "*"
+# Key name for SL4A extra params in config file
+ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY = "sl4a_client_port"
+ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY = "sl4a_forwarded_port"
+ANDROID_DEVICE_SL4A_SERVER_PORT_KEY = "sl4a_server_port"
 # Key name for adb logcat extra params in config file.
 ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = "adb_logcat_param"
 ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!"
@@ -232,12 +236,41 @@
             raise errors.AndroidDeviceConfigError(
                 "Required value 'serial' is missing in AndroidDevice config %s."
                 % c)
+        client_port = 0
+        if ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY in c:
+            try:
+                client_port = int(c.pop(ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY))
+            except ValueError:
+                raise errors.AndroidDeviceConfigError(
+                    "'%s' is not a valid number for config %s" %
+                    (ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY, c))
+        server_port = None
+        if ANDROID_DEVICE_SL4A_SERVER_PORT_KEY in c:
+            try:
+                server_port = int(c.pop(ANDROID_DEVICE_SL4A_SERVER_PORT_KEY))
+            except ValueError:
+                raise errors.AndroidDeviceConfigError(
+                    "'%s' is not a valid number for config %s" %
+                    (ANDROID_DEVICE_SL4A_SERVER_PORT_KEY, c))
+        forwarded_port = 0
+        if ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY in c:
+            try:
+                forwarded_port = int(
+                    c.pop(ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY))
+            except ValueError:
+                raise errors.AndroidDeviceConfigError(
+                    "'%s' is not a valid number for config %s" %
+                    (ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY, c))
         ssh_config = c.pop('ssh_config', None)
         ssh_connection = None
         if ssh_config is not None:
             ssh_settings = settings.from_config(ssh_config)
             ssh_connection = connection.SshConnection(ssh_settings)
-        ad = AndroidDevice(serial, ssh_connection=ssh_connection)
+        ad = AndroidDevice(serial,
+                           ssh_connection=ssh_connection,
+                           client_port=client_port,
+                           forwarded_port=forwarded_port,
+                           server_port=server_port)
         ad.load_config(c)
         results.append(ad)
     return results
@@ -357,14 +390,27 @@
         adb: An AdbProxy object used for interacting with the device via adb.
         fastboot: A FastbootProxy object used for interacting with the device
                   via fastboot.
+        client_port: Preferred client port number on the PC host side for SL4A
+        forwarded_port: Preferred server port number forwarded from Android
+                        to the host PC via adb for SL4A connections
+        server_port: Preferred server port used by SL4A on Android device
+
     """
 
-    def __init__(self, serial='', ssh_connection=None):
+    def __init__(self,
+                 serial='',
+                 ssh_connection=None,
+                 client_port=0,
+                 forwarded_port=0,
+                 server_port=None):
         self.serial = serial
         # logging.log_path only exists when this is used in an ACTS test run.
         log_path_base = getattr(logging, 'log_path', '/tmp/logs')
         self.log_dir = 'AndroidDevice%s' % serial
         self.log_path = os.path.join(log_path_base, self.log_dir)
+        self.client_port = client_port
+        self.forwarded_port = forwarded_port
+        self.server_port = server_port
         self.log = tracelogger.TraceLogger(
             AndroidDeviceLoggerAdapter(logging.getLogger(),
                                        {'serial': serial}))
@@ -374,8 +420,8 @@
         self.register_service(services.Sl4aService(self))
         self.adb_logcat_process = None
         self.adb = adb.AdbProxy(serial, ssh_connection=ssh_connection)
-        self.fastboot = fastboot.FastbootProxy(
-            serial, ssh_connection=ssh_connection)
+        self.fastboot = fastboot.FastbootProxy(serial,
+                                               ssh_connection=ssh_connection)
         if not self.is_bootloader:
             self.root_adb()
         self._ssh_connection = ssh_connection
@@ -425,8 +471,8 @@
 
         Stop adb logcat and terminate sl4a sessions if exist.
         """
-        event_bus.post(
-            android_events.AndroidStopServicesEvent(self), ignore_errors=True)
+        event_bus.post(android_events.AndroidStopServicesEvent(self),
+                       ignore_errors=True)
 
     def is_connected(self):
         out = self.adb.devices()
@@ -651,7 +697,13 @@
             >>> ad = AndroidDevice()
             >>> droid, ed = ad.get_droid()
         """
-        session = self._sl4a_manager.create_session()
+        self.log.debug(
+            "Creating RPC client_port={}, forwarded_port={}, server_port={}".
+            format(self.client_port, self.forwarded_port, self.server_port))
+        session = self._sl4a_manager.create_session(
+            client_port=self.client_port,
+            forwarded_port=self.forwarded_port,
+            server_port=self.server_port)
         droid = session.rpc_client
         if handle_event:
             ed = session.get_event_dispatcher()
@@ -671,9 +723,8 @@
         """
         for cmd in ("ps -A", "ps"):
             try:
-                out = self.adb.shell(
-                    '%s | grep "S %s"' % (cmd, package_name),
-                    ignore_status=True)
+                out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name),
+                                     ignore_status=True)
                 if package_name not in out:
                     continue
                 try:
@@ -765,10 +816,10 @@
         return adb_excerpt_path
 
     def search_logcat(self,
-                    matching_string,
-                    begin_time=None,
-                    end_time=None,
-                    logcat_path=None):
+                      matching_string,
+                      begin_time=None,
+                      end_time=None,
+                      logcat_path=None):
         """Search logcat message with given string.
 
         Args:
@@ -798,13 +849,12 @@
         """
         if not logcat_path:
             logcat_path = os.path.join(self.device_log_path,
-                                    'adblog_%s_debug.txt' % self.serial)
+                                       'adblog_%s_debug.txt' % self.serial)
         if not os.path.exists(logcat_path):
             self.log.warning("Logcat file %s does not exist." % logcat_path)
             return
-        output = job.run(
-            "grep '%s' %s" % (matching_string, logcat_path),
-            ignore_status=True)
+        output = job.run("grep '%s' %s" % (matching_string, logcat_path),
+                         ignore_status=True)
         if not output.stdout or output.exit_status != 0:
             return []
         if begin_time:
@@ -812,13 +862,13 @@
                 log_begin_time = acts_logger.epoch_to_log_line_timestamp(
                     begin_time)
                 begin_time = datetime.strptime(log_begin_time,
-                                            "%Y-%m-%d %H:%M:%S.%f")
+                                               "%Y-%m-%d %H:%M:%S.%f")
         if end_time:
             if not isinstance(end_time, datetime):
                 log_end_time = acts_logger.epoch_to_log_line_timestamp(
                     end_time)
                 end_time = datetime.strptime(log_end_time,
-                                            "%Y-%m-%d %H:%M:%S.%f")
+                                             "%Y-%m-%d %H:%M:%S.%f")
         result = []
         logs = re.findall(r'(\S+\s\S+)(.*)', output.stdout)
         for log in logs:
@@ -890,8 +940,8 @@
         Returns:
         Linux UID for the apk.
         """
-        output = self.adb.shell(
-            "dumpsys package %s | grep userId=" % apk_name, ignore_status=True)
+        output = self.adb.shell("dumpsys package %s | grep userId=" % apk_name,
+                                ignore_status=True)
         result = re.search(r"userId=(\d+)", output)
         if result:
             return result.group(1)
@@ -934,9 +984,8 @@
         """
         for cmd in ("ps -A", "ps"):
             try:
-                out = self.adb.shell(
-                    '%s | grep "S %s"' % (cmd, package_name),
-                    ignore_status=True)
+                out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name),
+                                     ignore_status=True)
                 if package_name in out:
                     self.log.info("apk %s is running", package_name)
                     return True
@@ -961,8 +1010,8 @@
         True if package is installed. False otherwise.
         """
         try:
-            self.adb.shell(
-                'am force-stop %s' % package_name, ignore_status=True)
+            self.adb.shell('am force-stop %s' % package_name,
+                           ignore_status=True)
         except Exception as e:
             self.log.warn("Fail to stop package %s: %s", package_name, e)
 
@@ -1010,8 +1059,8 @@
             br_out_path = out.split(':')[1].strip().split()[0]
             self.adb.pull("%s %s" % (br_out_path, full_out_path))
         else:
-            self.adb.bugreport(
-                " > {}".format(full_out_path), timeout=BUG_REPORT_TIMEOUT)
+            self.adb.bugreport(" > {}".format(full_out_path),
+                               timeout=BUG_REPORT_TIMEOUT)
         self.log.info("Bugreport for %s taken at %s.", test_name,
                       full_out_path)
         self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT)
@@ -1073,10 +1122,10 @@
         if not host_path:
             host_path = self.log_path
         for device_path in device_paths:
-            self.log.info(
-                'Pull from device: %s -> %s' % (device_path, host_path))
-            self.adb.pull(
-                "%s %s" % (device_path, host_path), timeout=PULL_TIMEOUT)
+            self.log.info('Pull from device: %s -> %s' %
+                          (device_path, host_path))
+            self.adb.pull("%s %s" % (device_path, host_path),
+                          timeout=PULL_TIMEOUT)
 
     def check_crash_report(self,
                            test_name=None,
@@ -1091,10 +1140,9 @@
             except Exception as e:
                 self.log.debug("received exception %s", e)
                 continue
-            crashes = self.get_file_names(
-                crash_path,
-                skip_files=CRASH_REPORT_SKIPS,
-                begin_time=begin_time)
+            crashes = self.get_file_names(crash_path,
+                                          skip_files=CRASH_REPORT_SKIPS,
+                                          begin_time=begin_time)
             if crash_path == "/data/tombstones/" and crashes:
                 tombstones = crashes[:]
                 for tombstone in tombstones:
@@ -1117,18 +1165,18 @@
         # Sleep 10 seconds for the buffered log to be written in qxdm log file
         time.sleep(10)
         log_path = getattr(self, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
-        qxdm_logs = self.get_file_names(
-            log_path, begin_time=begin_time, match_string="*.qmdl")
+        qxdm_logs = self.get_file_names(log_path,
+                                        begin_time=begin_time,
+                                        match_string="*.qmdl")
         if qxdm_logs:
             qxdm_log_path = os.path.join(self.device_log_path,
                                          "QXDM_%s" % self.serial)
             os.makedirs(qxdm_log_path, exist_ok=True)
             self.log.info("Pull QXDM Log %s to %s", qxdm_logs, qxdm_log_path)
             self.pull_files(qxdm_logs, qxdm_log_path)
-            self.adb.pull(
-                "/firmware/image/qdsp6m.qdb %s" % qxdm_log_path,
-                timeout=PULL_TIMEOUT,
-                ignore_status=True)
+            self.adb.pull("/firmware/image/qdsp6m.qdb %s" % qxdm_log_path,
+                          timeout=PULL_TIMEOUT,
+                          ignore_status=True)
         else:
             self.log.error("Didn't find QXDM logs in %s." % log_path)
         if "Verizon" in self.adb.getprop("gsm.sim.operator.alpha"):
@@ -1147,8 +1195,9 @@
         # Sleep 10 seconds for the buffered log to be written in sdm log file
         time.sleep(10)
         log_path = getattr(self, "sdm_log_path", DEFAULT_SDM_LOG_PATH)
-        sdm_logs = self.get_file_names(
-            log_path, begin_time=begin_time, match_string="*.sdm*")
+        sdm_logs = self.get_file_names(log_path,
+                                       begin_time=begin_time,
+                                       match_string="*.sdm*")
         if sdm_logs:
             sdm_log_path = os.path.join(self.device_log_path,
                                         "SDM_%s" % self.serial)
@@ -1234,8 +1283,8 @@
             status: true if iperf client start successfully.
             results: results have data flow information
         """
-        out = self.adb.shell(
-            "iperf3 -c {} {}".format(server_host, extra_args), timeout=timeout)
+        out = self.adb.shell("iperf3 -c {} {}".format(server_host, extra_args),
+                             timeout=timeout)
         clean_out = out.split('\n')
         if "error" in clean_out[0].lower():
             return False, clean_out
@@ -1286,7 +1335,9 @@
             'Device %s booting process timed out.' % self.serial,
             serial=self.serial)
 
-    def reboot(self, stop_at_lock_screen=False, timeout=180,
+    def reboot(self,
+               stop_at_lock_screen=False,
+               timeout=180,
                wait_after_reboot_complete=1):
         """Reboots the device.
 
@@ -1323,8 +1374,8 @@
                 # want the device to be missing to prove the device has kicked
                 # off the reboot.
                 break
-        self.wait_for_boot_completion(
-            timeout=(timeout - time.time() + timeout_start))
+        self.wait_for_boot_completion(timeout=(timeout - time.time() +
+                                               timeout_start))
 
         self.log.debug('Wait for a while after boot completion.')
         time.sleep(wait_after_reboot_complete)
@@ -1359,8 +1410,8 @@
                 break
             except adb.AdbError as e:
                 if timer + 1 == timeout:
-                    self.log.warning(
-                        'Unable to find IP address for %s.' % interface)
+                    self.log.warning('Unable to find IP address for %s.' %
+                                     interface)
                     return None
                 else:
                     time.sleep(1)
@@ -1426,7 +1477,7 @@
         for cmd in dumpsys_cmd:
             output = self.adb.shell(cmd, ignore_status=True)
             if not output or "not found" in output or "Can't find" in output or (
-                "mFocusedApp=null" in output):
+                    "mFocusedApp=null" in output):
                 result = ''
             else:
                 result = output.split(' ')[-2]
@@ -1565,11 +1616,11 @@
             return
         if not self.is_user_setup_complete() or self.is_setupwizard_on():
             # b/116709539 need this to prevent reboot after skip setup wizard
-            self.adb.shell(
-                "am start -a com.android.setupwizard.EXIT", ignore_status=True)
-            self.adb.shell(
-                "pm disable %s" % self.get_setupwizard_package_name(),
-                ignore_status=True)
+            self.adb.shell("am start -a com.android.setupwizard.EXIT",
+                           ignore_status=True)
+            self.adb.shell("pm disable %s" %
+                           self.get_setupwizard_package_name(),
+                           ignore_status=True)
         # Wait up to 5 seconds for user_setup_complete to be updated
         end_time = time.time() + 5
         while time.time() < end_time:
@@ -1619,8 +1670,8 @@
         try:
             self.ensure_verity_disabled()
             self.adb.remount()
-            out = self.adb.push(
-                '%s %s' % (src_file_path, dst_file_path), timeout=push_timeout)
+            out = self.adb.push('%s %s' % (src_file_path, dst_file_path),
+                                timeout=push_timeout)
             if 'error' in out:
                 self.log.error('Unable to push system file %s to %s due to %s',
                                src_file_path, dst_file_path, out)
diff --git a/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py b/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py
index 8d76f1f..181048d 100644
--- a/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py
+++ b/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py
@@ -109,12 +109,12 @@
         self._listen_for_port_lock = threading.Lock()
         self._sl4a_ports = set()
         self.adb = adb
-        self.log = logger.create_logger(
-            lambda msg: '[SL4A Manager|%s] %s' % (adb.serial, msg))
+        self.log = logger.create_logger(lambda msg: '[SL4A Manager|%s] %s' % (
+            adb.serial, msg))
         self.sessions = {}
         self._started = False
-        self.error_reporter = error_reporter.ErrorReporter(
-            'SL4A %s' % adb.serial)
+        self.error_reporter = error_reporter.ErrorReporter('SL4A %s' %
+                                                           adb.serial)
 
     @property
     def sl4a_ports_in_use(self):
@@ -161,8 +161,8 @@
 
         raise rpc_client.Sl4aConnectionError(
             'Unable to find a valid open port for a new server connection. '
-            'Expected port: %s. Open ports: %s' % (device_port,
-                                                   self._sl4a_ports))
+            'Expected port: %s. Open ports: %s' %
+            (device_port, self._sl4a_ports))
 
     def _get_all_ports_command(self):
         """Returns the list of all ports from the command to get ports."""
@@ -203,9 +203,8 @@
     def is_sl4a_installed(self):
         """Returns True if SL4A is installed on the AndroidDevice."""
         return bool(
-            self.adb.shell(
-                'pm path com.googlecode\.android_scripting',
-                ignore_status=True))
+            self.adb.shell('pm path com.googlecode\.android_scripting',
+                           ignore_status=True))
 
     def start_sl4a_service(self):
         """Starts the SL4A Service on the device.
@@ -219,7 +218,8 @@
                 raise rpc_client.Sl4aNotInstalledError(
                     'SL4A is not installed on device %s' % self.adb.serial)
             if self.adb.shell(
-                    '(ps | grep "S com.googlecode.android_scripting") || true'):
+                    '(ps | grep "S com.googlecode.android_scripting") || true'
+            ):
                 # Close all SL4A servers not opened by this manager.
                 # TODO(markdr): revert back to closing all ports after
                 # b/76147680 is resolved.
@@ -244,6 +244,7 @@
     def create_session(self,
                        max_connections=None,
                        client_port=0,
+                       forwarded_port=0,
                        server_port=None):
         """Creates an SL4A server with the given ports if possible.
 
@@ -252,7 +253,9 @@
         be randomized.
 
         Args:
-            client_port: The port on the host machine
+            client_port: The client port on the host machine
+            forwarded_port: The server port on the host machine forwarded
+                            by adb from the Android device
             server_port: The port on the Android device.
             max_connections: The max number of client connections for the
                 session.
@@ -268,14 +271,17 @@
             # Otherwise, open a new server on a random port.
             else:
                 server_port = 0
+        self.log.debug(
+            "Creating SL4A session client_port={}, forwarded_port={}, server_port={}"
+            .format(client_port, forwarded_port, server_port))
         self.start_sl4a_service()
-        session = sl4a_session.Sl4aSession(
-            self.adb,
-            client_port,
-            server_port,
-            self.obtain_sl4a_server,
-            self.diagnose_failure,
-            max_connections=max_connections)
+        session = sl4a_session.Sl4aSession(self.adb,
+                                           client_port,
+                                           server_port,
+                                           self.obtain_sl4a_server,
+                                           self.diagnose_failure,
+                                           forwarded_port,
+                                           max_connections=max_connections)
         self.sessions[session.uid] = session
         return session
 
diff --git a/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py b/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py
index c0a4e1d..04fd787 100644
--- a/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py
+++ b/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py
@@ -55,6 +55,7 @@
                  device_port,
                  get_server_port_func,
                  on_error_callback,
+                 forwarded_port=0,
                  max_connections=None):
         """Creates an SL4A Session.
 
@@ -67,6 +68,8 @@
                 server for its first connection.
             device_port: The SL4A server port to be used as a hint for which
                 SL4A server to connect to.
+            forwarded_port: The server port on host machine forwarded by adb
+                            from Android device to accept SL4A connection
         """
         self._event_dispatcher = None
         self._terminate_lock = threading.Lock()
@@ -79,24 +82,24 @@
 
         self.log = logger.create_logger(_log_formatter)
 
+        self.forwarded_port = forwarded_port
         self.server_port = device_port
         self.uid = UNKNOWN_UID
         self.obtain_server_port = get_server_port_func
         self._on_error_callback = on_error_callback
 
         connection_creator = self._rpc_connection_creator(host_port)
-        self.rpc_client = rpc_client.RpcClient(
-            self.uid,
-            self.adb.serial,
-            self.diagnose_failure,
-            connection_creator,
-            max_connections=max_connections)
+        self.rpc_client = rpc_client.RpcClient(self.uid,
+                                               self.adb.serial,
+                                               self.diagnose_failure,
+                                               connection_creator,
+                                               max_connections=max_connections)
 
     def _rpc_connection_creator(self, host_port):
         def create_client(uid):
-            return self._create_rpc_connection(
-                ports=sl4a_ports.Sl4aPorts(host_port, 0, self.server_port),
-                uid=uid)
+            return self._create_rpc_connection(ports=sl4a_ports.Sl4aPorts(
+                host_port, self.forwarded_port, self.server_port),
+                                               uid=uid)
 
         return create_client
 
@@ -156,10 +159,14 @@
         ports.server_port = self.obtain_server_port(ports.server_port)
         self.server_port = ports.server_port
         # Forward the device port to the host.
-        ports.forwarded_port = self._create_forwarded_port(ports.server_port)
+        ports.forwarded_port = self._create_forwarded_port(
+            ports.server_port, hinted_port=ports.forwarded_port)
         client_socket, fd = self._create_client_side_connection(ports)
-        client = rpc_connection.RpcConnection(
-            self.adb, ports, client_socket, fd, uid=uid)
+        client = rpc_connection.RpcConnection(self.adb,
+                                              ports,
+                                              client_socket,
+                                              fd,
+                                              uid=uid)
         client.open()
         if uid == UNKNOWN_UID:
             self.uid = client.uid
@@ -195,9 +202,9 @@
             except OSError as e:
                 # If the port is in use, log and ask for any open port.
                 if e.errno == errno.EADDRINUSE:
-                    self.log.warning(
-                        'Port %s is already in use on the host. '
-                        'Generating a random port.' % ports.client_port)
+                    self.log.warning('Port %s is already in use on the host. '
+                                     'Generating a random port.' %
+                                     ports.client_port)
                     ports.client_port = 0
                     return self._create_client_side_connection(ports)
                 raise