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

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

Issue 1180693002: Update from https://crrev.com/333737 (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: rebased Created 5 years, 6 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 | « build/android/apkbuilder_action.gypi ('k') | build/android/buildbot/bb_device_steps.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 build_product = ''
55 build_id = ''
56 battery_level = 100 56 battery_level = 100
57 errors = [] 57 errors = []
58 dev_good = True 58 dev_good = True
59 json_data = { 59 json_data = {}
60 'serial': serial,
61 'type': device.build_product,
62 'build': device.build_id,
63 'build_detail': device.GetProp('ro.build.fingerprint'),
64 'battery': {},
65 'imei_slice': 'Unknown',
66 'wifi_ip': device.GetProp('dhcp.wlan0.ipaddress'),
67 }
68 60
69 try: 61 try:
62 build_product = device.build_product
63 build_id = device.build_id
64
65 json_data = {
66 'serial': device.adb.GetDeviceSerial(),
67 'type': build_product,
68 'build': build_id,
69 'build_detail': device.GetProp('ro.build.fingerprint'),
70 'battery': {},
71 'imei_slice': 'Unknown',
72 'wifi_ip': device.GetProp('dhcp.wlan0.ipaddress'),
73 }
74
75 battery_info = {}
70 try: 76 try:
71 battery_info = battery.GetBatteryInfo(timeout=5) 77 battery_info = battery.GetBatteryInfo(timeout=5)
72 battery_level = int(battery_info.get('level', battery_level)) 78 battery_level = int(battery_info.get('level', battery_level))
73 json_data['battery'] = battery_info 79 json_data['battery'] = battery_info
74 except device_errors.CommandFailedError: 80 except device_errors.CommandFailedError:
75 logging.exception('Failed to get battery information for %s', serial) 81 logging.exception('Failed to get battery information for %s', str(device))
76 82
77 try: 83 try:
78 for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'], 84 for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'],
79 check_return=True, timeout=5): 85 check_return=True, timeout=5):
80 m = _RE_DEVICE_ID.match(l) 86 m = _RE_DEVICE_ID.match(l)
81 if m: 87 if m:
82 json_data['imei_slice'] = m.group(1)[-6:] 88 json_data['imei_slice'] = m.group(1)[-6:]
83 except device_errors.CommandFailedError: 89 except device_errors.CommandFailedError:
84 logging.exception('Failed to get IMEI slice for %s', serial) 90 logging.exception('Failed to get IMEI slice for %s', str(device))
85 91
86 if battery_level < 15: 92 if battery_level < 15:
87 errors += ['Device critically low in battery.'] 93 errors += ['Device critically low in battery.']
88 dev_good = False 94 dev_good = False
89 if not battery.GetCharging(): 95 if not battery.GetCharging():
90 battery.SetCharging(True) 96 battery.SetCharging(True)
91 if not options.no_provisioning_check: 97 if not options.no_provisioning_check:
92 setup_wizard_disabled = ( 98 setup_wizard_disabled = (
93 device.GetProp('ro.setupwizard.mode') == 'DISABLED') 99 device.GetProp('ro.setupwizard.mode') == 'DISABLED')
94 if not setup_wizard_disabled and device.build_type != 'user': 100 if not setup_wizard_disabled and device.build_type != 'user':
95 errors += ['Setup wizard not disabled. Was it provisioned correctly?'] 101 errors += ['Setup wizard not disabled. Was it provisioned correctly?']
96 if (device.product_name == 'mantaray' and 102 if (device.product_name == 'mantaray' and
97 battery_info.get('AC powered', None) != 'true'): 103 battery_info.get('AC powered', None) != 'true'):
98 errors += ['Mantaray device not connected to AC power.'] 104 errors += ['Mantaray device not connected to AC power.']
99 except device_errors.CommandFailedError: 105 except device_errors.CommandFailedError:
100 logging.exception('Failure while getting device status.') 106 logging.exception('Failure while getting device status.')
101 dev_good = False 107 dev_good = False
102 except device_errors.CommandTimeoutError: 108 except device_errors.CommandTimeoutError:
103 logging.exception('Timeout while getting device status.') 109 logging.exception('Timeout while getting device status.')
104 dev_good = False 110 dev_good = False
105 111
106 return (device.build_product, device.build_id, battery_level, errors, 112 return (build_product, build_id, battery_level, errors, dev_good, json_data)
107 dev_good, json_data)
108 113
109 114
110 def CheckForMissingDevices(options, adb_online_devs): 115 def CheckForMissingDevices(options, devices):
111 """Uses file of previous online devices to detect broken phones. 116 """Uses file of previous online devices to detect broken phones.
112 117
113 Args: 118 Args:
114 options: out_dir parameter of options argument is used as the base 119 options: out_dir parameter of options argument is used as the base
115 directory to load and update the cache file. 120 directory to load and update the cache file.
116 adb_online_devs: A list of serial numbers of the currently visible 121 devices: A list of DeviceUtils instance for the currently visible and
117 and online attached devices. 122 online attached devices.
118 """ 123 """
119 out_dir = os.path.abspath(options.out_dir) 124 out_dir = os.path.abspath(options.out_dir)
125 device_serials = set(d.adb.GetDeviceSerial() for d in devices)
120 126
121 # last_devices denotes all known devices prior to this run 127 # last_devices denotes all known devices prior to this run
122 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME) 128 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME)
123 last_missing_devices_path = os.path.join(out_dir, 129 last_missing_devices_path = os.path.join(out_dir,
124 device_list.LAST_MISSING_DEVICES_FILENAME) 130 device_list.LAST_MISSING_DEVICES_FILENAME)
125 try: 131 try:
126 last_devices = device_list.GetPersistentDeviceList(last_devices_path) 132 last_devices = device_list.GetPersistentDeviceList(last_devices_path)
127 except IOError: 133 except IOError:
128 # Ignore error, file might not exist 134 # Ignore error, file might not exist
129 last_devices = [] 135 last_devices = []
130 136
131 try: 137 try:
132 last_missing_devices = device_list.GetPersistentDeviceList( 138 last_missing_devices = device_list.GetPersistentDeviceList(
133 last_missing_devices_path) 139 last_missing_devices_path)
134 except IOError: 140 except IOError:
135 last_missing_devices = [] 141 last_missing_devices = []
136 142
137 missing_devs = list(set(last_devices) - set(adb_online_devs)) 143 missing_devs = list(set(last_devices) - device_serials)
138 new_missing_devs = list(set(missing_devs) - set(last_missing_devices)) 144 new_missing_devs = list(set(missing_devs) - set(last_missing_devices))
139 145
140 if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'): 146 if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'):
141 logging.info('new_missing_devs %s' % new_missing_devs) 147 logging.info('new_missing_devs %s' % new_missing_devs)
142 devices_missing_msg = '%d devices not detected.' % len(missing_devs) 148 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
143 bb_annotations.PrintSummaryText(devices_missing_msg) 149 bb_annotations.PrintSummaryText(devices_missing_msg)
144 150
145 from_address = 'chrome-bot@chromium.org' 151 from_address = 'chrome-bot@chromium.org'
146 to_addresses = ['chrome-labs-tech-ticket@google.com', 152 to_addresses = ['chrome-labs-tech-ticket@google.com',
147 'chrome-android-device-alert@google.com'] 153 'chrome-android-device-alert@google.com']
148 cc_addresses = ['chrome-android-device-alert@google.com'] 154 cc_addresses = ['chrome-android-device-alert@google.com']
149 subject = 'Devices offline on %s, %s, %s' % ( 155 subject = 'Devices offline on %s, %s, %s' % (
150 os.environ.get('BUILDBOT_SLAVENAME'), 156 os.environ.get('BUILDBOT_SLAVENAME'),
151 os.environ.get('BUILDBOT_BUILDERNAME'), 157 os.environ.get('BUILDBOT_BUILDERNAME'),
152 os.environ.get('BUILDBOT_BUILDNUMBER')) 158 os.environ.get('BUILDBOT_BUILDNUMBER'))
153 msg = ('Please reboot the following devices:\n%s' % 159 msg = ('Please reboot the following devices:\n%s' %
154 '\n'.join(map(str, new_missing_devs))) 160 '\n'.join(map(str, new_missing_devs)))
155 SendEmail(from_address, to_addresses, cc_addresses, subject, msg) 161 SendEmail(from_address, to_addresses, cc_addresses, subject, msg)
156 162
157 all_known_devices = list(set(adb_online_devs) | set(last_devices)) 163 all_known_devices = list(device_serials | set(last_devices))
158 device_list.WritePersistentDeviceList(last_devices_path, all_known_devices) 164 device_list.WritePersistentDeviceList(last_devices_path, all_known_devices)
159 device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs) 165 device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs)
160 166
161 if not all_known_devices: 167 if not all_known_devices:
162 # This can happen if for some reason the .last_devices file is not 168 # This can happen if for some reason the .last_devices file is not
163 # present or if it was empty. 169 # present or if it was empty.
164 return ['No online devices. Have any devices been plugged in?'] 170 return ['No online devices. Have any devices been plugged in?']
165 if missing_devs: 171 if missing_devs:
166 devices_missing_msg = '%d devices not detected.' % len(missing_devs) 172 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
167 bb_annotations.PrintSummaryText(devices_missing_msg) 173 bb_annotations.PrintSummaryText(devices_missing_msg)
168 174 return ['Current online devices: %s' % ', '.join(d for d in device_serials),
169 # TODO(navabi): Debug by printing both output from GetCmdOutput and 175 '%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: 176 else:
188 new_devs = set(adb_online_devs) - set(last_devices) 177 new_devs = device_serials - set(last_devices)
189 if new_devs and os.path.exists(last_devices_path): 178 if new_devs and os.path.exists(last_devices_path):
190 bb_annotations.PrintWarning() 179 bb_annotations.PrintWarning()
191 bb_annotations.PrintSummaryText( 180 bb_annotations.PrintSummaryText(
192 '%d new devices detected' % len(new_devs)) 181 '%d new devices detected' % len(new_devs))
193 logging.info('New devices detected:') 182 logging.info('New devices detected:')
194 for d in new_devs: 183 for d in new_devs:
195 logging.info(' %s', d) 184 logging.info(' %s', d)
196 185
197 186
198 def SendEmail(from_address, to_addresses, cc_addresses, subject, msg): 187 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) 279 run_tests_helper.SetLogLevel(options.verbose)
291 280
292 # Remove the last build's "bad devices" before checking device statuses. 281 # Remove the last build's "bad devices" before checking device statuses.
293 device_blacklist.ResetBlacklist() 282 device_blacklist.ResetBlacklist()
294 283
295 try: 284 try:
296 expected_devices = device_list.GetPersistentDeviceList( 285 expected_devices = device_list.GetPersistentDeviceList(
297 os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME)) 286 os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME))
298 except IOError: 287 except IOError:
299 expected_devices = [] 288 expected_devices = []
300 devices = android_commands.GetAttachedDevices() 289 devices = device_utils.DeviceUtils.HealthyDevices()
290 device_serials = [d.adb.GetDeviceSerial() for d in devices]
301 # Only restart usb if devices are missing. 291 # Only restart usb if devices are missing.
302 if set(expected_devices) != set(devices): 292 if set(expected_devices) != set(device_serials):
303 logging.warning('expected_devices: %s', expected_devices) 293 logging.warning('expected_devices: %s', expected_devices)
304 logging.warning('devices: %s', devices) 294 logging.warning('devices: %s', device_serials)
305 KillAllAdb() 295 KillAllAdb()
306 retries = 5 296 retries = 5
307 usb_restarted = True 297 usb_restarted = True
308 if options.restart_usb: 298 if options.restart_usb:
309 if not RestartUsb(): 299 if not RestartUsb():
310 usb_restarted = False 300 usb_restarted = False
311 bb_annotations.PrintWarning() 301 bb_annotations.PrintWarning()
312 logging.error('USB reset stage failed, ' 302 logging.error('USB reset stage failed, '
313 'wait for any device to come back.') 303 'wait for any device to come back.')
314 while retries: 304 while retries:
315 logging.info('retry adb devices...') 305 logging.info('retry adb devices...')
316 time.sleep(1) 306 time.sleep(1)
317 devices = android_commands.GetAttachedDevices() 307 devices = device_utils.DeviceUtils.HealthyDevices()
318 if set(expected_devices) == set(devices): 308 device_serials = [d.adb.GetDeviceSerial() for d in devices]
309 if set(expected_devices) == set(device_serials):
319 # All devices are online, keep going. 310 # All devices are online, keep going.
320 break 311 break
321 if not usb_restarted and devices: 312 if not usb_restarted and devices:
322 # The USB wasn't restarted, but there's at least one device online. 313 # The USB wasn't restarted, but there's at least one device online.
323 # No point in trying to wait for all devices. 314 # No point in trying to wait for all devices.
324 break 315 break
325 retries -= 1 316 retries -= 1
326 317
327 # TODO(navabi): Test to make sure this fails and then fix call
328 offline_devices = android_commands.GetAttachedDevices(
329 hardware=False, emulator=False, offline=True)
330
331 types, builds, batteries, errors, devices_ok, json_data = ( 318 types, builds, batteries, errors, devices_ok, json_data = (
332 [], [], [], [], [], []) 319 [], [], [], [], [], [])
333 if devices: 320 if devices:
334 types, builds, batteries, errors, devices_ok, json_data = ( 321 types, builds, batteries, errors, devices_ok, json_data = (
335 zip(*[DeviceInfo(dev, options) for dev in devices])) 322 zip(*[DeviceInfo(dev, options) for dev in devices]))
336 323
337 # Write device info to file for buildbot info display. 324 # Write device info to file for buildbot info display.
338 if os.path.exists('/home/chrome-bot'): 325 if os.path.exists('/home/chrome-bot'):
339 with open('/home/chrome-bot/.adb_device_info', 'w') as f: 326 with open('/home/chrome-bot/.adb_device_info', 'w') as f:
340 for device in json_data: 327 for device in json_data:
(...skipping 15 matching lines...) Expand all
356 for j in json_data: 343 for j in json_data:
357 logging.info('Device %s (%s)', j.get('serial'), j.get('type')) 344 logging.info('Device %s (%s)', j.get('serial'), j.get('type'))
358 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail')) 345 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail'))
359 logging.info(' Current Battery Service state:') 346 logging.info(' Current Battery Service state:')
360 for k, v in j.get('battery', {}).iteritems(): 347 for k, v in j.get('battery', {}).iteritems():
361 logging.info(' %s: %s', k, v) 348 logging.info(' %s: %s', k, v)
362 logging.info(' IMEI slice: %s', j.get('imei_slice')) 349 logging.info(' IMEI slice: %s', j.get('imei_slice'))
363 logging.info(' WiFi IP: %s', j.get('wifi_ip')) 350 logging.info(' WiFi IP: %s', j.get('wifi_ip'))
364 351
365 352
366 for serial, dev_errors in zip(devices, errors): 353 for dev, dev_errors in zip(devices, errors):
367 if dev_errors: 354 if dev_errors:
368 err_msg += ['%s errors:' % serial] 355 err_msg += ['%s errors:' % str(dev)]
369 err_msg += [' %s' % error for error in dev_errors] 356 err_msg += [' %s' % error for error in dev_errors]
370 357
371 if err_msg: 358 if err_msg:
372 bb_annotations.PrintWarning() 359 bb_annotations.PrintWarning()
373 for e in err_msg: 360 for e in err_msg:
374 logging.error(e) 361 logging.error(e)
375 from_address = 'buildbot@chromium.org' 362 from_address = 'buildbot@chromium.org'
376 to_addresses = ['chromium-android-device-alerts@google.com'] 363 to_addresses = ['chromium-android-device-alerts@google.com']
377 bot_name = os.environ.get('BUILDBOT_BUILDERNAME') 364 bot_name = os.environ.get('BUILDBOT_BUILDERNAME')
378 slave_name = os.environ.get('BUILDBOT_SLAVENAME') 365 slave_name = os.environ.get('BUILDBOT_SLAVENAME')
379 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) 366 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name)
380 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg)) 367 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg))
381 368
382 if options.device_status_dashboard: 369 if options.device_status_dashboard:
370 offline_devices = [
371 device_utils.DeviceUtils(a)
372 for a in adb_wrapper.AdbWrapper.Devices(is_ready=False)
373 if a.GetState() == 'offline']
374
383 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices', 375 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices',
384 [len(devices)], 'devices') 376 [len(devices)], 'devices')
385 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices', 377 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices',
386 [len(offline_devices)], 'devices', 378 [len(offline_devices)], 'devices',
387 'unimportant') 379 'unimportant')
388 for serial, battery in zip(devices, batteries): 380 for dev, battery in zip(devices, batteries):
389 perf_tests_results_helper.PrintPerfResult('DeviceBattery', serial, 381 perf_tests_results_helper.PrintPerfResult('DeviceBattery', str(dev),
390 [battery], '%', 382 [battery], '%',
391 'unimportant') 383 'unimportant')
392 384
393 if options.json_output: 385 if options.json_output:
394 with open(options.json_output, 'wb') as f: 386 with open(options.json_output, 'wb') as f:
395 f.write(json.dumps(json_data, indent=4)) 387 f.write(json.dumps(json_data, indent=4))
396 388
397 num_failed_devs = 0 389 num_failed_devs = 0
398 for device_ok, device in zip(devices_ok, devices): 390 for device_ok, device in zip(devices_ok, devices):
399 if not device_ok: 391 if not device_ok:
400 logging.warning('Blacklisting %s', str(device)) 392 logging.warning('Blacklisting %s', str(device))
401 device_blacklist.ExtendBlacklist([str(device)]) 393 device_blacklist.ExtendBlacklist([str(device)])
402 num_failed_devs += 1 394 num_failed_devs += 1
403 395
404 if num_failed_devs == len(devices): 396 if num_failed_devs == len(devices):
405 return 2 397 return 2
406 398
407 if not devices: 399 if not devices:
408 return 1 400 return 1
409 401
410 402
411 if __name__ == '__main__': 403 if __name__ == '__main__':
412 sys.exit(main()) 404 sys.exit(main())
OLDNEW
« no previous file with comments | « build/android/apkbuilder_action.gypi ('k') | build/android/buildbot/bb_device_steps.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698