encrypted media: Persistent state is always enabled on Android.
[chromium-blink-merge.git] / build / android / provision_devices.py
blob5f00b31e8a817440b99d845b6745401c6d46c947
1 #!/usr/bin/env python
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.
9 Usage:
10 ./provision_devices.py [-d <device serial number>]
11 """
13 import argparse
14 import logging
15 import os
16 import posixpath
17 import re
18 import subprocess
19 import sys
20 import time
22 from pylib import android_commands
23 from pylib import constants
24 from pylib import device_settings
25 from pylib.device import device_blacklist
26 from pylib.device import device_errors
27 from pylib.device import device_utils
28 from pylib.utils import run_tests_helper
29 from pylib.utils import timeout_retry
31 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
32 'third_party', 'android_testrunner'))
33 import errors
36 class _DEFAULT_TIMEOUTS(object):
37 # L can take a while to reboot after a wipe.
38 LOLLIPOP = 600
39 PRE_LOLLIPOP = 180
41 HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP)
44 def KillHostHeartbeat():
45 ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
46 stdout, _ = ps.communicate()
47 matches = re.findall('\\n.*host_heartbeat.*', stdout)
48 for match in matches:
49 logging.info('An instance of host heart beart running... will kill')
50 pid = re.findall(r'(\S+)', match)[1]
51 subprocess.call(['kill', str(pid)])
54 def LaunchHostHeartbeat():
55 # Kill if existing host_heartbeat
56 KillHostHeartbeat()
57 # Launch a new host_heartbeat
58 logging.info('Spawning host heartbeat...')
59 subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
60 'build/android/host_heartbeat.py')])
63 def PushAndLaunchAdbReboot(device, target):
64 """Pushes and launches the adb_reboot binary on the device.
66 Arguments:
67 device: The DeviceUtils instance for the device to which the adb_reboot
68 binary should be pushed.
69 target: The build target (example, Debug or Release) which helps in
70 locating the adb_reboot binary.
71 """
72 logging.info('Will push and launch adb_reboot on %s' % str(device))
73 # Kill if adb_reboot is already running.
74 try:
75 # Don't try to kill adb_reboot more than once. We don't expect it to be
76 # running at all.
77 device.KillAll('adb_reboot', blocking=True, timeout=2, retries=0)
78 except device_errors.CommandFailedError:
79 # We can safely ignore the exception because we don't expect adb_reboot
80 # to be running.
81 pass
82 # Push adb_reboot
83 logging.info(' Pushing adb_reboot ...')
84 adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
85 'out/%s/adb_reboot' % target)
86 device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
87 # Launch adb_reboot
88 logging.info(' Launching adb_reboot ...')
89 device.RunShellCommand([
90 device.GetDevicePieWrapper(),
91 '/data/local/tmp/adb_reboot'])
94 def _ConfigureLocalProperties(device, java_debug=True):
95 """Set standard readonly testing device properties prior to reboot."""
96 local_props = [
97 'persist.sys.usb.config=adb',
98 'ro.monkey=1',
99 'ro.test_harness=1',
100 'ro.audio.silent=1',
101 'ro.setupwizard.mode=DISABLED',
103 if java_debug:
104 local_props.append('%s=all' % android_commands.JAVA_ASSERT_PROPERTY)
105 local_props.append('debug.checkjni=1')
106 try:
107 device.WriteFile(
108 constants.DEVICE_LOCAL_PROPERTIES_PATH,
109 '\n'.join(local_props), as_root=True)
110 # Android will not respect the local props file if it is world writable.
111 device.RunShellCommand(
112 ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
113 as_root=True)
114 except device_errors.CommandFailedError as e:
115 logging.warning(str(e))
117 # LOCAL_PROPERTIES_PATH = '/data/local.prop'
119 def WriteAdbKeysFile(device, adb_keys_string):
120 dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
121 device.RunShellCommand('mkdir -p %s' % dir_path, as_root=True)
122 device.RunShellCommand('restorecon %s' % dir_path, as_root=True)
123 device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
124 device.RunShellCommand('restorecon %s' % constants.ADB_KEYS_FILE,
125 as_root=True)
128 def WipeDeviceData(device, options):
129 """Wipes data from device, keeping only the adb_keys for authorization.
131 After wiping data on a device that has been authorized, adb can still
132 communicate with the device, but after reboot the device will need to be
133 re-authorized because the adb keys file is stored in /data/misc/adb/.
134 Thus, adb_keys file is rewritten so the device does not need to be
135 re-authorized.
137 Arguments:
138 device: the device to wipe
140 device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
141 if device_authorized:
142 adb_keys = device.ReadFile(constants.ADB_KEYS_FILE,
143 as_root=True).splitlines()
144 device.RunShellCommand('wipe data', as_root=True)
145 if device_authorized:
146 adb_keys_set = set(adb_keys)
147 for adb_key_file in options.adb_key_files or []:
148 try:
149 with open(adb_key_file, 'r') as f:
150 adb_public_keys = f.readlines()
151 adb_keys_set.update(adb_public_keys)
152 except IOError:
153 logging.warning('Unable to find adb keys file %s.' % adb_key_file)
154 WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
157 def WipeDeviceIfPossible(device, timeout, options):
158 try:
159 device.EnableRoot()
160 WipeDeviceData(device, options)
161 device.Reboot(True, timeout=timeout, retries=0)
162 except (errors.DeviceUnresponsiveError, device_errors.CommandFailedError):
163 pass
166 def ChargeDeviceToLevel(device, level):
167 def device_charged():
168 battery_level = device.GetBatteryInfo().get('level')
169 if battery_level is None:
170 logging.warning('Unable to find current battery level.')
171 battery_level = 100
172 else:
173 logging.info('current battery level: %d', battery_level)
174 battery_level = int(battery_level)
175 return battery_level >= level
177 timeout_retry.WaitFor(device_charged, wait_period=60)
180 def ProvisionDevice(device, options):
181 if options.reboot_timeout:
182 reboot_timeout = options.reboot_timeout
183 elif (device.build_version_sdk >=
184 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
185 reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
186 else:
187 reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
189 try:
190 if not options.skip_wipe:
191 WipeDeviceIfPossible(device, reboot_timeout, options)
192 try:
193 device.EnableRoot()
194 except device_errors.CommandFailedError as e:
195 logging.warning(str(e))
196 _ConfigureLocalProperties(device, options.enable_java_debug)
197 device_settings.ConfigureContentSettings(
198 device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
199 if options.disable_location:
200 device_settings.ConfigureContentSettings(
201 device, device_settings.DISABLE_LOCATION_SETTINGS)
202 else:
203 device_settings.ConfigureContentSettings(
204 device, device_settings.ENABLE_LOCATION_SETTINGS)
205 device_settings.SetLockScreenSettings(device)
206 if options.disable_network:
207 device_settings.ConfigureContentSettings(
208 device, device_settings.NETWORK_DISABLED_SETTINGS)
209 if options.min_battery_level is not None:
210 try:
211 device.SetCharging(True)
212 ChargeDeviceToLevel(device, options.min_battery_level)
213 except device_errors.CommandFailedError as e:
214 logging.exception('Unable to charge device to specified level.')
216 if not options.skip_wipe:
217 device.Reboot(True, timeout=reboot_timeout, retries=0)
218 device.RunShellCommand('date -s %s' % time.strftime('%Y%m%d.%H%M%S',
219 time.gmtime()),
220 as_root=True)
221 props = device.RunShellCommand('getprop')
222 for prop in props:
223 logging.info(' %s' % prop)
224 if options.auto_reconnect:
225 PushAndLaunchAdbReboot(device, options.target)
226 except (errors.WaitForResponseTimedOutError,
227 device_errors.CommandTimeoutError):
228 logging.info('Timed out waiting for device %s. Adding to blacklist.',
229 str(device))
230 # Device black list is reset by bb_device_status_check.py per build.
231 device_blacklist.ExtendBlacklist([str(device)])
232 except device_errors.CommandFailedError:
233 logging.exception('Failed to provision device %s. Adding to blacklist.',
234 str(device))
235 device_blacklist.ExtendBlacklist([str(device)])
238 def ProvisionDevices(options):
239 if options.device is not None:
240 devices = [options.device]
241 else:
242 devices = android_commands.GetAttachedDevices()
244 parallel_devices = device_utils.DeviceUtils.parallel(devices)
245 parallel_devices.pMap(ProvisionDevice, options)
246 if options.auto_reconnect:
247 LaunchHostHeartbeat()
248 blacklist = device_blacklist.ReadBlacklist()
249 if all(d in blacklist for d in devices):
250 raise device_errors.NoDevicesError
251 return 0
254 def main():
255 custom_handler = logging.StreamHandler(sys.stdout)
256 custom_handler.setFormatter(run_tests_helper.CustomFormatter())
257 logging.getLogger().addHandler(custom_handler)
258 logging.getLogger().setLevel(logging.INFO)
260 # Recommended options on perf bots:
261 # --disable-network
262 # TODO(tonyg): We eventually want network on. However, currently radios
263 # can cause perfbots to drain faster than they charge.
264 # --min-battery-level 95
265 # Some perf bots run benchmarks with USB charging disabled which leads
266 # to gradual draining of the battery. We must wait for a full charge
267 # before starting a run in order to keep the devices online.
269 parser = argparse.ArgumentParser(
270 description='Provision Android devices with settings required for bots.')
271 parser.add_argument('-d', '--device', metavar='SERIAL',
272 help='the serial number of the device to be provisioned'
273 ' (the default is to provision all devices attached)')
274 parser.add_argument('--skip-wipe', action='store_true', default=False,
275 help="don't wipe device data during provisioning")
276 parser.add_argument('--reboot-timeout', metavar='SECS', type=int,
277 help='when wiping the device, max number of seconds to'
278 ' wait after each reboot '
279 '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
280 parser.add_argument('--min-battery-level', type=int, metavar='NUM',
281 help='wait for the device to reach this minimum battery'
282 ' level before trying to continue')
283 parser.add_argument('--disable-location', action='store_true',
284 help='disable Google location services on devices')
285 parser.add_argument('--disable-network', action='store_true',
286 help='disable network access on devices')
287 parser.add_argument('--disable-java-debug', action='store_false',
288 dest='enable_java_debug', default=True,
289 help='disable Java property asserts and JNI checking')
290 parser.add_argument('-t', '--target', default='Debug',
291 help='the build target (default: %(default)s)')
292 parser.add_argument('-r', '--auto-reconnect', action='store_true',
293 help='push binary which will reboot the device on adb'
294 ' disconnections')
295 parser.add_argument('--adb-key-files', type=str, nargs='+',
296 help='list of adb keys to push to device')
297 args = parser.parse_args()
298 constants.SetBuildType(args.target)
300 return ProvisionDevices(args)
303 if __name__ == '__main__':
304 sys.exit(main())