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