1#!/usr/bin/env python3.4 2# 3# Copyright 2016 - The Android Open Source Project 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 17import base64 18import concurrent.futures 19import datetime 20import json 21import functools 22import os 23import random 24import re 25import signal 26import string 27import subprocess 28import time 29import traceback 30 31# File name length is limited to 255 chars on some OS, so we need to make sure 32# the file names we output fits within the limit. 33MAX_FILENAME_LEN = 255 34 35 36class NexusModelNames: 37 # TODO(angli): This will be fixed later by angli. 38 ONE = 'sprout' 39 N5 = 'hammerhead' 40 N5v2 = 'bullhead' 41 N6 = 'shamu' 42 N6v2 = 'angler' 43 44 45class DozeModeStatus: 46 ACTIVE = "ACTIVE" 47 IDLE = "IDLE" 48 49 50ascii_letters_and_digits = string.ascii_letters + string.digits 51valid_filename_chars = "-_." + ascii_letters_and_digits 52 53models = ("sprout", "occam", "hammerhead", "bullhead", "razor", "razorg", 54 "shamu", "angler", "volantis", "volantisg", "mantaray", "fugu", 55 "ryu") 56 57manufacture_name_to_model = { 58 "flo": "razor", 59 "flo_lte": "razorg", 60 "flounder": "volantis", 61 "flounder_lte": "volantisg", 62 "dragon": "ryu" 63} 64 65GMT_to_olson = { 66 "GMT-9": "America/Anchorage", 67 "GMT-8": "US/Pacific", 68 "GMT-7": "US/Mountain", 69 "GMT-6": "US/Central", 70 "GMT-5": "US/Eastern", 71 "GMT-4": "America/Barbados", 72 "GMT-3": "America/Buenos_Aires", 73 "GMT-2": "Atlantic/South_Georgia", 74 "GMT-1": "Atlantic/Azores", 75 "GMT+0": "Africa/Casablanca", 76 "GMT+1": "Europe/Amsterdam", 77 "GMT+2": "Europe/Athens", 78 "GMT+3": "Europe/Moscow", 79 "GMT+4": "Asia/Baku", 80 "GMT+5": "Asia/Oral", 81 "GMT+6": "Asia/Almaty", 82 "GMT+7": "Asia/Bangkok", 83 "GMT+8": "Asia/Hong_Kong", 84 "GMT+9": "Asia/Tokyo", 85 "GMT+10": "Pacific/Guam", 86 "GMT+11": "Pacific/Noumea", 87 "GMT+12": "Pacific/Fiji", 88 "GMT+13": "Pacific/Tongatapu", 89 "GMT-11": "Pacific/Midway", 90 "GMT-10": "Pacific/Honolulu" 91} 92 93 94def abs_path(path): 95 """Resolve the '.' and '~' in a path to get the absolute path. 96 97 Args: 98 path: The path to expand. 99 100 Returns: 101 The absolute path of the input path. 102 """ 103 return os.path.abspath(os.path.expanduser(path)) 104 105 106def create_dir(path): 107 """Creates a directory if it does not exist already. 108 109 Args: 110 path: The path of the directory to create. 111 """ 112 full_path = abs_path(path) 113 if not os.path.exists(full_path): 114 os.makedirs(full_path) 115 116 117def get_current_epoch_time(): 118 """Current epoch time in milliseconds. 119 120 Returns: 121 An integer representing the current epoch time in milliseconds. 122 """ 123 return int(round(time.time() * 1000)) 124 125 126def get_current_human_time(): 127 """Returns the current time in human readable format. 128 129 Returns: 130 The current time stamp in Month-Day-Year Hour:Min:Sec format. 131 """ 132 return time.strftime("%m-%d-%Y %H:%M:%S ") 133 134 135def epoch_to_human_time(epoch_time): 136 """Converts an epoch timestamp to human readable time. 137 138 This essentially converts an output of get_current_epoch_time to an output 139 of get_current_human_time 140 141 Args: 142 epoch_time: An integer representing an epoch timestamp in milliseconds. 143 144 Returns: 145 A time string representing the input time. 146 None if input param is invalid. 147 """ 148 if isinstance(epoch_time, int): 149 try: 150 d = datetime.datetime.fromtimestamp(epoch_time / 1000) 151 return d.strftime("%m-%d-%Y %H:%M:%S ") 152 except ValueError: 153 return None 154 155 156def get_timezone_olson_id(): 157 """Return the Olson ID of the local (non-DST) timezone. 158 159 Returns: 160 A string representing one of the Olson IDs of the local (non-DST) 161 timezone. 162 """ 163 tzoffset = int(time.timezone / 3600) 164 gmt = None 165 if tzoffset <= 0: 166 gmt = "GMT+{}".format(-tzoffset) 167 else: 168 gmt = "GMT-{}".format(tzoffset) 169 return GMT_to_olson[gmt] 170 171 172def find_files(paths, file_predicate): 173 """Locate files whose names and extensions match the given predicate in 174 the specified directories. 175 176 Args: 177 paths: A list of directory paths where to find the files. 178 file_predicate: A function that returns True if the file name and 179 extension are desired. 180 181 Returns: 182 A list of files that match the predicate. 183 """ 184 file_list = [] 185 for path in paths: 186 p = abs_path(path) 187 for dirPath, subdirList, fileList in os.walk(p): 188 for fname in fileList: 189 name, ext = os.path.splitext(fname) 190 if file_predicate(name, ext): 191 file_list.append((dirPath, name, ext)) 192 return file_list 193 194 195def load_config(file_full_path): 196 """Loads a JSON config file. 197 198 Returns: 199 A JSON object. 200 """ 201 with open(file_full_path, 'r') as f: 202 conf = json.load(f) 203 return conf 204 205 206def load_file_to_base64_str(f_path): 207 """Loads the content of a file into a base64 string. 208 209 Args: 210 f_path: full path to the file including the file name. 211 212 Returns: 213 A base64 string representing the content of the file in utf-8 encoding. 214 """ 215 path = abs_path(f_path) 216 with open(path, 'rb') as f: 217 f_bytes = f.read() 218 base64_str = base64.b64encode(f_bytes).decode("utf-8") 219 return base64_str 220 221 222def find_field(item_list, cond, comparator, target_field): 223 """Finds the value of a field in a dict object that satisfies certain 224 conditions. 225 226 Args: 227 item_list: A list of dict objects. 228 cond: A param that defines the condition. 229 comparator: A function that checks if an dict satisfies the condition. 230 target_field: Name of the field whose value to be returned if an item 231 satisfies the condition. 232 233 Returns: 234 Target value or None if no item satisfies the condition. 235 """ 236 for item in item_list: 237 if comparator(item, cond) and target_field in item: 238 return item[target_field] 239 return None 240 241 242def rand_ascii_str(length): 243 """Generates a random string of specified length, composed of ascii letters 244 and digits. 245 246 Args: 247 length: The number of characters in the string. 248 249 Returns: 250 The random string generated. 251 """ 252 letters = [random.choice(ascii_letters_and_digits) for i in range(length)] 253 return ''.join(letters) 254 255 256# Thead/Process related functions. 257def concurrent_exec(func, param_list): 258 """Executes a function with different parameters pseudo-concurrently. 259 260 This is basically a map function. Each element (should be an iterable) in 261 the param_list is unpacked and passed into the function. Due to Python's 262 GIL, there's no true concurrency. This is suited for IO-bound tasks. 263 264 Args: 265 func: The function that parforms a task. 266 param_list: A list of iterables, each being a set of params to be 267 passed into the function. 268 269 Returns: 270 A list of return values from each function execution. If an execution 271 caused an exception, the exception object will be the corresponding 272 result. 273 """ 274 with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor: 275 # Start the load operations and mark each future with its params 276 future_to_params = {executor.submit(func, *p): p for p in param_list} 277 return_vals = [] 278 for future in concurrent.futures.as_completed(future_to_params): 279 params = future_to_params[future] 280 try: 281 return_vals.append(future.result()) 282 except Exception as exc: 283 print("{} generated an exception: {}".format( 284 params, traceback.format_exc())) 285 return_vals.append(exc) 286 return return_vals 287 288 289def exe_cmd(*cmds): 290 """Executes commands in a new shell. 291 292 Args: 293 cmds: A sequence of commands and arguments. 294 295 Returns: 296 The output of the command run. 297 298 Raises: 299 OSError is raised if an error occurred during the command execution. 300 """ 301 cmd = ' '.join(cmds) 302 proc = subprocess.Popen(cmd, 303 stdout=subprocess.PIPE, 304 stderr=subprocess.PIPE, 305 shell=True) 306 (out, err) = proc.communicate() 307 if not err: 308 return out 309 raise OSError(err) 310 311 312def require_sl4a(android_devices): 313 """Makes sure sl4a connection is established on the given AndroidDevice 314 objects. 315 316 Args: 317 android_devices: A list of AndroidDevice objects. 318 319 Raises: 320 AssertionError is raised if any given android device does not have SL4A 321 connection established. 322 """ 323 for ad in android_devices: 324 msg = "SL4A connection not established properly on %s." % ad.serial 325 assert ad.droid, msg 326 327 328def start_standing_subprocess(cmd): 329 """Starts a non-blocking subprocess that is going to continue running after 330 this function returns. 331 332 A subprocess group is actually started by setting sid, so we can kill all 333 the processes spun out from the subprocess when stopping it. This is 334 necessary in case users pass in pipe commands. 335 336 Args: 337 cmd: Command to start the subprocess with. 338 339 Returns: 340 The subprocess that got started. 341 """ 342 p = subprocess.Popen(cmd, 343 stdout=subprocess.PIPE, 344 stderr=subprocess.PIPE, 345 shell=True, 346 preexec_fn=os.setpgrp) 347 return p 348 349 350def stop_standing_subprocess(p, kill_signal=signal.SIGTERM): 351 """Stops a subprocess started by start_standing_subprocess. 352 353 Catches and ignores the PermissionError which only happens on Macs. 354 355 Args: 356 p: Subprocess to terminate. 357 """ 358 try: 359 os.killpg(p.pid, kill_signal) 360 except PermissionError: 361 pass 362 363 364def wait_for_standing_subprocess(p, timeout=None): 365 """Waits for a subprocess started by start_standing_subprocess to finish 366 or times out. 367 368 Propagates the exception raised by the subprocess.wait(.) function. 369 The subprocess.TimeoutExpired exception is raised if the process timed-out 370 rather then terminating. 371 372 If no exception is raised: the subprocess terminated on its own. No need 373 to call stop_standing_subprocess() to kill it. 374 375 If an exception is raised: the subprocess is still alive - it did not 376 terminate. Either call stop_standing_subprocess() to kill it, or call 377 wait_for_standing_subprocess() to keep waiting for it to terminate on its 378 own. 379 380 Args: 381 p: Subprocess to wait for. 382 timeout: An integer number of seconds to wait before timing out. 383 """ 384 p.wait(timeout) 385 386 387def sync_device_time(ad): 388 """Sync the time of an android device with the current system time. 389 390 Both epoch time and the timezone will be synced. 391 392 Args: 393 ad: The android device to sync time on. 394 """ 395 droid = ad.droid 396 droid.setTimeZone(get_timezone_olson_id()) 397 droid.setTime(get_current_epoch_time()) 398 399 400# Timeout decorator block 401class TimeoutError(Exception): 402 """Exception for timeout decorator related errors. 403 """ 404 pass 405 406 407def _timeout_handler(signum, frame): 408 """Handler function used by signal to terminate a timed out function. 409 """ 410 raise TimeoutError() 411 412 413def timeout(sec): 414 """A decorator used to add time out check to a function. 415 416 Args: 417 sec: Number of seconds to wait before the function times out. 418 No timeout if set to 0 419 420 Returns: 421 What the decorated function returns. 422 423 Raises: 424 TimeoutError is raised when time out happens. 425 """ 426 427 def decorator(func): 428 @functools.wraps(func) 429 def wrapper(*args, **kwargs): 430 if sec: 431 signal.signal(signal.SIGALRM, _timeout_handler) 432 signal.alarm(sec) 433 try: 434 return func(*args, **kwargs) 435 except TimeoutError: 436 raise TimeoutError(("Function {} timed out after {} " 437 "seconds.").format(func.__name__, sec)) 438 finally: 439 signal.alarm(0) 440 441 return wrapper 442 443 return decorator 444 445 446def trim_model_name(model): 447 """Trim any prefix and postfix and return the android designation of the 448 model name. 449 450 e.g. "m_shamu" will be trimmed to "shamu". 451 452 Args: 453 model: model name to be trimmed. 454 455 Returns 456 Trimmed model name if one of the known model names is found. 457 None otherwise. 458 """ 459 # Directly look up first. 460 if model in models: 461 return model 462 if model in manufacture_name_to_model: 463 return manufacture_name_to_model[model] 464 # If not found, try trimming off prefix/postfix and look up again. 465 tokens = re.split("_|-", model) 466 for t in tokens: 467 if t in models: 468 return t 469 if t in manufacture_name_to_model: 470 return manufacture_name_to_model[t] 471 return None 472 473 474def force_airplane_mode(ad, new_state, timeout_value=60): 475 """Force the device to set airplane mode on or off by adb shell command. 476 477 Args: 478 ad: android device object. 479 new_state: Turn on airplane mode if True. 480 Turn off airplane mode if False. 481 timeout_value: max wait time for 'adb wait-for-device' 482 483 Returns: 484 True if success. 485 False if timeout. 486 """ 487 # Using timeout decorator. 488 # Wait for device with timeout. If after <timeout_value> seconds, adb 489 # is still waiting for device, throw TimeoutError exception. 490 @timeout(timeout_value) 491 def wait_for_device_with_timeout(ad): 492 ad.adb.wait_for_device() 493 494 try: 495 wait_for_device_with_timeout(ad) 496 ad.adb.shell("settings put global airplane_mode_on {}".format( 497 1 if new_state else 0)) 498 except TimeoutError: 499 # adb wait for device timeout 500 return False 501 return True 502 503 504def enable_doze(ad): 505 """Force the device into doze mode. 506 507 Args: 508 ad: android device object. 509 510 Returns: 511 True if device is in doze mode. 512 False otherwise. 513 """ 514 ad.adb.shell("dumpsys battery unplug") 515 ad.adb.shell("dumpsys deviceidle enable") 516 ad.adb.shell("dumpsys deviceidle force-idle") 517 ad.droid.goToSleepNow() 518 time.sleep(5) 519 adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep").decode( 520 'utf-8') 521 if not adb_shell_result.startswith(DozeModeStatus.IDLE): 522 info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) 523 print(info) 524 return False 525 return True 526 527 528def disable_doze(ad): 529 """Force the device not in doze mode. 530 531 Args: 532 ad: android device object. 533 534 Returns: 535 True if device is not in doze mode. 536 False otherwise. 537 """ 538 ad.adb.shell("dumpsys deviceidle disable") 539 ad.adb.shell("dumpsys battery reset") 540 adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep").decode( 541 'utf-8') 542 if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): 543 info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) 544 print(info) 545 return False 546 return True 547 548 549def enable_doze_light(ad): 550 """Force the device into doze light mode. 551 552 Args: 553 ad: android device object. 554 555 Returns: 556 True if device is in doze light mode. 557 False otherwise. 558 """ 559 ad.adb.shell("dumpsys battery unplug") 560 ad.droid.goToSleepNow() 561 time.sleep(5) 562 ad.adb.shell("cmd deviceidle enable light") 563 ad.adb.shell("cmd deviceidle step light") 564 adb_shell_result = ad.adb.shell("dumpsys deviceidle get light").decode( 565 'utf-8') 566 if not adb_shell_result.startswith(DozeModeStatus.IDLE): 567 info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) 568 print(info) 569 return False 570 return True 571 572 573def disable_doze_light(ad): 574 """Force the device not in doze light mode. 575 576 Args: 577 ad: android device object. 578 579 Returns: 580 True if device is not in doze light mode. 581 False otherwise. 582 """ 583 ad.adb.shell("dumpsys battery reset") 584 ad.adb.shell("cmd deviceidle disable light") 585 adb_shell_result = ad.adb.shell("dumpsys deviceidle get light").decode( 586 'utf-8') 587 if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): 588 info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) 589 print(info) 590 return False 591 return True 592 593 594def set_ambient_display(ad, new_state): 595 """Set "Ambient Display" in Settings->Display 596 597 Args: 598 ad: android device object. 599 new_state: new state for "Ambient Display". True or False. 600 """ 601 ad.adb.shell("settings put secure doze_enabled {}".format(1 if new_state 602 else 0)) 603 604 605def set_adaptive_brightness(ad, new_state): 606 """Set "Adaptive Brightness" in Settings->Display 607 608 Args: 609 ad: android device object. 610 new_state: new state for "Adaptive Brightness". True or False. 611 """ 612 ad.adb.shell("settings put system screen_brightness_mode {}".format( 613 1 if new_state else 0)) 614 615 616def set_auto_rotate(ad, new_state): 617 """Set "Auto-rotate" in QuickSetting 618 619 Args: 620 ad: android device object. 621 new_state: new state for "Auto-rotate". True or False. 622 """ 623 ad.adb.shell("settings put system accelerometer_rotation {}".format( 624 1 if new_state else 0)) 625 626 627def set_location_service(ad, new_state): 628 """Set Location service on/off in Settings->Location 629 630 Args: 631 ad: android device object. 632 new_state: new state for "Location service". 633 If new_state is False, turn off location service. 634 If new_state if True, set location service to "High accuracy". 635 """ 636 if new_state: 637 ad.adb.shell("settings put secure location_providers_allowed +gps") 638 ad.adb.shell("settings put secure location_providers_allowed +network") 639 else: 640 ad.adb.shell("settings put secure location_providers_allowed -gps") 641 ad.adb.shell("settings put secure location_providers_allowed -network") 642 643 644def set_mobile_data_always_on(ad, new_state): 645 """Set Mobile_Data_Always_On feature bit 646 647 Args: 648 ad: android device object. 649 new_state: new state for "mobile_data_always_on" 650 if new_state is False, set mobile_data_always_on disabled. 651 if new_state if True, set mobile_data_always_on enabled. 652 """ 653 ad.adb.shell("settings put global mobile_data_always_on {}".format( 654 1 if new_state else 0)) 655