3 # Copyright (c) 2013 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.
7 """Provisions Android devices with settings required for bots.
10 ./provision_devices.py [-d <device serial number>]
21 from pylib
import android_commands
22 from pylib
import constants
23 from pylib
import device_settings
24 from pylib
.device
import device_blacklist
25 from pylib
.device
import device_errors
26 from pylib
.device
import device_utils
27 from pylib
.utils
import run_tests_helper
29 sys
.path
.append(os
.path
.join(constants
.DIR_SOURCE_ROOT
,
30 'third_party', 'android_testrunner'))
33 def KillHostHeartbeat():
34 ps
= subprocess
.Popen(['ps', 'aux'], stdout
=subprocess
.PIPE
)
35 stdout
, _
= ps
.communicate()
36 matches
= re
.findall('\\n.*host_heartbeat.*', stdout
)
38 logging
.info('An instance of host heart beart running... will kill')
39 pid
= re
.findall(r
'(\S+)', match
)[1]
40 subprocess
.call(['kill', str(pid
)])
43 def LaunchHostHeartbeat():
44 # Kill if existing host_heartbeat
46 # Launch a new host_heartbeat
47 logging
.info('Spawning host heartbeat...')
48 subprocess
.Popen([os
.path
.join(constants
.DIR_SOURCE_ROOT
,
49 'build/android/host_heartbeat.py')])
52 def PushAndLaunchAdbReboot(device
, target
):
53 """Pushes and launches the adb_reboot binary on the device.
56 device: The DeviceUtils instance for the device to which the adb_reboot
57 binary should be pushed.
58 target: The build target (example, Debug or Release) which helps in
59 locating the adb_reboot binary.
61 logging
.info('Will push and launch adb_reboot on %s' % str(device
))
62 # Kill if adb_reboot is already running.
64 # Don't try to kill adb_reboot more than once. We don't expect it to be
66 device
.KillAll('adb_reboot', blocking
=True, timeout
=2, retries
=0)
67 except device_errors
.CommandFailedError
:
68 # We can safely ignore the exception because we don't expect adb_reboot
72 logging
.info(' Pushing adb_reboot ...')
73 adb_reboot
= os
.path
.join(constants
.DIR_SOURCE_ROOT
,
74 'out/%s/adb_reboot' % target
)
75 device
.PushChangedFiles([(adb_reboot
, '/data/local/tmp/')])
77 logging
.info(' Launching adb_reboot ...')
78 device
.old_interface
.GetAndroidToolStatusAndOutput(
79 '/data/local/tmp/adb_reboot')
82 def _ConfigureLocalProperties(device
, is_perf
):
83 """Set standard readonly testing device properties prior to reboot."""
85 'persist.sys.usb.config=adb',
89 'ro.setupwizard.mode=DISABLED',
92 local_props
.append('%s=all' % android_commands
.JAVA_ASSERT_PROPERTY
)
93 local_props
.append('debug.checkjni=1')
96 constants
.DEVICE_LOCAL_PROPERTIES_PATH
,
97 '\n'.join(local_props
), as_root
=True)
98 # Android will not respect the local props file if it is world writable.
99 device
.RunShellCommand(
100 'chmod 644 %s' % constants
.DEVICE_LOCAL_PROPERTIES_PATH
,
102 except device_errors
.CommandFailedError
as e
:
103 logging
.warning(str(e
))
105 # LOCAL_PROPERTIES_PATH = '/data/local.prop'
108 def WipeDeviceData(device
):
109 """Wipes data from device, keeping only the adb_keys for authorization.
111 After wiping data on a device that has been authorized, adb can still
112 communicate with the device, but after reboot the device will need to be
113 re-authorized because the adb keys file is stored in /data/misc/adb/.
114 Thus, adb_keys file is rewritten so the device does not need to be
118 device: the device to wipe
120 device_authorized
= device
.FileExists(constants
.ADB_KEYS_FILE
)
121 if device_authorized
:
122 adb_keys
= device
.RunShellCommand('cat %s' % constants
.ADB_KEYS_FILE
,
124 device
.RunShellCommand('wipe data', as_root
=True)
125 if device_authorized
:
126 path_list
= constants
.ADB_KEYS_FILE
.split('/')
127 dir_path
= '/'.join(path_list
[:len(path_list
)-1])
128 device
.RunShellCommand('mkdir -p %s' % dir_path
, as_root
=True)
129 device
.RunShellCommand('restorecon %s' % dir_path
, as_root
=True)
130 device
.RunShellCommand('echo %s > %s' %
131 (adb_keys
[0], constants
.ADB_KEYS_FILE
), as_root
=True)
132 for adb_key
in adb_keys
[1:]:
133 device
.RunShellCommand(
134 'echo %s >> %s' % (adb_key
, constants
.ADB_KEYS_FILE
), as_root
=True)
135 device
.RunShellCommand('restorecon %s' % constants
.ADB_KEYS_FILE
,
139 def WipeDeviceIfPossible(device
):
142 WipeDeviceData(device
)
143 # TODO(jbudorick): Tune the timeout per OS version.
144 device
.Reboot(True, timeout
=600, retries
=0)
145 except (errors
.DeviceUnresponsiveError
, device_errors
.CommandFailedError
):
149 def ProvisionDevice(device
, options
, is_perf
):
151 if not options
.skip_wipe
:
152 WipeDeviceIfPossible(device
)
155 except device_errors
.CommandFailedError
as e
:
156 logging
.warning(str(e
))
157 _ConfigureLocalProperties(device
, is_perf
)
158 device_settings
.ConfigureContentSettings(
159 device
, device_settings
.DETERMINISTIC_DEVICE_SETTINGS
)
160 if options
.disable_location
:
161 device_settings
.ConfigureContentSettings(
162 device
, device_settings
.DISABLE_LOCATION_SETTINGS
)
164 device_settings
.ConfigureContentSettings(
165 device
, device_settings
.ENABLE_LOCATION_SETTINGS
)
166 device_settings
.SetLockScreenSettings(device
)
168 # TODO(tonyg): We eventually want network on. However, currently radios
169 # can cause perfbots to drain faster than they charge.
170 device_settings
.ConfigureContentSettings(
171 device
, device_settings
.NETWORK_DISABLED_SETTINGS
)
172 # Some perf bots run benchmarks with USB charging disabled which leads
173 # to gradual draining of the battery. We must wait for a full charge
174 # before starting a run in order to keep the devices online.
176 battery_info
= device
.old_interface
.GetBatteryInfo()
177 except Exception as e
:
179 logging
.error('Unable to obtain battery info for %s, %s',
182 while int(battery_info
.get('level', 100)) < 95:
183 if not device
.old_interface
.IsDeviceCharging():
184 if device
.old_interface
.CanControlUsbCharging():
185 device
.old_interface
.EnableUsbCharging()
187 logging
.error('Device is not charging')
189 logging
.info('Waiting for device to charge. Current level=%s',
190 battery_info
.get('level', 0))
192 battery_info
= device
.old_interface
.GetBatteryInfo()
193 if not options
.skip_wipe
:
194 # TODO(jbudorick): Tune the timeout per OS version.
195 device
.Reboot(True, timeout
=600, retries
=0)
196 device
.RunShellCommand('date -s %s' % time
.strftime('%Y%m%d.%H%M%S',
199 props
= device
.RunShellCommand('getprop')
201 logging
.info(' %s' % prop
)
202 if options
.auto_reconnect
:
203 PushAndLaunchAdbReboot(device
, options
.target
)
204 except (errors
.WaitForResponseTimedOutError
,
205 device_errors
.CommandTimeoutError
):
206 logging
.info('Timed out waiting for device %s. Adding to blacklist.',
208 # Device black list is reset by bb_device_status_check.py per build.
209 device_blacklist
.ExtendBlacklist([str(device
)])
210 except device_errors
.CommandFailedError
:
211 logging
.exception('Failed to provision device %s. Adding to blacklist.',
213 device_blacklist
.ExtendBlacklist([str(device
)])
216 def ProvisionDevices(options
):
217 is_perf
= 'perf' in os
.environ
.get('BUILDBOT_BUILDERNAME', '').lower()
218 if options
.device
is not None:
219 devices
= [options
.device
]
221 devices
= android_commands
.GetAttachedDevices()
223 parallel_devices
= device_utils
.DeviceUtils
.parallel(devices
)
224 parallel_devices
.pMap(ProvisionDevice
, options
, is_perf
)
225 if options
.auto_reconnect
:
226 LaunchHostHeartbeat()
227 blacklist
= device_blacklist
.ReadBlacklist()
228 if all(d
in blacklist
for d
in devices
):
229 raise device_errors
.NoDevicesError
234 custom_handler
= logging
.StreamHandler(sys
.stdout
)
235 custom_handler
.setFormatter(run_tests_helper
.CustomFormatter())
236 logging
.getLogger().addHandler(custom_handler
)
237 logging
.getLogger().setLevel(logging
.INFO
)
239 parser
= optparse
.OptionParser()
240 parser
.add_option('--skip-wipe', action
='store_true', default
=False,
241 help="Don't wipe device data during provisioning.")
242 parser
.add_option('--disable-location', action
='store_true', default
=False,
243 help="Disallow Google location services on devices.")
244 parser
.add_option('-d', '--device',
245 help='The serial number of the device to be provisioned')
246 parser
.add_option('-t', '--target', default
='Debug', help='The build target')
248 '-r', '--auto-reconnect', action
='store_true',
249 help='Push binary which will reboot the device on adb disconnections.')
250 options
, args
= parser
.parse_args(argv
[1:])
251 constants
.SetBuildType(options
.target
)
254 print >> sys
.stderr
, 'Unused args %s' % args
257 return ProvisionDevices(options
)
260 if __name__
== '__main__':
261 sys
.exit(main(sys
.argv
))