1#!/usr/bin/python
2#
3# Copyright (c) 2012 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7""" adb_list_devices: list information about attached Android devices. """
8
9
10import os
11import re
12import shlex
13import subprocess
14import sys
15
16# This file, which resides on every Android device, contains a great deal of
17# information about the device.
18INFO_FILE = '/system/build.prop'
19
20# Default set of properties to query about a device.
21DEFAULT_PROPS_TO_GET = ['ro.product.device', 'ro.build.version.release',
22                        'ro.build.type']
23
24
25def GetDeviceInfo(adb, serial, props_to_get):
26  """ Return a list of values (or "<Unknown>" if no value can be found) for the
27  given set of properties for the device with the given serial number.
28
29  adb: path to the ADB program.
30  serial: serial number of the target device.
31  props_to_get: list of strings indicating which properties to determine.
32  """
33  device_proc = subprocess.Popen([adb, '-s', serial, 'shell', 'cat',
34                                  INFO_FILE], stdout=subprocess.PIPE)
35  code = device_proc.wait()
36  if code != 0:
37    raise Exception('Could not query device with serial number %s.' % serial)
38  output = device_proc.stdout.read()
39  device_info = []
40  for prop in props_to_get:
41    # Find the property in the outputs
42    search_str = r'%s=(\S+)' % prop
43    match = re.search(search_str, output)
44    if not match:
45      value = '<Unknown>'
46    else:
47      value = match.group(1)
48    device_info.append(value)
49  return device_info
50
51
52def PrintPrettyTable(data, file=None):
53  """ Print out the given data in a nicely-spaced format. This function scans
54  the list multiple times and uses extra memory, so don't use it for big data
55  sets.
56
57  data: list of lists of strings, where each list represents a row of data.
58      This table is assumed to be rectangular; if the length of any list differs
59      some of the output may not get printed.
60  file: file-like object into which the table should be written. If none is
61      provided, the table is written to stdout.
62  """
63  if not file:
64    file = sys.stdout
65  column_widths = [0 for length in data[0]]
66  for line in data:
67    column_widths = [max(longest_len, len(prop)) for \
68                    longest_len, prop in zip(column_widths, line)]
69  for line in data:
70    for prop, width in zip(line, column_widths):
71      file.write(prop.ljust(width + 1))
72    file.write('\n')
73
74
75def FindADB(hint=None):
76  """ Attempt to find the ADB program using the following sequence of steps.
77  Returns the path to ADB if it can be found, or None otherwise.
78  1. If a hint was provided, is it a valid path to ADB?
79  2. Is ADB in PATH?
80  3. Is there an environment variable for ADB?
81  4. If the ANDROID_SDK_ROOT variable is set, try to find ADB in the SDK
82     directory.
83
84  hint: string indicating a possible path to ADB.
85  """
86  # 1. If a hint was provided, does it point to ADB?
87  if hint:
88    if os.path.basename(hint) == 'adb':
89      adb = hint
90    else:
91      adb = os.path.join(hint, 'adb')
92    if subprocess.Popen([adb, 'version'], stdout=subprocess.PIPE).wait() == 0:
93      return adb
94
95  # 2. Is 'adb' in our PATH?
96  adb = 'adb'
97  if subprocess.Popen([adb, 'version'], stdout=subprocess.PIPE).wait() == 0:
98    return adb
99
100  # 3. Is there an environment variable for ADB?
101  try:
102    adb = os.environ.get('ADB')
103    if subprocess.Popen([adb, 'version'], stdout=subprocess.PIPE).wait() == 0:
104      return adb
105  except:
106    pass
107
108  # 4. If ANDROID_SDK_ROOT is set, try to find ADB in the SDK directory.
109  try:
110    sdk_dir = os.environ.get('ANDROID_SDK_ROOT')
111    adb = os.path.join(sdk_dir, 'platform-tools', 'adb')
112    if subprocess.Popen([adb, 'version'], stdout=subprocess.PIPE).wait() == 0:
113      return adb
114  except:
115    pass
116  return None
117
118
119def main(argv):
120  """ Print out information about connected Android devices. By default, print
121  the serial number, status, device name, OS version, and build type of each
122  device. If any arguments are supplied on the command line, print the serial
123  number and status for each device along with values for those arguments
124  interpreted as properties.
125  """
126  if len(argv) > 1:
127    props_to_get = argv[1:]
128  else:
129    props_to_get = DEFAULT_PROPS_TO_GET
130  adb = FindADB()
131  if not adb:
132    raise Exception('Could not find ADB!')
133  proc = subprocess.Popen([adb, 'devices'], stdout=subprocess.PIPE)
134  code = proc.wait()
135  if code != 0:
136    raise Exception('Failure in ADB: could not find attached devices.')
137  header = ['Serial', 'Status']
138  header.extend(props_to_get)
139  output_lines = [header]
140  for line in proc.stdout:
141    line = line.rstrip()
142    if line != 'List of devices attached' and line != '':
143      line_list = shlex.split(line)
144      serial = line_list[0]
145      status = line_list[1]
146      device_info = [serial, status]
147      device_info.extend(GetDeviceInfo(adb, serial, props_to_get))
148      output_lines.append(device_info)
149  PrintPrettyTable(output_lines)
150
151
152if __name__ == '__main__':
153  sys.exit(main(sys.argv))