• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2019 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""A tool that help to run adb to check device status."""
15
16import re
17import subprocess
18
19from acloud import errors
20from acloud.internal import constants
21from acloud.internal.lib import utils
22
23_ADB_CONNECT = "connect"
24_ADB_DEVICE = "devices"
25_ADB_DISCONNECT = "disconnect"
26_ADB_STATUS_DEVICE = "device"
27_ADB_STATUS_DEVICE_ARGS = "-l"
28_RE_ADB_DEVICE_INFO = (r"%s\s*(?P<adb_status>[\S]+)? ?"
29                       r"(usb:(?P<usb>[\S]+))? ?"
30                       r"(product:(?P<product>[\S]+))? ?"
31                       r"(model:(?P<model>[\S]+))? ?"
32                       r"(device:(?P<device>[\S]+))? ?"
33                       r"(transport_id:(?P<transport_id>[\S]+))? ?")
34_DEVICE_ATTRIBUTES = ["adb_status", "usb", "product", "model", "device", "transport_id"]
35_MAX_RETRIES_ON_WAIT_ADB_GONE = 5
36#KEY_CODE 82 = KEY_MENU
37_UNLOCK_SCREEN_KEYEVENT = ("%(adb_bin)s -s %(device_serial)s "
38                           "shell input keyevent 82")
39_WAIT_ADB_RETRY_BACKOFF_FACTOR = 1.5
40_WAIT_ADB_SLEEP_MULTIPLIER = 2
41
42
43class AdbTools:
44    """Adb tools.
45
46    Attributes:
47        _adb_command: String, combine adb commands then execute it.
48        _adb_port: Integer, Specified adb port to establish connection.
49        _device_address: String, the device's host and port for adb to connect
50                         to. For example, adb connect 127.0.0.1:5555.
51        _device_serial: String, adb device's serial number. The value can be
52                        different from _device_address. For example,
53                        adb -s emulator-5554 shell.
54        _device_information: Dict, will be added to adb information include usb,
55                            product model, device and transport_id
56    """
57    _adb_command = None
58
59    def __init__(self, adb_port=None, device_serial=""):
60        """Initialize.
61
62        Args:
63            adb_port: String of adb port number.
64            device_serial: String, adb device's serial number.
65        """
66        self._adb_port = adb_port
67        self._device_address = ""
68        self._device_serial = ""
69        self._SetDeviceSerial(device_serial)
70        self._device_information = {}
71        self._CheckAdb()
72        self._GetAdbInformation()
73
74    def _SetDeviceSerial(self, device_serial):
75        """Set device serial and address.
76
77        Args:
78            device_serial: String, the device's serial number. If this
79                           argument is empty, the serial number is set to the
80                           network address.
81        """
82        self._device_address = ("127.0.0.1:%s" % self._adb_port if
83                                self._adb_port else "")
84        self._device_serial = (device_serial if device_serial else
85                               self._device_address)
86
87    @classmethod
88    def _CheckAdb(cls):
89        """Find adb bin path.
90
91        Raises:
92            errors.NoExecuteCmd: Can't find the execute adb bin.
93        """
94        if cls._adb_command:
95            return
96        cls._adb_command = utils.FindExecutable(constants.ADB_BIN)
97        if not cls._adb_command:
98            raise errors.NoExecuteCmd("Can't find the adb command.")
99
100    def GetAdbConnectionStatus(self):
101        """Get Adb connect status.
102
103        Check if self._adb_port is null (ssh tunnel is broken).
104
105        Returns:
106            String, the result of adb connection.
107        """
108        if not self._adb_port:
109            return None
110
111        return self._device_information["adb_status"]
112
113    def _GetAdbInformation(self):
114        """Get Adb connect information.
115
116        1. Check adb devices command to get the connection information.
117
118        2. Gather information include usb, product model, device and transport_id
119        when the attached field is device.
120
121        e.g.
122            Case 1:
123            List of devices attached
124            127.0.0.1:48451 device product:aosp_cf model:Cuttlefish device:vsoc_x86 transport_id:147
125            _device_information = {"adb_status":"device",
126                                   "usb":None,
127                                   "product":"aosp_cf",
128                                   "model":"Cuttlefish",
129                                   "device":"vsoc_x86",
130                                   "transport_id":"147"}
131
132            Case 2:
133            List of devices attached
134            127.0.0.1:48451 offline
135            _device_information = {"adb_status":"offline",
136                                   "usb":None,
137                                   "product":None,
138                                   "model":None,
139                                   "device":None,
140                                   "transport_id":None}
141
142            Case 3:
143            List of devices attached
144            _device_information = {"adb_status":None,
145                                   "usb":None,
146                                   "product":None,
147                                   "model":None,
148                                   "device":None,
149                                   "transport_id":None}
150        """
151        adb_cmd = [self._adb_command, _ADB_DEVICE, _ADB_STATUS_DEVICE_ARGS]
152        device_info = utils.CheckOutput(adb_cmd)
153        self._device_information = {
154            attribute: None for attribute in _DEVICE_ATTRIBUTES}
155
156        for device in device_info.splitlines():
157            match = re.match(_RE_ADB_DEVICE_INFO % self._device_serial, device)
158            if match:
159                self._device_information = {
160                    attribute: match.group(attribute) if match.group(attribute)
161                               else None for attribute in _DEVICE_ATTRIBUTES}
162
163    @classmethod
164    def GetDeviceSerials(cls):
165        """Get the serial numbers of connected devices."""
166        cls._CheckAdb()
167        adb_cmd = [cls._adb_command, _ADB_DEVICE]
168        device_info = utils.CheckOutput(adb_cmd)
169        serials = []
170        # Skip the first line which is "List of devices attached". Each of the
171        # following lines consists of the serial number, a tab character, and
172        # the state. The last line is empty.
173        for line in device_info.splitlines()[1:]:
174            serial_state = line.split()
175            if len(serial_state) > 1:
176                serials.append(serial_state[0])
177        return serials
178
179    def IsAdbConnectionAlive(self):
180        """Check devices connect alive.
181
182        Returns:
183            Boolean, True if adb status is device. False otherwise.
184        """
185        return self.GetAdbConnectionStatus() == _ADB_STATUS_DEVICE
186
187    def IsAdbConnected(self):
188        """Check devices connected or not.
189
190        If adb connected and the status is device or offline, return True.
191        If there is no any connection, return False.
192
193        Returns:
194            Boolean, True if adb status not none. False otherwise.
195        """
196        return self.GetAdbConnectionStatus() is not None
197
198    def _DisconnectAndRaiseError(self):
199        """Disconnect adb.
200
201        Disconnect from the device's network address if it shows up in adb
202        devices. For example, adb disconnect 127.0.0.1:5555.
203
204        Raises:
205            errors.WaitForAdbDieError: adb is alive after disconnect adb.
206        """
207        try:
208            if self.IsAdbConnected():
209                adb_disconnect_args = [self._adb_command,
210                                       _ADB_DISCONNECT,
211                                       self._device_address]
212                subprocess.check_call(adb_disconnect_args)
213                # check adb device status
214                self._GetAdbInformation()
215                if self.IsAdbConnected():
216                    raise errors.AdbDisconnectFailed(
217                        "adb disconnect failed, device is still connected and "
218                        "has status: [%s]" % self.GetAdbConnectionStatus())
219
220        except subprocess.CalledProcessError:
221            utils.PrintColorString("Failed to adb disconnect %s" %
222                                   self._device_address,
223                                   utils.TextColors.FAIL)
224
225    def DisconnectAdb(self, retry=False):
226        """Retry to disconnect adb.
227
228        When retry=True, this method will retry to disconnect adb until adb
229        device is completely gone.
230
231        Args:
232            retry: Boolean, True to retry disconnect on error.
233        """
234        retry_count = _MAX_RETRIES_ON_WAIT_ADB_GONE if retry else 0
235        # Wait for adb device is reset and gone.
236        utils.RetryExceptionType(exception_types=errors.AdbDisconnectFailed,
237                                 max_retries=retry_count,
238                                 functor=self._DisconnectAndRaiseError,
239                                 sleep_multiplier=_WAIT_ADB_SLEEP_MULTIPLIER,
240                                 retry_backoff_factor=
241                                 _WAIT_ADB_RETRY_BACKOFF_FACTOR)
242
243    def ConnectAdb(self):
244        """Connect adb.
245
246        Connect adb to the device's network address if the connection is not
247        alive. For example, adb connect 127.0.0.1:5555.
248        """
249        try:
250            if not self.IsAdbConnectionAlive():
251                adb_connect_args = [self._adb_command,
252                                    _ADB_CONNECT,
253                                    self._device_address]
254                subprocess.check_call(adb_connect_args)
255        except subprocess.CalledProcessError:
256            utils.PrintColorString("Failed to adb connect %s" %
257                                   self._device_address,
258                                   utils.TextColors.FAIL)
259
260    def AutoUnlockScreen(self):
261        """Auto unlock screen.
262
263        Auto unlock screen after invoke vnc client.
264        """
265        try:
266            adb_unlock_args = _UNLOCK_SCREEN_KEYEVENT % {
267                "adb_bin": self._adb_command,
268                "device_serial": self._device_serial}
269            subprocess.check_call(adb_unlock_args.split())
270        except subprocess.CalledProcessError:
271            utils.PrintColorString("Failed to unlock screen."
272                                   "(adb_port: %s)" % self._adb_port,
273                                   utils.TextColors.WARNING)
274
275    def EmuCommand(self, *args):
276        """Send an emulator command to the device.
277
278        Args:
279            args: List of strings, the emulator command.
280
281        Returns:
282            Integer, the return code of the adb command.
283            The return code is 0 if adb successfully sends the command to
284            emulator. It is irrelevant to the result of the command.
285        """
286        adb_cmd = [self._adb_command, "-s", self._device_serial, "emu"]
287        adb_cmd.extend(args)
288        proc = subprocess.Popen(adb_cmd, stdin=subprocess.PIPE,
289                                stdout=subprocess.PIPE,
290                                stderr=subprocess.PIPE)
291        proc.communicate()
292        return proc.returncode
293
294    @property
295    def device_information(self):
296        """Return the device information."""
297        return self._device_information
298