• 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 
16 import re
17 import subprocess
18 
19 from acloud import errors
20 from acloud.internal import constants
21 from 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 
43 class 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