def restore_app_connection(self, port=None): """Restores the app after device got reconnected. Instead of creating new instance of the client: - Uses the given port (or find a new available host_port if none is given). - Tries to connect to remote server with selected port. Args: port: If given, this is the host port from which to connect to remote device port. If not provided, find a new available port as host port. Raises: AppRestoreConnectionError: When the app was not able to be started. """ self.host_port = port or utils.get_available_host_port() self._adb.forward( ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port]) try: self.connect() except: # Failed to connect to app, something went wrong. raise jsonrpc_client_base.AppRestoreConnectionError( self._ad( 'Failed to restore app connection for %s at host port %s, ' 'device port %s'), self.package, self.host_port, self.device_port) # Because the previous connection was lost, update self._proc self._proc = None self._restore_event_client()
def start_app_and_connect(self): """Overrides superclass. Launches a snippet app and connects to it.""" self._check_app_installed() persists_shell_cmd = self._get_persist_command() # Try launching the app with the v1 protocol. If that fails, fall back # to v0 for compatibility. Use info here so people know exactly what's # happening here, which is helpful since they need to create their own # instrumentations and manifest. self.log.info('Launching snippet apk %s with protocol v1', self.package) cmd = _LAUNCH_CMD_V1 % (persists_shell_cmd, self.package) start_time = time.time() self._proc = self._do_start_app(cmd) # "Instrumentation crashed" could be due to several reasons, eg # exception thrown during startup or just a launch protocol 0 snippet # dying because it needs the port flag. Sadly we have no way to tell so # just warn and retry as v0. # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is # removed. line = self._read_protocol_line() # Forward the device port to a new host port, and connect to that port self.host_port = utils.get_available_host_port() if line in ('INSTRUMENTATION_RESULT: shortMsg=Process crashed.', 'INSTRUMENTATION_RESULT: shortMsg=' 'java.lang.IllegalArgumentException'): self.log.warning('Snippet %s crashed on startup. This might be an ' 'actual error or a snippet using deprecated v0 ' 'start protocol. Retrying as a v0 snippet.', self.package) # Reuse the host port as the device port in v0 snippet. This isn't # safe in general, but the protocol is deprecated. self.device_port = self.host_port cmd = _LAUNCH_CMD_V0 % (persists_shell_cmd, self.device_port, self.package) self._proc = self._do_start_app(cmd) self._connect_to_v0() self._launch_version = 'v0' else: # Check protocol version and get the device port match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$', line) if not match or match.group(1) != '1': raise ProtocolVersionError(line) line = self._read_protocol_line() match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line) if not match: raise ProtocolVersionError(line) self.device_port = int(match.group(1)) self._connect_to_v1() self.log.debug('Snippet %s started after %.1fs on host port %s', self.package, time.time() - start_time, self.host_port)
def _start_app_and_connect(self): """Starts snippet apk on the device and connects to it. After prechecks, this launches the snippet apk with an adb cmd in a standing subprocess, checks the cmd response from the apk for protocol version, then sets up the socket connection over adb port-forwarding. Args: ProtocolVersionError, if protocol info or port info cannot be retrieved from the snippet apk. """ self._check_app_installed() self.disable_hidden_api_blacklist() persists_shell_cmd = self._get_persist_command() # Use info here so people can follow along with the snippet startup # process. Starting snippets can be slow, especially if there are # multiple, and this avoids the perception that the framework is hanging # for a long time doing nothing. self.log.info('Launching snippet apk %s with protocol %d.%d', self.package, _PROTOCOL_MAJOR_VERSION, _PROTOCOL_MINOR_VERSION) cmd = _LAUNCH_CMD.format(shell_cmd=persists_shell_cmd, user=self._get_user_command_string(), snippet_package=self.package) start_time = time.time() self._proc = self._do_start_app(cmd) # Check protocol version and get the device port line = self._read_protocol_line() match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$', line) if not match or match.group(1) != '1': raise ProtocolVersionError(self._ad, line) line = self._read_protocol_line() match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line) if not match: raise ProtocolVersionError(self._ad, line) self.device_port = int(match.group(1)) # Forward the device port to a new host port, and connect to that port self.host_port = utils.get_available_host_port() self._adb.forward( ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port]) self.connect() # Yaaay! We're done! self.log.debug('Snippet %s started after %.1fs on host port %s', self.package, time.time() - start_time, self.host_port)
def start_app_and_connect(self): """Overrides superclass.""" # Check that sl4a is installed out = self._adb.shell('pm list package') if not utils.grep('com.googlecode.android_scripting', out): raise AppStartError('%s is not installed on %s' % (_APP_NAME, self._adb.serial)) # sl4a has problems connecting after disconnection, so kill the apk and # try connecting again. try: self.stop_app() except Exception as e: self.log.warning(e) # Launch the app self.host_port = utils.get_available_host_port() self.device_port = _DEVICE_SIDE_PORT self._adb.forward( ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port]) self._adb.shell(_LAUNCH_CMD % self.device_port) # Connect with retry start_time = time.time() expiration_time = start_time + _APP_START_WAIT_TIME started = False while time.time() < expiration_time: self.log.debug('Attempting to start %s.', self.app_name) try: self.connect() started = True break except: self.log.debug('%s is not yet running, retrying', self.app_name, exc_info=True) time.sleep(1) if not started: raise jsonrpc_client_base.AppStartError( '%s failed to start on %s.' % (self.app_name, self._adb.serial)) # Start an EventDispatcher for the current sl4a session event_client = Sl4aClient(self._adb, self.log) event_client.host_port = self.host_port event_client.connect(uid=self.uid, cmd=jsonrpc_client_base.JsonRpcCommand.CONTINUE) self.ed = event_dispatcher.EventDispatcher(event_client) self.ed.start()
def load_snippet(self, name, package): """Starts the snippet apk with the given package name and connects. Examples: >>> ad = AndroidDevice() >>> ad.load_snippet( name='maps', package='com.google.maps.snippets') >>> ad.maps.activateZoom('3') Args: name: The attribute name to which to attach the snippet server. e.g. name='maps' will attach the snippet server to ad.maps. package: The package name defined in AndroidManifest.xml of the snippet apk. Raises: SnippetError is raised if illegal load operations are attempted. """ # Should not load snippet with the same attribute more than once. if name in self._snippet_clients: raise SnippetError( self, 'Attribute "%s" is already registered with package "%s", it ' 'cannot be used again.' % (name, self._snippet_clients[name].package)) # Should not load snippet with an existing attribute. if hasattr(self, name): raise SnippetError( self, 'Attribute "%s" already exists, please use a different name.' % name) # Should not load the same snippet package more than once. for client_name, client in self._snippet_clients.items(): if package == client.package: raise SnippetError( self, 'Snippet package "%s" has already been loaded under name' ' "%s".' % (package, client_name)) host_port = utils.get_available_host_port() client = snippet_client.SnippetClient( package=package, host_port=host_port, adb_proxy=self.adb, log=self.log) self._start_jsonrpc_client(client) self._snippet_clients[name] = client setattr(self, name, client)
def test_get_available_port_returns_free_port( self, mock_list_occupied_adb_ports): """This test checks we can bind to a socket on the port returned by portpicker and should pass as long as we can bind to either an ipv4 or ipv6 socket on such port.""" port = utils.get_available_host_port() got_socket = False for family in (socket.AF_INET6, socket.AF_INET): try: s = socket.socket(family, socket.SOCK_STREAM) got_socket = True except socket.error: continue s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.bind(('localhost', port)) finally: s.close() self.assertTrue(got_socket)
def restore_app_connection(self, port=None): """Restores the sl4a after device got disconnected. Instead of creating new instance of the client: - Uses the given port (or find a new available host_port if none is given). - Tries to connect to remote server with selected port. Args: port: If given, this is the host port from which to connect to remote device port. If not provided, find a new available port as host port. Raises: AppRestoreConnectionError: When the app was not able to be started. """ self.host_port = port or utils.get_available_host_port() self._retry_connect() self.ed = self._start_event_client()
def load_sl4a(self): """Start sl4a service on the Android device. Launch sl4a server if not already running, spin up a session on the server, and two connections to this session. Creates an sl4a client (self.sl4a) with one connection, and one EventDispatcher obj (self.ed) with the other connection. """ host_port = utils.get_available_host_port() self.sl4a = sl4a_client.Sl4aClient(host_port=host_port, adb_proxy=self.adb) self._start_jsonrpc_client(self.sl4a) # Start an EventDispatcher for the current sl4a session event_client = sl4a_client.Sl4aClient(host_port=host_port, adb_proxy=self.adb) event_client.connect(uid=self.sl4a.uid, cmd=jsonrpc_client_base.JsonRpcCommand.CONTINUE) self.ed = event_dispatcher.EventDispatcher(event_client) self.ed.start()
def test_get_available_port_returns_free_port(self, _): """Verifies logic to pick a free port on the host. Test checks we can bind to either an ipv4 or ipv6 socket on the port returned by get_available_host_port. """ port = utils.get_available_host_port() got_socket = False for family in (socket.AF_INET, socket.AF_INET6): try: s = socket.socket(family, socket.SOCK_STREAM) got_socket = True break except socket.error: continue self.assertTrue(got_socket) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.bind(('localhost', port)) finally: s.close()
def start_app_and_connect(self): """Overrides superclass. Launches a snippet app and connects to it.""" self._check_app_installed() self.disable_hidden_api_blacklist() persists_shell_cmd = self._get_persist_command() # Use info here so people can follow along with the snippet startup # process. Starting snippets can be slow, especially if there are # multiple, and this avoids the perception that the framework is hanging # for a long time doing nothing. self.log.info('Launching snippet apk %s with protocol %d.%d', self.package, _PROTOCOL_MAJOR_VERSION, _PROTOCOL_MINOR_VERSION) cmd = _LAUNCH_CMD % (persists_shell_cmd, self.package) start_time = time.time() self._proc = self._do_start_app(cmd) # Check protocol version and get the device port line = self._read_protocol_line() match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$', line) if not match or match.group(1) != '1': raise ProtocolVersionError(self._ad, line) line = self._read_protocol_line() match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line) if not match: raise ProtocolVersionError(self._ad, line) self.device_port = int(match.group(1)) # Forward the device port to a new host port, and connect to that port self.host_port = utils.get_available_host_port() self._adb.forward( ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port]) self.connect() # Yaaay! We're done! self.log.debug('Snippet %s started after %.1fs on host port %s', self.package, time.time() - start_time, self.host_port)
def get_droid(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.get_droid(False) Need event handling: >>> ad = AndroidDevice() >>> droid, ed = ad.get_droid() """ if not self.h_port or not utils.is_port_available(self.h_port): self.h_port = utils.get_available_host_port() self.adb.tcp_forward(self.h_port, self.d_port) try: droid = self.start_new_session() except: sl4a_client.start_sl4a(self.adb) droid = self.start_new_session() if handle_event: ed = self.get_dispatcher(droid) return droid, ed return droid
def test_get_available_port_negative(self, mock_list_occupied_adb_ports, mock_pick_unused_port): with self.assertRaisesRegex(utils.Error, 'Failed to find.* retries'): utils.get_available_host_port()
def test_get_available_port_positive(self, mock_list_occupied_adb_ports, mock_pick_unused_port): self.assertEqual(utils.get_available_host_port(), MOCK_AVAILABLE_PORT)
def _forward_device_port(self): """Forwards the device port to a host port.""" if not self.host_port: self.host_port = utils.get_available_host_port() self._adb.forward([f'tcp:{self.host_port}', f'tcp:{self.device_port}'])
def test_get_available_port_negative(self, *_): with self.assertRaisesRegex(utils.Error, 'Failed to find.* retries'): utils.get_available_host_port()
def test_get_available_port_positive_no_adb(self, mock_list_occupied_adb_ports, *_): self.assertEqual(utils.get_available_host_port(), MOCK_AVAILABLE_PORT) mock_list_occupied_adb_ports.assert_not_called()
def test_get_available_port_positive(self, *_): self.assertEqual(utils.get_available_host_port(), MOCK_AVAILABLE_PORT)