Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: build/android/buildbot/bb_device_status_check.py

Issue 1123263005: [Android] Remove hand-rolled adb devices call from bb_device_status_check. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/android/pylib/device/adb_wrapper.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2013 The Chromium Authors. All rights reserved. 3 # Copyright 2013 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 """A class to keep track of devices across builds and report state.""" 7 """A class to keep track of devices across builds and report state."""
8 import json 8 import json
9 import logging 9 import logging
10 import optparse 10 import optparse
11 import os 11 import os
12 import psutil 12 import psutil
13 import re 13 import re
14 import signal 14 import signal
15 import smtplib 15 import smtplib
16 import subprocess 16 import subprocess
17 import sys 17 import sys
18 import time 18 import time
19 import urllib 19 import urllib
20 20
21 import bb_annotations 21 import bb_annotations
22 import bb_utils 22 import bb_utils
23 23
24 sys.path.append(os.path.join(os.path.dirname(__file__), 24 sys.path.append(os.path.join(os.path.dirname(__file__),
25 os.pardir, os.pardir, 'util', 'lib', 25 os.pardir, os.pardir, 'util', 'lib',
26 'common')) 26 'common'))
27 import perf_tests_results_helper # pylint: disable=F0401 27 import perf_tests_results_helper # pylint: disable=F0401
28 28
29 sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 29 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
30 from pylib import android_commands
31 from pylib import constants 30 from pylib import constants
32 from pylib.cmd_helper import GetCmdOutput 31 from pylib.cmd_helper import GetCmdOutput
32 from pylib.device import adb_wrapper
33 from pylib.device import battery_utils 33 from pylib.device import battery_utils
34 from pylib.device import device_blacklist 34 from pylib.device import device_blacklist
35 from pylib.device import device_errors 35 from pylib.device import device_errors
36 from pylib.device import device_list 36 from pylib.device import device_list
37 from pylib.device import device_utils 37 from pylib.device import device_utils
38 from pylib.utils import run_tests_helper 38 from pylib.utils import run_tests_helper
39 39
40 _RE_DEVICE_ID = re.compile('Device ID = (\d+)') 40 _RE_DEVICE_ID = re.compile('Device ID = (\d+)')
41 41
42 def DeviceInfo(serial, options): 42 def DeviceInfo(device, options):
43 """Gathers info on a device via various adb calls. 43 """Gathers info on a device via various adb calls.
44 44
45 Args: 45 Args:
46 serial: The serial of the attached device to construct info about. 46 device: A DeviceUtils instance for the device to construct info about.
47 47
48 Returns: 48 Returns:
49 Tuple of device type, build id, report as a string, error messages, and 49 Tuple of device type, build id, report as a string, error messages, and
50 boolean indicating whether or not device can be used for testing. 50 boolean indicating whether or not device can be used for testing.
51 """ 51 """
52 device = device_utils.DeviceUtils(serial)
53 battery = battery_utils.BatteryUtils(device) 52 battery = battery_utils.BatteryUtils(device)
54 53
55 battery_info = {} 54 battery_info = {}
56 battery_level = 100 55 battery_level = 100
57 errors = [] 56 errors = []
58 dev_good = True 57 dev_good = True
59 json_data = { 58 json_data = {
60 'serial': serial, 59 'serial': device.adb.GetDeviceSerial(),
61 'type': device.build_product, 60 'type': device.build_product,
62 'build': device.build_id, 61 'build': device.build_id,
63 'build_detail': device.GetProp('ro.build.fingerprint'), 62 'build_detail': device.GetProp('ro.build.fingerprint'),
64 'battery': {}, 63 'battery': {},
65 'imei_slice': 'Unknown', 64 'imei_slice': 'Unknown',
66 'wifi_ip': device.GetProp('dhcp.wlan0.ipaddress'), 65 'wifi_ip': device.GetProp('dhcp.wlan0.ipaddress'),
67 } 66 }
68 67
69 try: 68 try:
70 try: 69 try:
71 battery_info = battery.GetBatteryInfo(timeout=5) 70 battery_info = battery.GetBatteryInfo(timeout=5)
72 battery_level = int(battery_info.get('level', battery_level)) 71 battery_level = int(battery_info.get('level', battery_level))
73 json_data['battery'] = battery_info 72 json_data['battery'] = battery_info
74 except device_errors.CommandFailedError: 73 except device_errors.CommandFailedError:
75 logging.exception('Failed to get battery information for %s', serial) 74 logging.exception('Failed to get battery information for %s', str(device))
76 75
77 try: 76 try:
78 for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'], 77 for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'],
79 check_return=True, timeout=5): 78 check_return=True, timeout=5):
80 m = _RE_DEVICE_ID.match(l) 79 m = _RE_DEVICE_ID.match(l)
81 if m: 80 if m:
82 json_data['imei_slice'] = m.group(1)[-6:] 81 json_data['imei_slice'] = m.group(1)[-6:]
83 except device_errors.CommandFailedError: 82 except device_errors.CommandFailedError:
84 logging.exception('Failed to get IMEI slice for %s', serial) 83 logging.exception('Failed to get IMEI slice for %s', str(device))
85 84
86 if battery_level < 15: 85 if battery_level < 15:
87 errors += ['Device critically low in battery.'] 86 errors += ['Device critically low in battery.']
88 dev_good = False 87 dev_good = False
89 if not battery.GetCharging(): 88 if not battery.GetCharging():
90 battery.SetCharging(True) 89 battery.SetCharging(True)
91 if not options.no_provisioning_check: 90 if not options.no_provisioning_check:
92 setup_wizard_disabled = ( 91 setup_wizard_disabled = (
93 device.GetProp('ro.setupwizard.mode') == 'DISABLED') 92 device.GetProp('ro.setupwizard.mode') == 'DISABLED')
94 if not setup_wizard_disabled and device.build_type != 'user': 93 if not setup_wizard_disabled and device.build_type != 'user':
95 errors += ['Setup wizard not disabled. Was it provisioned correctly?'] 94 errors += ['Setup wizard not disabled. Was it provisioned correctly?']
96 if (device.product_name == 'mantaray' and 95 if (device.product_name == 'mantaray' and
97 battery_info.get('AC powered', None) != 'true'): 96 battery_info.get('AC powered', None) != 'true'):
98 errors += ['Mantaray device not connected to AC power.'] 97 errors += ['Mantaray device not connected to AC power.']
99 except device_errors.CommandFailedError: 98 except device_errors.CommandFailedError:
100 logging.exception('Failure while getting device status.') 99 logging.exception('Failure while getting device status.')
101 dev_good = False 100 dev_good = False
102 except device_errors.CommandTimeoutError: 101 except device_errors.CommandTimeoutError:
103 logging.exception('Timeout while getting device status.') 102 logging.exception('Timeout while getting device status.')
104 dev_good = False 103 dev_good = False
105 104
106 return (device.build_product, device.build_id, battery_level, errors, 105 return (device.build_product, device.build_id, battery_level, errors,
107 dev_good, json_data) 106 dev_good, json_data)
108 107
109 108
110 def CheckForMissingDevices(options, adb_online_devs): 109 def CheckForMissingDevices(options, devices):
111 """Uses file of previous online devices to detect broken phones. 110 """Uses file of previous online devices to detect broken phones.
112 111
113 Args: 112 Args:
114 options: out_dir parameter of options argument is used as the base 113 options: out_dir parameter of options argument is used as the base
115 directory to load and update the cache file. 114 directory to load and update the cache file.
116 adb_online_devs: A list of serial numbers of the currently visible 115 devices: A list of DeviceUtils instance for the currently visible and
117 and online attached devices. 116 online attached devices.
118 """ 117 """
119 out_dir = os.path.abspath(options.out_dir) 118 out_dir = os.path.abspath(options.out_dir)
119 device_serials = set(d.adb.GetDeviceSerial() for d in devices)
120 120
121 # last_devices denotes all known devices prior to this run 121 # last_devices denotes all known devices prior to this run
122 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME) 122 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME)
123 last_missing_devices_path = os.path.join(out_dir, 123 last_missing_devices_path = os.path.join(out_dir,
124 device_list.LAST_MISSING_DEVICES_FILENAME) 124 device_list.LAST_MISSING_DEVICES_FILENAME)
125 try: 125 try:
126 last_devices = device_list.GetPersistentDeviceList(last_devices_path) 126 last_devices = device_list.GetPersistentDeviceList(last_devices_path)
127 except IOError: 127 except IOError:
128 # Ignore error, file might not exist 128 # Ignore error, file might not exist
129 last_devices = [] 129 last_devices = []
130 130
131 try: 131 try:
132 last_missing_devices = device_list.GetPersistentDeviceList( 132 last_missing_devices = device_list.GetPersistentDeviceList(
133 last_missing_devices_path) 133 last_missing_devices_path)
134 except IOError: 134 except IOError:
135 last_missing_devices = [] 135 last_missing_devices = []
136 136
137 missing_devs = list(set(last_devices) - set(adb_online_devs)) 137 missing_devs = list(set(last_devices) - device_serials)
138 new_missing_devs = list(set(missing_devs) - set(last_missing_devices)) 138 new_missing_devs = list(set(missing_devs) - set(last_missing_devices))
139 139
140 if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'): 140 if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'):
141 logging.info('new_missing_devs %s' % new_missing_devs) 141 logging.info('new_missing_devs %s' % new_missing_devs)
142 devices_missing_msg = '%d devices not detected.' % len(missing_devs) 142 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
143 bb_annotations.PrintSummaryText(devices_missing_msg) 143 bb_annotations.PrintSummaryText(devices_missing_msg)
144 144
145 from_address = 'chrome-bot@chromium.org' 145 from_address = 'chrome-bot@chromium.org'
146 to_addresses = ['chrome-labs-tech-ticket@google.com', 146 to_addresses = ['chrome-labs-tech-ticket@google.com',
147 'chrome-android-device-alert@google.com'] 147 'chrome-android-device-alert@google.com']
148 cc_addresses = ['chrome-android-device-alert@google.com'] 148 cc_addresses = ['chrome-android-device-alert@google.com']
149 subject = 'Devices offline on %s, %s, %s' % ( 149 subject = 'Devices offline on %s, %s, %s' % (
150 os.environ.get('BUILDBOT_SLAVENAME'), 150 os.environ.get('BUILDBOT_SLAVENAME'),
151 os.environ.get('BUILDBOT_BUILDERNAME'), 151 os.environ.get('BUILDBOT_BUILDERNAME'),
152 os.environ.get('BUILDBOT_BUILDNUMBER')) 152 os.environ.get('BUILDBOT_BUILDNUMBER'))
153 msg = ('Please reboot the following devices:\n%s' % 153 msg = ('Please reboot the following devices:\n%s' %
154 '\n'.join(map(str, new_missing_devs))) 154 '\n'.join(map(str, new_missing_devs)))
155 SendEmail(from_address, to_addresses, cc_addresses, subject, msg) 155 SendEmail(from_address, to_addresses, cc_addresses, subject, msg)
156 156
157 all_known_devices = list(set(adb_online_devs) | set(last_devices)) 157 all_known_devices = list(device_serials | set(last_devices))
158 device_list.WritePersistentDeviceList(last_devices_path, all_known_devices) 158 device_list.WritePersistentDeviceList(last_devices_path, all_known_devices)
159 device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs) 159 device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs)
160 160
161 if not all_known_devices: 161 if not all_known_devices:
162 # This can happen if for some reason the .last_devices file is not 162 # This can happen if for some reason the .last_devices file is not
163 # present or if it was empty. 163 # present or if it was empty.
164 return ['No online devices. Have any devices been plugged in?'] 164 return ['No online devices. Have any devices been plugged in?']
165 if missing_devs: 165 if missing_devs:
166 devices_missing_msg = '%d devices not detected.' % len(missing_devs) 166 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
167 bb_annotations.PrintSummaryText(devices_missing_msg) 167 bb_annotations.PrintSummaryText(devices_missing_msg)
168 168 return ['Current online devices: %s' % ', '.join(d for d in device_serials),
169 # TODO(navabi): Debug by printing both output from GetCmdOutput and 169 '%s are no longer visible. Were they removed?' % missing_devs]
170 # GetAttachedDevices to compare results.
171 crbug_link = ('https://code.google.com/p/chromium/issues/entry?summary='
172 '%s&comment=%s&labels=Restrict-View-Google,OS-Android,Infra' %
173 (urllib.quote('Device Offline'),
174 urllib.quote('Buildbot: %s %s\n'
175 'Build: %s\n'
176 '(please don\'t change any labels)' %
177 (os.environ.get('BUILDBOT_BUILDERNAME'),
178 os.environ.get('BUILDBOT_SLAVENAME'),
179 os.environ.get('BUILDBOT_BUILDNUMBER')))))
180 return ['Current online devices: %s' % adb_online_devs,
181 '%s are no longer visible. Were they removed?' % missing_devs,
182 'SHERIFF:',
183 '@@@STEP_LINK@Click here to file a bug@%s@@@' % crbug_link,
184 'Cache file: %s' % last_devices_path,
185 'adb devices: %s' % GetCmdOutput(['adb', 'devices']),
186 'adb devices(GetAttachedDevices): %s' % adb_online_devs]
187 else: 170 else:
188 new_devs = set(adb_online_devs) - set(last_devices) 171 new_devs = device_serials - set(last_devices)
189 if new_devs and os.path.exists(last_devices_path): 172 if new_devs and os.path.exists(last_devices_path):
190 bb_annotations.PrintWarning() 173 bb_annotations.PrintWarning()
191 bb_annotations.PrintSummaryText( 174 bb_annotations.PrintSummaryText(
192 '%d new devices detected' % len(new_devs)) 175 '%d new devices detected' % len(new_devs))
193 logging.info('New devices detected:') 176 logging.info('New devices detected:')
194 for d in new_devs: 177 for d in new_devs:
195 logging.info(' %s', d) 178 logging.info(' %s', d)
196 179
197 180
198 def SendEmail(from_address, to_addresses, cc_addresses, subject, msg): 181 def SendEmail(from_address, to_addresses, cc_addresses, subject, msg):
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
290 run_tests_helper.SetLogLevel(options.verbose) 273 run_tests_helper.SetLogLevel(options.verbose)
291 274
292 # Remove the last build's "bad devices" before checking device statuses. 275 # Remove the last build's "bad devices" before checking device statuses.
293 device_blacklist.ResetBlacklist() 276 device_blacklist.ResetBlacklist()
294 277
295 try: 278 try:
296 expected_devices = device_list.GetPersistentDeviceList( 279 expected_devices = device_list.GetPersistentDeviceList(
297 os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME)) 280 os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME))
298 except IOError: 281 except IOError:
299 expected_devices = [] 282 expected_devices = []
300 devices = android_commands.GetAttachedDevices() 283 devices = device_utils.DeviceUtils.HealthyDevices()
284 device_serials = [d.adb.GetDeviceSerial() for d in devices]
301 # Only restart usb if devices are missing. 285 # Only restart usb if devices are missing.
302 if set(expected_devices) != set(devices): 286 if set(expected_devices) != set(device_serials):
303 logging.warning('expected_devices: %s', expected_devices) 287 logging.warning('expected_devices: %s', expected_devices)
304 logging.warning('devices: %s', devices) 288 logging.warning('devices: %s', device_serials)
305 KillAllAdb() 289 KillAllAdb()
306 retries = 5 290 retries = 5
307 usb_restarted = True 291 usb_restarted = True
308 if options.restart_usb: 292 if options.restart_usb:
309 if not RestartUsb(): 293 if not RestartUsb():
310 usb_restarted = False 294 usb_restarted = False
311 bb_annotations.PrintWarning() 295 bb_annotations.PrintWarning()
312 logging.error('USB reset stage failed, ' 296 logging.error('USB reset stage failed, '
313 'wait for any device to come back.') 297 'wait for any device to come back.')
314 while retries: 298 while retries:
315 logging.info('retry adb devices...') 299 logging.info('retry adb devices...')
316 time.sleep(1) 300 time.sleep(1)
317 devices = android_commands.GetAttachedDevices() 301 devices = device_utils.DeviceUtils.HealthyDevices()
318 if set(expected_devices) == set(devices): 302 device_serials = [d.adb.GetDeviceSerial() for d in devices]
303 if set(expected_devices) == set(device_serials):
319 # All devices are online, keep going. 304 # All devices are online, keep going.
320 break 305 break
321 if not usb_restarted and devices: 306 if not usb_restarted and devices:
322 # The USB wasn't restarted, but there's at least one device online. 307 # The USB wasn't restarted, but there's at least one device online.
323 # No point in trying to wait for all devices. 308 # No point in trying to wait for all devices.
324 break 309 break
325 retries -= 1 310 retries -= 1
326 311
327 # TODO(navabi): Test to make sure this fails and then fix call 312 # TODO(navabi): Test to make sure this fails and then fix call
navabi 2015/05/19 20:21:00 Remove this TODO. I assume it is referring to the
328 offline_devices = android_commands.GetAttachedDevices(
329 hardware=False, emulator=False, offline=True)
330
331 types, builds, batteries, errors, devices_ok, json_data = ( 313 types, builds, batteries, errors, devices_ok, json_data = (
332 [], [], [], [], [], []) 314 [], [], [], [], [], [])
333 if devices: 315 if devices:
334 types, builds, batteries, errors, devices_ok, json_data = ( 316 types, builds, batteries, errors, devices_ok, json_data = (
335 zip(*[DeviceInfo(dev, options) for dev in devices])) 317 zip(*[DeviceInfo(dev, options) for dev in devices]))
336 318
337 # Write device info to file for buildbot info display. 319 # Write device info to file for buildbot info display.
338 if os.path.exists('/home/chrome-bot'): 320 if os.path.exists('/home/chrome-bot'):
339 with open('/home/chrome-bot/.adb_device_info', 'w') as f: 321 with open('/home/chrome-bot/.adb_device_info', 'w') as f:
340 for device in json_data: 322 for device in json_data:
(...skipping 15 matching lines...) Expand all
356 for j in json_data: 338 for j in json_data:
357 logging.info('Device %s (%s)', j.get('serial'), j.get('type')) 339 logging.info('Device %s (%s)', j.get('serial'), j.get('type'))
358 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail')) 340 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail'))
359 logging.info(' Current Battery Service state:') 341 logging.info(' Current Battery Service state:')
360 for k, v in j.get('battery', {}).iteritems(): 342 for k, v in j.get('battery', {}).iteritems():
361 logging.info(' %s: %s', k, v) 343 logging.info(' %s: %s', k, v)
362 logging.info(' IMEI slice: %s', j.get('imei_slice')) 344 logging.info(' IMEI slice: %s', j.get('imei_slice'))
363 logging.info(' WiFi IP: %s', j.get('wifi_ip')) 345 logging.info(' WiFi IP: %s', j.get('wifi_ip'))
364 346
365 347
366 for serial, dev_errors in zip(devices, errors): 348 for dev, dev_errors in zip(devices, errors):
367 if dev_errors: 349 if dev_errors:
368 err_msg += ['%s errors:' % serial] 350 err_msg += ['%s errors:' % str(dev)]
369 err_msg += [' %s' % error for error in dev_errors] 351 err_msg += [' %s' % error for error in dev_errors]
370 352
371 if err_msg: 353 if err_msg:
372 bb_annotations.PrintWarning() 354 bb_annotations.PrintWarning()
373 for e in err_msg: 355 for e in err_msg:
374 logging.error(e) 356 logging.error(e)
375 from_address = 'buildbot@chromium.org' 357 from_address = 'buildbot@chromium.org'
376 to_addresses = ['chromium-android-device-alerts@google.com'] 358 to_addresses = ['chromium-android-device-alerts@google.com']
377 bot_name = os.environ.get('BUILDBOT_BUILDERNAME') 359 bot_name = os.environ.get('BUILDBOT_BUILDERNAME')
378 slave_name = os.environ.get('BUILDBOT_SLAVENAME') 360 slave_name = os.environ.get('BUILDBOT_SLAVENAME')
379 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) 361 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name)
380 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg)) 362 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg))
381 363
382 if options.device_status_dashboard: 364 if options.device_status_dashboard:
365 offline_devices = [
366 device_utils.DeviceUtils(a)
367 for a in adb_wrapper.AdbWrapper.Devices(is_ready=False)
368 if a.GetState() == 'offline']
369
383 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices', 370 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices',
384 [len(devices)], 'devices') 371 [len(devices)], 'devices')
385 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices', 372 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices',
386 [len(offline_devices)], 'devices', 373 [len(offline_devices)], 'devices',
387 'unimportant') 374 'unimportant')
388 for serial, battery in zip(devices, batteries): 375 for dev, battery in zip(devices, batteries):
389 perf_tests_results_helper.PrintPerfResult('DeviceBattery', serial, 376 perf_tests_results_helper.PrintPerfResult('DeviceBattery', str(dev),
390 [battery], '%', 377 [battery], '%',
391 'unimportant') 378 'unimportant')
392 379
393 if options.json_output: 380 if options.json_output:
394 with open(options.json_output, 'wb') as f: 381 with open(options.json_output, 'wb') as f:
395 f.write(json.dumps(json_data, indent=4)) 382 f.write(json.dumps(json_data, indent=4))
396 383
397 num_failed_devs = 0 384 num_failed_devs = 0
398 for device_ok, device in zip(devices_ok, devices): 385 for device_ok, device in zip(devices_ok, devices):
399 if not device_ok: 386 if not device_ok:
400 logging.warning('Blacklisting %s', str(device)) 387 logging.warning('Blacklisting %s', str(device))
401 device_blacklist.ExtendBlacklist([str(device)]) 388 device_blacklist.ExtendBlacklist([str(device)])
402 num_failed_devs += 1 389 num_failed_devs += 1
403 390
404 if num_failed_devs == len(devices): 391 if num_failed_devs == len(devices):
405 return 2 392 return 2
406 393
407 if not devices: 394 if not devices:
408 return 1 395 return 1
409 396
410 397
411 if __name__ == '__main__': 398 if __name__ == '__main__':
412 sys.exit(main()) 399 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/device/adb_wrapper.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698