• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3.4
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17from builtins import str
18
19import random
20import socket
21import subprocess
22import time
23
24class AdbError(Exception):
25    """Raised when there is an error in adb operations."""
26
27SL4A_LAUNCH_CMD=("am start -a com.googlecode.android_scripting.action.LAUNCH_SERVER "
28    "--ei com.googlecode.android_scripting.extra.USE_SERVICE_PORT {} "
29    "com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher" )
30
31def get_available_host_port():
32    """Gets a host port number available for adb forward.
33
34    Returns:
35        An integer representing a port number on the host available for adb
36        forward.
37    """
38    while True:
39        port = random.randint(1024, 9900)
40        if is_port_available(port):
41            return port
42
43def is_port_available(port):
44    """Checks if a given port number is available on the system.
45
46    Args:
47        port: An integer which is the port number to check.
48
49    Returns:
50        True if the port is available; False otherwise.
51    """
52    # Make sure adb is not using this port so we don't accidentally interrupt
53    # ongoing runs by trying to bind to the port.
54    if port in list_occupied_adb_ports():
55        return False
56    s = None
57    try:
58        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
59        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
60        s.bind(('localhost', port))
61        return True
62    except socket.error:
63        return False
64    finally:
65        if s:
66            s.close()
67
68def list_occupied_adb_ports():
69    """Lists all the host ports occupied by adb forward.
70
71    This is useful because adb will silently override the binding if an attempt
72    to bind to a port already used by adb was made, instead of throwing binding
73    error. So one should always check what ports adb is using before trying to
74    bind to a port with adb.
75
76    Returns:
77        A list of integers representing occupied host ports.
78    """
79    out = AdbProxy().forward("--list")
80    clean_lines = str(out, 'utf-8').strip().split('\n')
81    used_ports = []
82    for line in clean_lines:
83        tokens = line.split(" tcp:")
84        if len(tokens) != 3:
85            continue
86        used_ports.append(int(tokens[1]))
87    return used_ports
88
89class AdbProxy():
90    """Proxy class for ADB.
91
92    For syntactic reasons, the '-' in adb commands need to be replaced with
93    '_'. Can directly execute adb commands on an object:
94    >> adb = AdbProxy(<serial>)
95    >> adb.start_server()
96    >> adb.devices() # will return the console output of "adb devices".
97    """
98    def __init__(self, serial="", log=None):
99        self.serial = serial
100        if serial:
101            self.adb_str = "adb -s {}".format(serial)
102        else:
103            self.adb_str = "adb"
104        self.log = log
105
106    def _exec_cmd(self, cmd):
107        """Executes adb commands in a new shell.
108
109        This is specific to executing adb binary because stderr is not a good
110        indicator of cmd execution status.
111
112        Args:
113            cmds: A string that is the adb command to execute.
114
115        Returns:
116            The output of the adb command run if exit code is 0.
117
118        Raises:
119            AdbError is raised if the adb command exit code is not 0.
120        """
121        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
122        (out, err) = proc.communicate()
123        ret = proc.returncode
124        total_output = "stdout: {}, stderr: {}, ret: {}".format(out, err, ret)
125        # TODO(angli): Fix this when global logger is done.
126        if self.log:
127            self.log.debug("{}\n{}".format(cmd, total_output))
128        if ret == 0:
129            return out
130        else:
131            raise AdbError(total_output)
132
133    def _exec_adb_cmd(self, name, arg_str):
134        return self._exec_cmd(' '.join((self.adb_str, name, arg_str)))
135
136    def tcp_forward(self, host_port, device_port):
137        """Starts tcp forwarding.
138
139        Args:
140            host_port: Port number to use on the computer.
141            device_port: Port number to use on the android device.
142        """
143        self.forward("tcp:{} tcp:{}".format(host_port, device_port))
144
145    def start_sl4a(self, port=8080):
146        """Starts sl4a server on the android device.
147
148        Args:
149            port: Port number to use on the android device.
150        """
151        MAX_SL4A_WAIT_TIME = 10
152        print(self.shell(SL4A_LAUNCH_CMD.format(port)))
153
154        for _ in range(MAX_SL4A_WAIT_TIME):
155            time.sleep(1)
156            if self.is_sl4a_running():
157                return
158        raise AdbError(
159                "com.googlecode.android_scripting process never started.")
160
161    def is_sl4a_running(self):
162        """Checks if the sl4a app is running on an android device.
163
164        Returns:
165            True if the sl4a app is running, False otherwise.
166        """
167        #Grep for process with a preceding S which means it is truly started.
168        out = self.shell('ps | grep "S com.googlecode.android_scripting"')
169        if len(out)==0:
170          return False
171        return True
172
173    def __getattr__(self, name):
174        def adb_call(*args):
175            clean_name = name.replace('_', '-')
176            arg_str = ' '.join(str(elem) for elem in args)
177            return self._exec_adb_cmd(clean_name, arg_str)
178        return adb_call
179