tturney | 1bdf77d | 2015-12-28 17:46:13 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3.4 |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 2 | # |
tturney | 1bdf77d | 2015-12-28 17:46:13 -0800 | [diff] [blame] | 3 | # Copyright 2016 - The Android Open Source Project |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | import base64 |
| 18 | import concurrent.futures |
| 19 | import datetime |
| 20 | import json |
| 21 | import functools |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 22 | import logging |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 23 | import os |
| 24 | import random |
| 25 | import re |
| 26 | import signal |
| 27 | import string |
| 28 | import subprocess |
| 29 | import time |
| 30 | import traceback |
markdr | c302825 | 2017-08-22 19:07:20 -0700 | [diff] [blame] | 31 | import zipfile |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 32 | |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 33 | from acts.controllers import adb |
markdr | 6607bf1 | 2018-01-02 14:45:38 -0800 | [diff] [blame] | 34 | from acts import tracelogger |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 35 | |
Ang Li | 5e49480 | 2016-02-26 18:10:26 -0800 | [diff] [blame] | 36 | # File name length is limited to 255 chars on some OS, so we need to make sure |
| 37 | # the file names we output fits within the limit. |
markdr | 6607bf1 | 2018-01-02 14:45:38 -0800 | [diff] [blame] | 38 | from acts.libs.proc import job |
| 39 | |
Ang Li | 5e49480 | 2016-02-26 18:10:26 -0800 | [diff] [blame] | 40 | MAX_FILENAME_LEN = 255 |
| 41 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 42 | |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 43 | class ActsUtilsError(Exception): |
| 44 | """Generic error raised for exceptions in ACTS utils.""" |
| 45 | |
| 46 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 47 | class NexusModelNames: |
| 48 | # TODO(angli): This will be fixed later by angli. |
| 49 | ONE = 'sprout' |
| 50 | N5 = 'hammerhead' |
| 51 | N5v2 = 'bullhead' |
| 52 | N6 = 'shamu' |
| 53 | N6v2 = 'angler' |
Nathan Harold | b05c8e6 | 2016-05-10 12:02:54 -0700 | [diff] [blame] | 54 | N6v3 = 'marlin' |
| 55 | N5v3 = 'sailfish' |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 56 | |
Joe Brennan | 8bc4d1b | 2017-01-23 14:43:07 -0800 | [diff] [blame] | 57 | |
Yang Liu | d2b9ccb | 2016-05-03 16:04:02 -0700 | [diff] [blame] | 58 | class DozeModeStatus: |
| 59 | ACTIVE = "ACTIVE" |
| 60 | IDLE = "IDLE" |
| 61 | |
| 62 | |
Joe Brennan | 43d1511 | 2017-01-26 13:25:26 -0800 | [diff] [blame] | 63 | class CapablityPerDevice: |
| 64 | energy_info_models = [ |
| 65 | "shamu", "volantis", "volantisg", "angler", "bullhead", "ryu", |
| 66 | "marlin", "sailfish" |
| 67 | ] |
| 68 | tdls_models = [ |
| 69 | "shamu", "hammerhead", "angler", "bullhead", "marlin", "sailfish" |
| 70 | ] |
| 71 | |
| 72 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 73 | ascii_letters_and_digits = string.ascii_letters + string.digits |
| 74 | valid_filename_chars = "-_." + ascii_letters_and_digits |
| 75 | |
| 76 | models = ("sprout", "occam", "hammerhead", "bullhead", "razor", "razorg", |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 77 | "shamu", "angler", "volantis", "volantisg", "mantaray", "fugu", |
Nathan Harold | b05c8e6 | 2016-05-10 12:02:54 -0700 | [diff] [blame] | 78 | "ryu", "marlin", "sailfish") |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 79 | |
| 80 | manufacture_name_to_model = { |
| 81 | "flo": "razor", |
| 82 | "flo_lte": "razorg", |
| 83 | "flounder": "volantis", |
| 84 | "flounder_lte": "volantisg", |
| 85 | "dragon": "ryu" |
| 86 | } |
| 87 | |
| 88 | GMT_to_olson = { |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 89 | "GMT-9": "America/Anchorage", |
| 90 | "GMT-8": "US/Pacific", |
| 91 | "GMT-7": "US/Mountain", |
| 92 | "GMT-6": "US/Central", |
| 93 | "GMT-5": "US/Eastern", |
| 94 | "GMT-4": "America/Barbados", |
| 95 | "GMT-3": "America/Buenos_Aires", |
| 96 | "GMT-2": "Atlantic/South_Georgia", |
| 97 | "GMT-1": "Atlantic/Azores", |
| 98 | "GMT+0": "Africa/Casablanca", |
| 99 | "GMT+1": "Europe/Amsterdam", |
| 100 | "GMT+2": "Europe/Athens", |
| 101 | "GMT+3": "Europe/Moscow", |
| 102 | "GMT+4": "Asia/Baku", |
| 103 | "GMT+5": "Asia/Oral", |
| 104 | "GMT+6": "Asia/Almaty", |
| 105 | "GMT+7": "Asia/Bangkok", |
| 106 | "GMT+8": "Asia/Hong_Kong", |
| 107 | "GMT+9": "Asia/Tokyo", |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 108 | "GMT+10": "Pacific/Guam", |
| 109 | "GMT+11": "Pacific/Noumea", |
| 110 | "GMT+12": "Pacific/Fiji", |
| 111 | "GMT+13": "Pacific/Tongatapu", |
| 112 | "GMT-11": "Pacific/Midway", |
| 113 | "GMT-10": "Pacific/Honolulu" |
| 114 | } |
| 115 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 116 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 117 | def abs_path(path): |
| 118 | """Resolve the '.' and '~' in a path to get the absolute path. |
| 119 | |
| 120 | Args: |
| 121 | path: The path to expand. |
| 122 | |
| 123 | Returns: |
| 124 | The absolute path of the input path. |
| 125 | """ |
| 126 | return os.path.abspath(os.path.expanduser(path)) |
| 127 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 128 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 129 | def create_dir(path): |
| 130 | """Creates a directory if it does not exist already. |
| 131 | |
| 132 | Args: |
| 133 | path: The path of the directory to create. |
| 134 | """ |
| 135 | full_path = abs_path(path) |
| 136 | if not os.path.exists(full_path): |
| 137 | os.makedirs(full_path) |
| 138 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 139 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 140 | def get_current_epoch_time(): |
| 141 | """Current epoch time in milliseconds. |
| 142 | |
| 143 | Returns: |
| 144 | An integer representing the current epoch time in milliseconds. |
| 145 | """ |
| 146 | return int(round(time.time() * 1000)) |
| 147 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 148 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 149 | def get_current_human_time(): |
| 150 | """Returns the current time in human readable format. |
| 151 | |
| 152 | Returns: |
| 153 | The current time stamp in Month-Day-Year Hour:Min:Sec format. |
| 154 | """ |
| 155 | return time.strftime("%m-%d-%Y %H:%M:%S ") |
| 156 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 157 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 158 | def epoch_to_human_time(epoch_time): |
| 159 | """Converts an epoch timestamp to human readable time. |
| 160 | |
| 161 | This essentially converts an output of get_current_epoch_time to an output |
| 162 | of get_current_human_time |
| 163 | |
| 164 | Args: |
| 165 | epoch_time: An integer representing an epoch timestamp in milliseconds. |
| 166 | |
| 167 | Returns: |
| 168 | A time string representing the input time. |
| 169 | None if input param is invalid. |
| 170 | """ |
| 171 | if isinstance(epoch_time, int): |
| 172 | try: |
| 173 | d = datetime.datetime.fromtimestamp(epoch_time / 1000) |
| 174 | return d.strftime("%m-%d-%Y %H:%M:%S ") |
| 175 | except ValueError: |
| 176 | return None |
| 177 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 178 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 179 | def get_timezone_olson_id(): |
| 180 | """Return the Olson ID of the local (non-DST) timezone. |
| 181 | |
| 182 | Returns: |
| 183 | A string representing one of the Olson IDs of the local (non-DST) |
| 184 | timezone. |
| 185 | """ |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 186 | tzoffset = int(time.timezone / 3600) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 187 | gmt = None |
| 188 | if tzoffset <= 0: |
| 189 | gmt = "GMT+{}".format(-tzoffset) |
| 190 | else: |
| 191 | gmt = "GMT-{}".format(tzoffset) |
| 192 | return GMT_to_olson[gmt] |
| 193 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 194 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 195 | def find_files(paths, file_predicate): |
| 196 | """Locate files whose names and extensions match the given predicate in |
| 197 | the specified directories. |
| 198 | |
| 199 | Args: |
| 200 | paths: A list of directory paths where to find the files. |
| 201 | file_predicate: A function that returns True if the file name and |
| 202 | extension are desired. |
| 203 | |
| 204 | Returns: |
| 205 | A list of files that match the predicate. |
| 206 | """ |
| 207 | file_list = [] |
Benny Peake | aa238e0 | 2016-12-14 16:21:42 -0800 | [diff] [blame] | 208 | if not isinstance(paths, list): |
| 209 | paths = [paths] |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 210 | for path in paths: |
| 211 | p = abs_path(path) |
| 212 | for dirPath, subdirList, fileList in os.walk(p): |
| 213 | for fname in fileList: |
| 214 | name, ext = os.path.splitext(fname) |
| 215 | if file_predicate(name, ext): |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 216 | file_list.append((dirPath, name, ext)) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 217 | return file_list |
| 218 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 219 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 220 | def load_config(file_full_path): |
| 221 | """Loads a JSON config file. |
| 222 | |
| 223 | Returns: |
| 224 | A JSON object. |
| 225 | """ |
| 226 | with open(file_full_path, 'r') as f: |
Betty Zhou | 1e3d074 | 2017-07-17 11:17:18 -0700 | [diff] [blame] | 227 | try: |
| 228 | conf = json.load(f) |
| 229 | except Exception as e: |
| 230 | logging.error("Exception error to load %s: %s", f, e) |
| 231 | raise |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 232 | return conf |
| 233 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 234 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 235 | def load_file_to_base64_str(f_path): |
| 236 | """Loads the content of a file into a base64 string. |
| 237 | |
| 238 | Args: |
| 239 | f_path: full path to the file including the file name. |
| 240 | |
| 241 | Returns: |
| 242 | A base64 string representing the content of the file in utf-8 encoding. |
| 243 | """ |
| 244 | path = abs_path(f_path) |
| 245 | with open(path, 'rb') as f: |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 246 | f_bytes = f.read() |
| 247 | base64_str = base64.b64encode(f_bytes).decode("utf-8") |
| 248 | return base64_str |
| 249 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 250 | |
Jack He | d0b3bf7 | 2017-01-24 22:36:09 -0800 | [diff] [blame] | 251 | def dump_string_to_file(content, file_path, mode='w'): |
| 252 | """ Dump content of a string to |
| 253 | |
| 254 | Args: |
| 255 | content: content to be dumped to file |
| 256 | file_path: full path to the file including the file name. |
| 257 | mode: file open mode, 'w' (truncating file) by default |
| 258 | :return: |
| 259 | """ |
| 260 | full_path = abs_path(file_path) |
| 261 | with open(full_path, mode) as f: |
| 262 | f.write(content) |
| 263 | |
| 264 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 265 | def find_field(item_list, cond, comparator, target_field): |
| 266 | """Finds the value of a field in a dict object that satisfies certain |
| 267 | conditions. |
| 268 | |
| 269 | Args: |
| 270 | item_list: A list of dict objects. |
| 271 | cond: A param that defines the condition. |
| 272 | comparator: A function that checks if an dict satisfies the condition. |
| 273 | target_field: Name of the field whose value to be returned if an item |
| 274 | satisfies the condition. |
| 275 | |
| 276 | Returns: |
| 277 | Target value or None if no item satisfies the condition. |
| 278 | """ |
| 279 | for item in item_list: |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 280 | if comparator(item, cond) and target_field in item: |
| 281 | return item[target_field] |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 282 | return None |
| 283 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 284 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 285 | def rand_ascii_str(length): |
| 286 | """Generates a random string of specified length, composed of ascii letters |
| 287 | and digits. |
| 288 | |
| 289 | Args: |
| 290 | length: The number of characters in the string. |
| 291 | |
| 292 | Returns: |
| 293 | The random string generated. |
| 294 | """ |
| 295 | letters = [random.choice(ascii_letters_and_digits) for i in range(length)] |
| 296 | return ''.join(letters) |
| 297 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 298 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 299 | # Thead/Process related functions. |
| 300 | def concurrent_exec(func, param_list): |
| 301 | """Executes a function with different parameters pseudo-concurrently. |
| 302 | |
| 303 | This is basically a map function. Each element (should be an iterable) in |
| 304 | the param_list is unpacked and passed into the function. Due to Python's |
| 305 | GIL, there's no true concurrency. This is suited for IO-bound tasks. |
| 306 | |
| 307 | Args: |
| 308 | func: The function that parforms a task. |
| 309 | param_list: A list of iterables, each being a set of params to be |
| 310 | passed into the function. |
Ang Li | 8d3e18e | 2015-12-04 17:41:38 -0800 | [diff] [blame] | 311 | |
| 312 | Returns: |
| 313 | A list of return values from each function execution. If an execution |
| 314 | caused an exception, the exception object will be the corresponding |
| 315 | result. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 316 | """ |
| 317 | with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor: |
| 318 | # Start the load operations and mark each future with its params |
| 319 | future_to_params = {executor.submit(func, *p): p for p in param_list} |
Ang Li | 8d3e18e | 2015-12-04 17:41:38 -0800 | [diff] [blame] | 320 | return_vals = [] |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 321 | for future in concurrent.futures.as_completed(future_to_params): |
| 322 | params = future_to_params[future] |
| 323 | try: |
Ang Li | 8d3e18e | 2015-12-04 17:41:38 -0800 | [diff] [blame] | 324 | return_vals.append(future.result()) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 325 | except Exception as exc: |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 326 | print("{} generated an exception: {}".format( |
| 327 | params, traceback.format_exc())) |
Ang Li | 8d3e18e | 2015-12-04 17:41:38 -0800 | [diff] [blame] | 328 | return_vals.append(exc) |
| 329 | return return_vals |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 330 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 331 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 332 | def exe_cmd(*cmds): |
| 333 | """Executes commands in a new shell. |
| 334 | |
| 335 | Args: |
| 336 | cmds: A sequence of commands and arguments. |
| 337 | |
| 338 | Returns: |
| 339 | The output of the command run. |
| 340 | |
| 341 | Raises: |
Ang Li | d18a3a5 | 2016-04-06 10:36:46 -0700 | [diff] [blame] | 342 | OSError is raised if an error occurred during the command execution. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 343 | """ |
| 344 | cmd = ' '.join(cmds) |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 345 | proc = subprocess.Popen( |
| 346 | cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 347 | (out, err) = proc.communicate() |
| 348 | if not err: |
| 349 | return out |
Ang Li | d18a3a5 | 2016-04-06 10:36:46 -0700 | [diff] [blame] | 350 | raise OSError(err) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 351 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 352 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 353 | def require_sl4a(android_devices): |
| 354 | """Makes sure sl4a connection is established on the given AndroidDevice |
| 355 | objects. |
| 356 | |
| 357 | Args: |
| 358 | android_devices: A list of AndroidDevice objects. |
| 359 | |
| 360 | Raises: |
| 361 | AssertionError is raised if any given android device does not have SL4A |
| 362 | connection established. |
| 363 | """ |
| 364 | for ad in android_devices: |
| 365 | msg = "SL4A connection not established properly on %s." % ad.serial |
| 366 | assert ad.droid, msg |
| 367 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 368 | |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 369 | def _assert_subprocess_running(proc): |
| 370 | """Checks if a subprocess has terminated on its own. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 371 | |
| 372 | Args: |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 373 | proc: A subprocess returned by subprocess.Popen. |
| 374 | |
| 375 | Raises: |
| 376 | ActsUtilsError is raised if the subprocess has stopped. |
| 377 | """ |
| 378 | ret = proc.poll() |
| 379 | if ret is not None: |
| 380 | out, err = proc.communicate() |
| 381 | raise ActsUtilsError("Process %d has terminated. ret: %d, stderr: %s," |
| 382 | " stdout: %s" % (proc.pid, ret, err, out)) |
| 383 | |
| 384 | |
| 385 | def start_standing_subprocess(cmd, check_health_delay=0): |
| 386 | """Starts a long-running subprocess. |
| 387 | |
| 388 | This is not a blocking call and the subprocess started by it should be |
| 389 | explicitly terminated with stop_standing_subprocess. |
| 390 | |
| 391 | For short-running commands, you should use exe_cmd, which blocks. |
| 392 | |
| 393 | You can specify a health check after the subprocess is started to make sure |
| 394 | it did not stop prematurely. |
| 395 | |
| 396 | Args: |
| 397 | cmd: string, the command to start the subprocess with. |
| 398 | check_health_delay: float, the number of seconds to wait after the |
| 399 | subprocess starts to check its health. Default is 0, |
| 400 | which means no check. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 401 | |
| 402 | Returns: |
| 403 | The subprocess that got started. |
| 404 | """ |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 405 | proc = subprocess.Popen( |
| 406 | cmd, |
| 407 | stdout=subprocess.PIPE, |
| 408 | stderr=subprocess.PIPE, |
| 409 | shell=True, |
| 410 | preexec_fn=os.setpgrp) |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 411 | logging.debug("Start standing subprocess with cmd: %s", cmd) |
| 412 | if check_health_delay > 0: |
| 413 | time.sleep(check_health_delay) |
| 414 | _assert_subprocess_running(proc) |
| 415 | return proc |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 416 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 417 | |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 418 | def stop_standing_subprocess(proc, kill_signal=signal.SIGTERM): |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 419 | """Stops a subprocess started by start_standing_subprocess. |
| 420 | |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 421 | Before killing the process, we check if the process is running, if it has |
| 422 | terminated, ActsUtilsError is raised. |
| 423 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 424 | Catches and ignores the PermissionError which only happens on Macs. |
| 425 | |
| 426 | Args: |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 427 | proc: Subprocess to terminate. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 428 | """ |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 429 | pid = proc.pid |
| 430 | logging.debug("Stop standing subprocess %d", pid) |
| 431 | _assert_subprocess_running(proc) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 432 | try: |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 433 | os.killpg(pid, kill_signal) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 434 | except PermissionError: |
| 435 | pass |
| 436 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 437 | |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 438 | def wait_for_standing_subprocess(proc, timeout=None): |
Etan Cohen | 568faa6 | 2016-02-26 11:03:59 -0800 | [diff] [blame] | 439 | """Waits for a subprocess started by start_standing_subprocess to finish |
| 440 | or times out. |
| 441 | |
| 442 | Propagates the exception raised by the subprocess.wait(.) function. |
| 443 | The subprocess.TimeoutExpired exception is raised if the process timed-out |
| 444 | rather then terminating. |
| 445 | |
| 446 | If no exception is raised: the subprocess terminated on its own. No need |
| 447 | to call stop_standing_subprocess() to kill it. |
| 448 | |
| 449 | If an exception is raised: the subprocess is still alive - it did not |
| 450 | terminate. Either call stop_standing_subprocess() to kill it, or call |
| 451 | wait_for_standing_subprocess() to keep waiting for it to terminate on its |
| 452 | own. |
| 453 | |
| 454 | Args: |
| 455 | p: Subprocess to wait for. |
| 456 | timeout: An integer number of seconds to wait before timing out. |
| 457 | """ |
Ang Li | 60b071d | 2016-06-29 12:52:42 -0700 | [diff] [blame] | 458 | proc.wait(timeout) |
Etan Cohen | 568faa6 | 2016-02-26 11:03:59 -0800 | [diff] [blame] | 459 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 460 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 461 | def sync_device_time(ad): |
| 462 | """Sync the time of an android device with the current system time. |
| 463 | |
| 464 | Both epoch time and the timezone will be synced. |
| 465 | |
| 466 | Args: |
| 467 | ad: The android device to sync time on. |
| 468 | """ |
| 469 | droid = ad.droid |
| 470 | droid.setTimeZone(get_timezone_olson_id()) |
| 471 | droid.setTime(get_current_epoch_time()) |
| 472 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 473 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 474 | # Timeout decorator block |
| 475 | class TimeoutError(Exception): |
| 476 | """Exception for timeout decorator related errors. |
| 477 | """ |
| 478 | pass |
| 479 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 480 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 481 | def _timeout_handler(signum, frame): |
| 482 | """Handler function used by signal to terminate a timed out function. |
| 483 | """ |
| 484 | raise TimeoutError() |
| 485 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 486 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 487 | def timeout(sec): |
| 488 | """A decorator used to add time out check to a function. |
| 489 | |
Ang Li | 3082240 | 2016-06-17 16:38:35 -0700 | [diff] [blame] | 490 | This only works in main thread due to its dependency on signal module. |
| 491 | Do NOT use it if the decorated funtion does not run in the Main thread. |
| 492 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 493 | Args: |
| 494 | sec: Number of seconds to wait before the function times out. |
| 495 | No timeout if set to 0 |
| 496 | |
| 497 | Returns: |
| 498 | What the decorated function returns. |
| 499 | |
| 500 | Raises: |
| 501 | TimeoutError is raised when time out happens. |
| 502 | """ |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 503 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 504 | def decorator(func): |
| 505 | @functools.wraps(func) |
| 506 | def wrapper(*args, **kwargs): |
| 507 | if sec: |
| 508 | signal.signal(signal.SIGALRM, _timeout_handler) |
| 509 | signal.alarm(sec) |
| 510 | try: |
| 511 | return func(*args, **kwargs) |
| 512 | except TimeoutError: |
| 513 | raise TimeoutError(("Function {} timed out after {} " |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 514 | "seconds.").format(func.__name__, sec)) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 515 | finally: |
| 516 | signal.alarm(0) |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 517 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 518 | return wrapper |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 519 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 520 | return decorator |
| 521 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 522 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 523 | def trim_model_name(model): |
| 524 | """Trim any prefix and postfix and return the android designation of the |
| 525 | model name. |
| 526 | |
| 527 | e.g. "m_shamu" will be trimmed to "shamu". |
| 528 | |
| 529 | Args: |
| 530 | model: model name to be trimmed. |
| 531 | |
| 532 | Returns |
| 533 | Trimmed model name if one of the known model names is found. |
| 534 | None otherwise. |
| 535 | """ |
| 536 | # Directly look up first. |
| 537 | if model in models: |
| 538 | return model |
| 539 | if model in manufacture_name_to_model: |
| 540 | return manufacture_name_to_model[model] |
| 541 | # If not found, try trimming off prefix/postfix and look up again. |
| 542 | tokens = re.split("_|-", model) |
| 543 | for t in tokens: |
| 544 | if t in models: |
| 545 | return t |
| 546 | if t in manufacture_name_to_model: |
| 547 | return manufacture_name_to_model[t] |
| 548 | return None |
| 549 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 550 | |
Yang Liu | 29a70d5 | 2016-01-15 17:38:46 -0800 | [diff] [blame] | 551 | def force_airplane_mode(ad, new_state, timeout_value=60): |
| 552 | """Force the device to set airplane mode on or off by adb shell command. |
| 553 | |
| 554 | Args: |
| 555 | ad: android device object. |
| 556 | new_state: Turn on airplane mode if True. |
| 557 | Turn off airplane mode if False. |
| 558 | timeout_value: max wait time for 'adb wait-for-device' |
| 559 | |
| 560 | Returns: |
| 561 | True if success. |
| 562 | False if timeout. |
| 563 | """ |
| 564 | # Using timeout decorator. |
| 565 | # Wait for device with timeout. If after <timeout_value> seconds, adb |
| 566 | # is still waiting for device, throw TimeoutError exception. |
| 567 | @timeout(timeout_value) |
| 568 | def wait_for_device_with_timeout(ad): |
| 569 | ad.adb.wait_for_device() |
| 570 | |
| 571 | try: |
| 572 | wait_for_device_with_timeout(ad) |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 573 | ad.adb.shell("settings put global airplane_mode_on {}".format( |
| 574 | 1 if new_state else 0)) |
Bindu Mahadev | 102ed53 | 2017-02-16 16:56:11 -0800 | [diff] [blame] | 575 | ad.adb.shell("am broadcast -a android.intent.action.AIRPLANE_MODE") |
Yang Liu | 29a70d5 | 2016-01-15 17:38:46 -0800 | [diff] [blame] | 576 | except TimeoutError: |
| 577 | # adb wait for device timeout |
| 578 | return False |
| 579 | return True |
| 580 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 581 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 582 | def enable_doze(ad): |
| 583 | """Force the device into doze mode. |
| 584 | |
| 585 | Args: |
| 586 | ad: android device object. |
| 587 | |
| 588 | Returns: |
| 589 | True if device is in doze mode. |
| 590 | False otherwise. |
| 591 | """ |
| 592 | ad.adb.shell("dumpsys battery unplug") |
| 593 | ad.adb.shell("dumpsys deviceidle enable") |
Yang Liu | d2b9ccb | 2016-05-03 16:04:02 -0700 | [diff] [blame] | 594 | ad.adb.shell("dumpsys deviceidle force-idle") |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 595 | ad.droid.goToSleepNow() |
| 596 | time.sleep(5) |
Girish Moturu | 0559877 | 2017-05-11 11:10:45 +0530 | [diff] [blame] | 597 | adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep") |
Yang Liu | d2b9ccb | 2016-05-03 16:04:02 -0700 | [diff] [blame] | 598 | if not adb_shell_result.startswith(DozeModeStatus.IDLE): |
| 599 | info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 600 | print(info) |
| 601 | return False |
| 602 | return True |
| 603 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 604 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 605 | def disable_doze(ad): |
| 606 | """Force the device not in doze mode. |
| 607 | |
| 608 | Args: |
| 609 | ad: android device object. |
| 610 | |
| 611 | Returns: |
| 612 | True if device is not in doze mode. |
| 613 | False otherwise. |
| 614 | """ |
| 615 | ad.adb.shell("dumpsys deviceidle disable") |
| 616 | ad.adb.shell("dumpsys battery reset") |
Girish Moturu | 0559877 | 2017-05-11 11:10:45 +0530 | [diff] [blame] | 617 | adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep") |
Yang Liu | d2b9ccb | 2016-05-03 16:04:02 -0700 | [diff] [blame] | 618 | if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): |
| 619 | info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) |
| 620 | print(info) |
| 621 | return False |
| 622 | return True |
| 623 | |
| 624 | |
| 625 | def enable_doze_light(ad): |
| 626 | """Force the device into doze light mode. |
| 627 | |
| 628 | Args: |
| 629 | ad: android device object. |
| 630 | |
| 631 | Returns: |
| 632 | True if device is in doze light mode. |
| 633 | False otherwise. |
| 634 | """ |
| 635 | ad.adb.shell("dumpsys battery unplug") |
| 636 | ad.droid.goToSleepNow() |
| 637 | time.sleep(5) |
| 638 | ad.adb.shell("cmd deviceidle enable light") |
| 639 | ad.adb.shell("cmd deviceidle step light") |
Girish Moturu | 0559877 | 2017-05-11 11:10:45 +0530 | [diff] [blame] | 640 | adb_shell_result = ad.adb.shell("dumpsys deviceidle get light") |
Yang Liu | d2b9ccb | 2016-05-03 16:04:02 -0700 | [diff] [blame] | 641 | if not adb_shell_result.startswith(DozeModeStatus.IDLE): |
| 642 | info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) |
| 643 | print(info) |
| 644 | return False |
| 645 | return True |
| 646 | |
| 647 | |
| 648 | def disable_doze_light(ad): |
| 649 | """Force the device not in doze light mode. |
| 650 | |
| 651 | Args: |
| 652 | ad: android device object. |
| 653 | |
| 654 | Returns: |
| 655 | True if device is not in doze light mode. |
| 656 | False otherwise. |
| 657 | """ |
| 658 | ad.adb.shell("dumpsys battery reset") |
| 659 | ad.adb.shell("cmd deviceidle disable light") |
Girish Moturu | 0559877 | 2017-05-11 11:10:45 +0530 | [diff] [blame] | 660 | adb_shell_result = ad.adb.shell("dumpsys deviceidle get light") |
Yang Liu | d2b9ccb | 2016-05-03 16:04:02 -0700 | [diff] [blame] | 661 | if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): |
| 662 | info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 663 | print(info) |
| 664 | return False |
| 665 | return True |
| 666 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 667 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 668 | def set_ambient_display(ad, new_state): |
| 669 | """Set "Ambient Display" in Settings->Display |
| 670 | |
| 671 | Args: |
| 672 | ad: android device object. |
| 673 | new_state: new state for "Ambient Display". True or False. |
| 674 | """ |
Jack He | d0b3bf7 | 2017-01-24 22:36:09 -0800 | [diff] [blame] | 675 | ad.adb.shell( |
| 676 | "settings put secure doze_enabled {}".format(1 if new_state else 0)) |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 677 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 678 | |
| 679 | def set_adaptive_brightness(ad, new_state): |
| 680 | """Set "Adaptive Brightness" in Settings->Display |
| 681 | |
| 682 | Args: |
| 683 | ad: android device object. |
| 684 | new_state: new state for "Adaptive Brightness". True or False. |
| 685 | """ |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 686 | ad.adb.shell("settings put system screen_brightness_mode {}".format( |
| 687 | 1 if new_state else 0)) |
| 688 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 689 | |
| 690 | def set_auto_rotate(ad, new_state): |
| 691 | """Set "Auto-rotate" in QuickSetting |
| 692 | |
| 693 | Args: |
| 694 | ad: android device object. |
| 695 | new_state: new state for "Auto-rotate". True or False. |
| 696 | """ |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 697 | ad.adb.shell("settings put system accelerometer_rotation {}".format( |
| 698 | 1 if new_state else 0)) |
| 699 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 700 | |
| 701 | def set_location_service(ad, new_state): |
| 702 | """Set Location service on/off in Settings->Location |
| 703 | |
| 704 | Args: |
| 705 | ad: android device object. |
| 706 | new_state: new state for "Location service". |
| 707 | If new_state is False, turn off location service. |
| 708 | If new_state if True, set location service to "High accuracy". |
| 709 | """ |
Etan Cohen | 171c375 | 2018-05-25 07:54:27 -0700 | [diff] [blame] | 710 | ad.adb.shell("content insert --uri " |
Etan Cohen | 126f6fc | 2018-05-09 12:49:58 -0700 | [diff] [blame] | 711 | " content://com.google.settings/partner --bind " |
| 712 | "name:s:network_location_opt_in --bind value:s:1") |
Etan Cohen | 171c375 | 2018-05-25 07:54:27 -0700 | [diff] [blame] | 713 | ad.adb.shell("content insert --uri " |
Etan Cohen | 126f6fc | 2018-05-09 12:49:58 -0700 | [diff] [blame] | 714 | " content://com.google.settings/partner --bind " |
| 715 | "name:s:use_location_for_services --bind value:s:1") |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 716 | if new_state: |
| 717 | ad.adb.shell("settings put secure location_providers_allowed +gps") |
| 718 | ad.adb.shell("settings put secure location_providers_allowed +network") |
| 719 | else: |
| 720 | ad.adb.shell("settings put secure location_providers_allowed -gps") |
| 721 | ad.adb.shell("settings put secure location_providers_allowed -network") |
Yang Liu | 963caef | 2016-01-15 16:27:43 -0800 | [diff] [blame] | 722 | |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 723 | |
Yang Liu | 963caef | 2016-01-15 16:27:43 -0800 | [diff] [blame] | 724 | def set_mobile_data_always_on(ad, new_state): |
| 725 | """Set Mobile_Data_Always_On feature bit |
| 726 | |
| 727 | Args: |
| 728 | ad: android device object. |
| 729 | new_state: new state for "mobile_data_always_on" |
| 730 | if new_state is False, set mobile_data_always_on disabled. |
| 731 | if new_state if True, set mobile_data_always_on enabled. |
| 732 | """ |
Nathan Harold | 97d5f82 | 2016-03-21 14:00:00 -0700 | [diff] [blame] | 733 | ad.adb.shell("settings put global mobile_data_always_on {}".format( |
| 734 | 1 if new_state else 0)) |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 735 | |
| 736 | |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 737 | def bypass_setup_wizard(ad, bypass_wait_time=3): |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 738 | """Bypass the setup wizard on an input Android device |
| 739 | |
| 740 | Args: |
| 741 | ad: android device object. |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 742 | bypass_wait_time: Do not set this variable. Only modified for framework |
| 743 | tests. |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 744 | |
| 745 | Returns: |
| 746 | True if Andorid device successfully bypassed the setup wizard. |
| 747 | False if failed. |
| 748 | """ |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 749 | try: |
| 750 | ad.adb.shell("am start -n \"com.google.android.setupwizard/" |
| 751 | ".SetupWizardExitActivity\"") |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 752 | logging.debug("No error during default bypass call.") |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 753 | except adb.AdbError as adb_error: |
| 754 | if adb_error.stdout == "ADB_CMD_OUTPUT:0": |
| 755 | if adb_error.stderr and \ |
| 756 | not adb_error.stderr.startswith("Error type 3\n"): |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 757 | logging.error( |
| 758 | "ADB_CMD_OUTPUT:0, but error is %s " % adb_error.stderr) |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 759 | raise adb_error |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 760 | logging.debug("Bypass wizard call received harmless error 3: " |
| 761 | "No setup to bypass.") |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 762 | elif adb_error.stdout == "ADB_CMD_OUTPUT:255": |
| 763 | # Run it again as root. |
| 764 | ad.adb.root_adb() |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 765 | logging.debug("Need root access to bypass setup wizard.") |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 766 | try: |
| 767 | ad.adb.shell("am start -n \"com.google.android.setupwizard/" |
| 768 | ".SetupWizardExitActivity\"") |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 769 | logging.debug("No error during rooted bypass call.") |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 770 | except adb.AdbError as adb_error: |
| 771 | if adb_error.stdout == "ADB_CMD_OUTPUT:0": |
| 772 | if adb_error.stderr and \ |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 773 | not adb_error.stderr.startswith("Error type 3\n"): |
| 774 | logging.error("Rooted ADB_CMD_OUTPUT:0, but error is " |
| 775 | "%s " % adb_error.stderr) |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 776 | raise adb_error |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 777 | logging.debug( |
| 778 | "Rooted bypass wizard call received harmless " |
| 779 | "error 3: No setup to bypass.") |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 780 | |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 781 | # magical sleep to wait for the gservices override broadcast to complete |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 782 | time.sleep(bypass_wait_time) |
| 783 | |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 784 | provisioned_state = int( |
| 785 | ad.adb.shell("settings get global device_provisioned")) |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 786 | if provisioned_state != 1: |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 787 | logging.error("Failed to bypass setup wizard.") |
| 788 | return False |
markdr | 8db34ee | 2017-04-25 17:50:51 -0700 | [diff] [blame] | 789 | logging.debug("Setup wizard successfully bypassed.") |
tturney | bd2cedf | 2016-09-26 12:44:58 -0700 | [diff] [blame] | 790 | return True |
Jaineel | d0083ba | 2017-04-03 10:58:23 -0700 | [diff] [blame] | 791 | |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 792 | |
Jaineel | d0083ba | 2017-04-03 10:58:23 -0700 | [diff] [blame] | 793 | def parse_ping_ouput(ad, count, out, loss_tolerance=20): |
| 794 | """Ping Parsing util. |
| 795 | |
| 796 | Args: |
| 797 | ad: Android Device Object. |
| 798 | count: Number of ICMP packets sent |
| 799 | out: shell output text of ping operation |
| 800 | loss_tolerance: Threshold after which flag test as false |
| 801 | Returns: |
| 802 | False: if packet loss is more than loss_tolerance% |
| 803 | True: if all good |
| 804 | """ |
| 805 | out = out.split('\n')[-3:] |
| 806 | stats = out[1].split(',') |
| 807 | # For failure case, line of interest becomes the last line |
| 808 | if len(stats) != 4: |
| 809 | stats = out[2].split(',') |
| 810 | packet_loss = float(stats[2].split('%')[0]) |
| 811 | packet_xmit = int(stats[0].split()[0]) |
| 812 | packet_rcvd = int(stats[1].split()[0]) |
| 813 | min_packet_xmit_rcvd = (100 - loss_tolerance) * 0.01 |
| 814 | |
Sean Sutherland | 473661d | 2017-10-27 17:43:12 -0700 | [diff] [blame] | 815 | if (packet_loss >= loss_tolerance |
| 816 | or packet_xmit < count * min_packet_xmit_rcvd |
| 817 | or packet_rcvd < count * min_packet_xmit_rcvd): |
Jaineel | d0083ba | 2017-04-03 10:58:23 -0700 | [diff] [blame] | 818 | ad.log.error( |
| 819 | "More than %d %% packet loss seen, Expected Packet_count %d \ |
| 820 | Packet loss %.2f%% Packets_xmitted %d Packets_rcvd %d", |
| 821 | loss_tolerance, count, packet_loss, packet_xmit, packet_rcvd) |
| 822 | return False |
| 823 | ad.log.info("Pkt_count %d Pkt_loss %.2f%% Pkt_xmit %d Pkt_rcvd %d", count, |
| 824 | packet_loss, packet_xmit, packet_rcvd) |
| 825 | return True |
| 826 | |
| 827 | |
markdr | ddcd7f2 | 2017-04-20 11:19:12 -0700 | [diff] [blame] | 828 | def adb_shell_ping(ad, |
| 829 | count=120, |
| 830 | dest_ip="www.google.com", |
| 831 | timeout=200, |
Jaineel | d0083ba | 2017-04-03 10:58:23 -0700 | [diff] [blame] | 832 | loss_tolerance=20): |
| 833 | """Ping utility using adb shell. |
| 834 | |
| 835 | Args: |
| 836 | ad: Android Device Object. |
| 837 | count: Number of ICMP packets to send |
| 838 | dest_ip: hostname or IP address |
| 839 | default www.google.com |
| 840 | timeout: timeout for icmp pings to complete. |
| 841 | """ |
| 842 | ping_cmd = "ping -W 1" |
| 843 | if count: |
| 844 | ping_cmd += " -c %d" % count |
| 845 | if dest_ip: |
| 846 | ping_cmd += " %s | tee /data/ping.txt" % dest_ip |
| 847 | try: |
| 848 | ad.log.info("Starting ping test to %s using adb command %s", dest_ip, |
| 849 | ping_cmd) |
| 850 | out = ad.adb.shell(ping_cmd, timeout=timeout) |
| 851 | if not parse_ping_ouput(ad, count, out, loss_tolerance): |
| 852 | return False |
| 853 | return True |
| 854 | except Exception as e: |
Jaineel | e133ca3 | 2017-07-18 14:49:47 -0700 | [diff] [blame] | 855 | ad.log.warning("Ping Test to %s failed with exception %s", dest_ip, e) |
Jaineel | d0083ba | 2017-04-03 10:58:23 -0700 | [diff] [blame] | 856 | return False |
| 857 | finally: |
| 858 | ad.adb.shell("rm /data/ping.txt", timeout=10, ignore_status=True) |
markdr | c302825 | 2017-08-22 19:07:20 -0700 | [diff] [blame] | 859 | |
| 860 | |
| 861 | def unzip_maintain_permissions(zip_path, extract_location): |
| 862 | """Unzip a .zip file while maintaining permissions. |
| 863 | |
| 864 | Args: |
| 865 | zip_path: The path to the zipped file. |
| 866 | extract_location: the directory to extract to. |
| 867 | """ |
| 868 | with zipfile.ZipFile(zip_path, 'r') as zip_file: |
| 869 | for info in zip_file.infolist(): |
| 870 | _extract_file(zip_file, info, extract_location) |
| 871 | |
| 872 | |
| 873 | def _extract_file(zip_file, zip_info, extract_location): |
| 874 | """Extracts a single entry from a ZipFile while maintaining permissions. |
| 875 | |
| 876 | Args: |
| 877 | zip_file: A zipfile.ZipFile. |
| 878 | zip_info: A ZipInfo object from zip_file. |
| 879 | extract_location: The directory to extract to. |
| 880 | """ |
| 881 | out_path = zip_file.extract(zip_info.filename, path=extract_location) |
| 882 | perm = zip_info.external_attr >> 16 |
| 883 | os.chmod(out_path, perm) |
Sean Sutherland | 473661d | 2017-10-27 17:43:12 -0700 | [diff] [blame] | 884 | |
| 885 | |
| 886 | def get_directory_size(path): |
| 887 | """Computes the total size of the files in a directory, including subdirectories. |
| 888 | |
| 889 | Args: |
| 890 | path: The path of the directory. |
| 891 | Returns: |
| 892 | The size of the provided directory. |
| 893 | """ |
| 894 | total = 0 |
| 895 | for dirpath, dirnames, filenames in os.walk(path): |
| 896 | for filename in filenames: |
| 897 | total += os.path.getsize(os.path.join(dirpath, filename)) |
markdr | 6607bf1 | 2018-01-02 14:45:38 -0800 | [diff] [blame] | 898 | return total |
| 899 | |
| 900 | |
| 901 | def get_process_uptime(process): |
| 902 | """Returns the runtime in [[dd-]hh:]mm:ss, or '' if not running.""" |
| 903 | pid = job.run('pidof %s' % process, ignore_status=True).stdout |
| 904 | runtime = '' |
| 905 | if pid: |
| 906 | runtime = job.run('ps -o etime= -p "%s"' % pid).stdout |
| 907 | return runtime |
| 908 | |
| 909 | |
| 910 | def get_device_process_uptime(adb, process): |
| 911 | """Returns the uptime of a device process.""" |
| 912 | pid = adb.shell('pidof %s' % process, ignore_status=True) |
| 913 | runtime = '' |
| 914 | if pid: |
| 915 | runtime = adb.shell('ps -o etime= -p "%s"' % pid) |
| 916 | return runtime |