1#!/usr/bin/env python3 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 copy 20import datetime 21import functools 22import ipaddress 23import json 24import logging 25import os 26import random 27import re 28import signal 29import string 30import socket 31import subprocess 32import time 33import threading 34import traceback 35import zipfile 36from concurrent.futures import ThreadPoolExecutor 37 38from acts import signals 39from acts.controllers import adb 40from acts.libs.proc import job 41 42# File name length is limited to 255 chars on some OS, so we need to make sure 43# the file names we output fits within the limit. 44MAX_FILENAME_LEN = 255 45 46 47class ActsUtilsError(Exception): 48 """Generic error raised for exceptions in ACTS utils.""" 49 50 51class NexusModelNames: 52 # TODO(angli): This will be fixed later by angli. 53 ONE = 'sprout' 54 N5 = 'hammerhead' 55 N5v2 = 'bullhead' 56 N6 = 'shamu' 57 N6v2 = 'angler' 58 N6v3 = 'marlin' 59 N5v3 = 'sailfish' 60 61 62class DozeModeStatus: 63 ACTIVE = "ACTIVE" 64 IDLE = "IDLE" 65 66 67ascii_letters_and_digits = string.ascii_letters + string.digits 68valid_filename_chars = "-_." + ascii_letters_and_digits 69 70models = ("sprout", "occam", "hammerhead", "bullhead", "razor", "razorg", 71 "shamu", "angler", "volantis", "volantisg", "mantaray", "fugu", 72 "ryu", "marlin", "sailfish") 73 74manufacture_name_to_model = { 75 "flo": "razor", 76 "flo_lte": "razorg", 77 "flounder": "volantis", 78 "flounder_lte": "volantisg", 79 "dragon": "ryu" 80} 81 82GMT_to_olson = { 83 "GMT-9": "America/Anchorage", 84 "GMT-8": "US/Pacific", 85 "GMT-7": "US/Mountain", 86 "GMT-6": "US/Central", 87 "GMT-5": "US/Eastern", 88 "GMT-4": "America/Barbados", 89 "GMT-3": "America/Buenos_Aires", 90 "GMT-2": "Atlantic/South_Georgia", 91 "GMT-1": "Atlantic/Azores", 92 "GMT+0": "Africa/Casablanca", 93 "GMT+1": "Europe/Amsterdam", 94 "GMT+2": "Europe/Athens", 95 "GMT+3": "Europe/Moscow", 96 "GMT+4": "Asia/Baku", 97 "GMT+5": "Asia/Oral", 98 "GMT+6": "Asia/Almaty", 99 "GMT+7": "Asia/Bangkok", 100 "GMT+8": "Asia/Hong_Kong", 101 "GMT+9": "Asia/Tokyo", 102 "GMT+10": "Pacific/Guam", 103 "GMT+11": "Pacific/Noumea", 104 "GMT+12": "Pacific/Fiji", 105 "GMT+13": "Pacific/Tongatapu", 106 "GMT-11": "Pacific/Midway", 107 "GMT-10": "Pacific/Honolulu" 108} 109 110 111def abs_path(path): 112 """Resolve the '.' and '~' in a path to get the absolute path. 113 114 Args: 115 path: The path to expand. 116 117 Returns: 118 The absolute path of the input path. 119 """ 120 return os.path.abspath(os.path.expanduser(path)) 121 122 123def get_current_epoch_time(): 124 """Current epoch time in milliseconds. 125 126 Returns: 127 An integer representing the current epoch time in milliseconds. 128 """ 129 return int(round(time.time() * 1000)) 130 131 132def get_current_human_time(): 133 """Returns the current time in human readable format. 134 135 Returns: 136 The current time stamp in Month-Day-Year Hour:Min:Sec format. 137 """ 138 return time.strftime("%m-%d-%Y %H:%M:%S ") 139 140 141def epoch_to_human_time(epoch_time): 142 """Converts an epoch timestamp to human readable time. 143 144 This essentially converts an output of get_current_epoch_time to an output 145 of get_current_human_time 146 147 Args: 148 epoch_time: An integer representing an epoch timestamp in milliseconds. 149 150 Returns: 151 A time string representing the input time. 152 None if input param is invalid. 153 """ 154 if isinstance(epoch_time, int): 155 try: 156 d = datetime.datetime.fromtimestamp(epoch_time / 1000) 157 return d.strftime("%m-%d-%Y %H:%M:%S ") 158 except ValueError: 159 return None 160 161 162def get_timezone_olson_id(): 163 """Return the Olson ID of the local (non-DST) timezone. 164 165 Returns: 166 A string representing one of the Olson IDs of the local (non-DST) 167 timezone. 168 """ 169 tzoffset = int(time.timezone / 3600) 170 gmt = None 171 if tzoffset <= 0: 172 gmt = "GMT+{}".format(-tzoffset) 173 else: 174 gmt = "GMT-{}".format(tzoffset) 175 return GMT_to_olson[gmt] 176 177 178def get_next_device(test_bed_controllers, used_devices): 179 """Gets the next device in a list of testbed controllers 180 181 Args: 182 test_bed_controllers: A list of testbed controllers of a particular 183 type, for example a list ACTS Android devices. 184 used_devices: A list of devices that have been used. This can be a 185 mix of devices, for example a fuchsia device and an Android device. 186 Returns: 187 The next device in the test_bed_controllers list or None if there are 188 no items that are not in the used devices list. 189 """ 190 if test_bed_controllers: 191 device_list = test_bed_controllers 192 else: 193 raise ValueError('test_bed_controllers is empty.') 194 for used_device in used_devices: 195 if used_device in device_list: 196 device_list.remove(used_device) 197 if device_list: 198 return device_list[0] 199 else: 200 return None 201 202 203def find_files(paths, file_predicate): 204 """Locate files whose names and extensions match the given predicate in 205 the specified directories. 206 207 Args: 208 paths: A list of directory paths where to find the files. 209 file_predicate: A function that returns True if the file name and 210 extension are desired. 211 212 Returns: 213 A list of files that match the predicate. 214 """ 215 file_list = [] 216 if not isinstance(paths, list): 217 paths = [paths] 218 for path in paths: 219 p = abs_path(path) 220 for dirPath, subdirList, fileList in os.walk(p): 221 for fname in fileList: 222 name, ext = os.path.splitext(fname) 223 if file_predicate(name, ext): 224 file_list.append((dirPath, name, ext)) 225 return file_list 226 227 228def load_config(file_full_path, log_errors=True): 229 """Loads a JSON config file. 230 231 Returns: 232 A JSON object. 233 """ 234 with open(file_full_path, 'r') as f: 235 try: 236 return json.load(f) 237 except Exception as e: 238 if log_errors: 239 logging.error("Exception error to load %s: %s", f, e) 240 raise 241 242 243def load_file_to_base64_str(f_path): 244 """Loads the content of a file into a base64 string. 245 246 Args: 247 f_path: full path to the file including the file name. 248 249 Returns: 250 A base64 string representing the content of the file in utf-8 encoding. 251 """ 252 path = abs_path(f_path) 253 with open(path, 'rb') as f: 254 f_bytes = f.read() 255 base64_str = base64.b64encode(f_bytes).decode("utf-8") 256 return base64_str 257 258 259def dump_string_to_file(content, file_path, mode='w'): 260 """ Dump content of a string to 261 262 Args: 263 content: content to be dumped to file 264 file_path: full path to the file including the file name. 265 mode: file open mode, 'w' (truncating file) by default 266 :return: 267 """ 268 full_path = abs_path(file_path) 269 with open(full_path, mode) as f: 270 f.write(content) 271 272 273def list_of_dict_to_dict_of_dict(list_of_dicts, dict_key): 274 """Transforms a list of dicts to a dict of dicts. 275 276 For instance: 277 >>> list_of_dict_to_dict_of_dict([{'a': '1', 'b':'2'}, 278 >>> {'a': '3', 'b':'4'}], 279 >>> 'b') 280 281 returns: 282 283 >>> {'2': {'a': '1', 'b':'2'}, 284 >>> '4': {'a': '3', 'b':'4'}} 285 286 Args: 287 list_of_dicts: A list of dictionaries. 288 dict_key: The key in the inner dict to be used as the key for the 289 outer dict. 290 Returns: 291 A dict of dicts. 292 """ 293 return {d[dict_key]: d for d in list_of_dicts} 294 295 296def dict_purge_key_if_value_is_none(dictionary): 297 """Removes all pairs with value None from dictionary.""" 298 for k, v in dict(dictionary).items(): 299 if v is None: 300 del dictionary[k] 301 return dictionary 302 303 304def find_field(item_list, cond, comparator, target_field): 305 """Finds the value of a field in a dict object that satisfies certain 306 conditions. 307 308 Args: 309 item_list: A list of dict objects. 310 cond: A param that defines the condition. 311 comparator: A function that checks if an dict satisfies the condition. 312 target_field: Name of the field whose value to be returned if an item 313 satisfies the condition. 314 315 Returns: 316 Target value or None if no item satisfies the condition. 317 """ 318 for item in item_list: 319 if comparator(item, cond) and target_field in item: 320 return item[target_field] 321 return None 322 323 324def rand_ascii_str(length): 325 """Generates a random string of specified length, composed of ascii letters 326 and digits. 327 328 Args: 329 length: The number of characters in the string. 330 331 Returns: 332 The random string generated. 333 """ 334 letters = [random.choice(ascii_letters_and_digits) for i in range(length)] 335 return ''.join(letters) 336 337 338def rand_hex_str(length): 339 """Generates a random string of specified length, composed of hex digits 340 341 Args: 342 length: The number of characters in the string. 343 344 Returns: 345 The random string generated. 346 """ 347 letters = [random.choice(string.hexdigits) for i in range(length)] 348 return ''.join(letters) 349 350 351# Thead/Process related functions. 352def concurrent_exec(func, param_list): 353 """Executes a function with different parameters pseudo-concurrently. 354 355 This is basically a map function. Each element (should be an iterable) in 356 the param_list is unpacked and passed into the function. Due to Python's 357 GIL, there's no true concurrency. This is suited for IO-bound tasks. 358 359 Args: 360 func: The function that parforms a task. 361 param_list: A list of iterables, each being a set of params to be 362 passed into the function. 363 364 Returns: 365 A list of return values from each function execution. If an execution 366 caused an exception, the exception object will be the corresponding 367 result. 368 """ 369 with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor: 370 # Start the load operations and mark each future with its params 371 future_to_params = {executor.submit(func, *p): p for p in param_list} 372 return_vals = [] 373 for future in concurrent.futures.as_completed(future_to_params): 374 params = future_to_params[future] 375 try: 376 return_vals.append(future.result()) 377 except Exception as exc: 378 print("{} generated an exception: {}".format( 379 params, traceback.format_exc())) 380 return_vals.append(exc) 381 return return_vals 382 383 384def exe_cmd(*cmds): 385 """Executes commands in a new shell. 386 387 Args: 388 cmds: A sequence of commands and arguments. 389 390 Returns: 391 The output of the command run. 392 393 Raises: 394 OSError is raised if an error occurred during the command execution. 395 """ 396 cmd = ' '.join(cmds) 397 proc = subprocess.Popen(cmd, 398 stdout=subprocess.PIPE, 399 stderr=subprocess.PIPE, 400 shell=True) 401 (out, err) = proc.communicate() 402 if not err: 403 return out 404 raise OSError(err) 405 406 407def require_sl4a(android_devices): 408 """Makes sure sl4a connection is established on the given AndroidDevice 409 objects. 410 411 Args: 412 android_devices: A list of AndroidDevice objects. 413 414 Raises: 415 AssertionError is raised if any given android device does not have SL4A 416 connection established. 417 """ 418 for ad in android_devices: 419 msg = "SL4A connection not established properly on %s." % ad.serial 420 assert ad.droid, msg 421 422 423def _assert_subprocess_running(proc): 424 """Checks if a subprocess has terminated on its own. 425 426 Args: 427 proc: A subprocess returned by subprocess.Popen. 428 429 Raises: 430 ActsUtilsError is raised if the subprocess has stopped. 431 """ 432 ret = proc.poll() 433 if ret is not None: 434 out, err = proc.communicate() 435 raise ActsUtilsError("Process %d has terminated. ret: %d, stderr: %s," 436 " stdout: %s" % (proc.pid, ret, err, out)) 437 438 439def start_standing_subprocess(cmd, check_health_delay=0, shell=True): 440 """Starts a long-running subprocess. 441 442 This is not a blocking call and the subprocess started by it should be 443 explicitly terminated with stop_standing_subprocess. 444 445 For short-running commands, you should use exe_cmd, which blocks. 446 447 You can specify a health check after the subprocess is started to make sure 448 it did not stop prematurely. 449 450 Args: 451 cmd: string, the command to start the subprocess with. 452 check_health_delay: float, the number of seconds to wait after the 453 subprocess starts to check its health. Default is 0, 454 which means no check. 455 456 Returns: 457 The subprocess that got started. 458 """ 459 proc = subprocess.Popen(cmd, 460 stdout=subprocess.PIPE, 461 stderr=subprocess.PIPE, 462 shell=shell, 463 preexec_fn=os.setpgrp) 464 logging.debug("Start standing subprocess with cmd: %s", cmd) 465 if check_health_delay > 0: 466 time.sleep(check_health_delay) 467 _assert_subprocess_running(proc) 468 return proc 469 470 471def stop_standing_subprocess(proc, kill_signal=signal.SIGTERM): 472 """Stops a subprocess started by start_standing_subprocess. 473 474 Before killing the process, we check if the process is running, if it has 475 terminated, ActsUtilsError is raised. 476 477 Catches and ignores the PermissionError which only happens on Macs. 478 479 Args: 480 proc: Subprocess to terminate. 481 """ 482 pid = proc.pid 483 logging.debug("Stop standing subprocess %d", pid) 484 _assert_subprocess_running(proc) 485 try: 486 os.killpg(pid, kill_signal) 487 except PermissionError: 488 pass 489 490 491def wait_for_standing_subprocess(proc, timeout=None): 492 """Waits for a subprocess started by start_standing_subprocess to finish 493 or times out. 494 495 Propagates the exception raised by the subprocess.wait(.) function. 496 The subprocess.TimeoutExpired exception is raised if the process timed-out 497 rather then terminating. 498 499 If no exception is raised: the subprocess terminated on its own. No need 500 to call stop_standing_subprocess() to kill it. 501 502 If an exception is raised: the subprocess is still alive - it did not 503 terminate. Either call stop_standing_subprocess() to kill it, or call 504 wait_for_standing_subprocess() to keep waiting for it to terminate on its 505 own. 506 507 Args: 508 p: Subprocess to wait for. 509 timeout: An integer number of seconds to wait before timing out. 510 """ 511 proc.wait(timeout) 512 513 514def sync_device_time(ad): 515 """Sync the time of an android device with the current system time. 516 517 Both epoch time and the timezone will be synced. 518 519 Args: 520 ad: The android device to sync time on. 521 """ 522 ad.adb.shell("settings put global auto_time 0", ignore_status=True) 523 ad.adb.shell("settings put global auto_time_zone 0", ignore_status=True) 524 droid = ad.droid 525 droid.setTimeZone(get_timezone_olson_id()) 526 droid.setTime(get_current_epoch_time()) 527 528 529# Timeout decorator block 530class TimeoutError(Exception): 531 """Exception for timeout decorator related errors. 532 """ 533 pass 534 535 536def _timeout_handler(signum, frame): 537 """Handler function used by signal to terminate a timed out function. 538 """ 539 raise TimeoutError() 540 541 542def timeout(sec): 543 """A decorator used to add time out check to a function. 544 545 This only works in main thread due to its dependency on signal module. 546 Do NOT use it if the decorated funtion does not run in the Main thread. 547 548 Args: 549 sec: Number of seconds to wait before the function times out. 550 No timeout if set to 0 551 552 Returns: 553 What the decorated function returns. 554 555 Raises: 556 TimeoutError is raised when time out happens. 557 """ 558 def decorator(func): 559 @functools.wraps(func) 560 def wrapper(*args, **kwargs): 561 if sec: 562 signal.signal(signal.SIGALRM, _timeout_handler) 563 signal.alarm(sec) 564 try: 565 return func(*args, **kwargs) 566 except TimeoutError: 567 raise TimeoutError(("Function {} timed out after {} " 568 "seconds.").format(func.__name__, sec)) 569 finally: 570 signal.alarm(0) 571 572 return wrapper 573 574 return decorator 575 576 577def trim_model_name(model): 578 """Trim any prefix and postfix and return the android designation of the 579 model name. 580 581 e.g. "m_shamu" will be trimmed to "shamu". 582 583 Args: 584 model: model name to be trimmed. 585 586 Returns 587 Trimmed model name if one of the known model names is found. 588 None otherwise. 589 """ 590 # Directly look up first. 591 if model in models: 592 return model 593 if model in manufacture_name_to_model: 594 return manufacture_name_to_model[model] 595 # If not found, try trimming off prefix/postfix and look up again. 596 tokens = re.split("_|-", model) 597 for t in tokens: 598 if t in models: 599 return t 600 if t in manufacture_name_to_model: 601 return manufacture_name_to_model[t] 602 return None 603 604 605def force_airplane_mode(ad, new_state, timeout_value=60): 606 """Force the device to set airplane mode on or off by adb shell command. 607 608 Args: 609 ad: android device object. 610 new_state: Turn on airplane mode if True. 611 Turn off airplane mode if False. 612 timeout_value: max wait time for 'adb wait-for-device' 613 614 Returns: 615 True if success. 616 False if timeout. 617 """ 618 619 # Using timeout decorator. 620 # Wait for device with timeout. If after <timeout_value> seconds, adb 621 # is still waiting for device, throw TimeoutError exception. 622 @timeout(timeout_value) 623 def wait_for_device_with_timeout(ad): 624 ad.adb.wait_for_device() 625 626 try: 627 wait_for_device_with_timeout(ad) 628 ad.adb.shell("settings put global airplane_mode_on {}".format( 629 1 if new_state else 0)) 630 ad.adb.shell("am broadcast -a android.intent.action.AIRPLANE_MODE") 631 except TimeoutError: 632 # adb wait for device timeout 633 return False 634 return True 635 636 637def get_battery_level(ad): 638 """Gets battery level from device 639 640 Returns: 641 battery_level: int indicating battery level 642 """ 643 output = ad.adb.shell("dumpsys battery") 644 match = re.search(r"level: (?P<battery_level>\S+)", output) 645 battery_level = int(match.group("battery_level")) 646 return battery_level 647 648 649def get_device_usb_charging_status(ad): 650 """ Returns the usb charging status of the device. 651 652 Args: 653 ad: android device object 654 655 Returns: 656 True if charging 657 False if not charging 658 """ 659 adb_shell_result = ad.adb.shell("dumpsys deviceidle get charging") 660 ad.log.info("Device Charging State: {}".format(adb_shell_result)) 661 return adb_shell_result == 'true' 662 663 664def disable_usb_charging(ad): 665 """ Unplug device from usb charging. 666 667 Args: 668 ad: android device object 669 670 Returns: 671 True if device is unplugged 672 False otherwise 673 """ 674 ad.adb.shell("dumpsys battery unplug") 675 if not get_device_usb_charging_status(ad): 676 return True 677 else: 678 ad.log.info("Could not disable USB charging") 679 return False 680 681 682def enable_usb_charging(ad): 683 """ Plug device to usb charging. 684 685 Args: 686 ad: android device object 687 688 Returns: 689 True if device is Plugged 690 False otherwise 691 """ 692 ad.adb.shell("dumpsys battery reset") 693 if get_device_usb_charging_status(ad): 694 return True 695 else: 696 ad.log.info("Could not enable USB charging") 697 return False 698 699 700def enable_doze(ad): 701 """Force the device into doze mode. 702 703 Args: 704 ad: android device object. 705 706 Returns: 707 True if device is in doze mode. 708 False otherwise. 709 """ 710 ad.adb.shell("dumpsys battery unplug") 711 ad.adb.shell("dumpsys deviceidle enable") 712 ad.adb.shell("dumpsys deviceidle force-idle") 713 ad.droid.goToSleepNow() 714 time.sleep(5) 715 adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep") 716 if not adb_shell_result.startswith(DozeModeStatus.IDLE): 717 info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) 718 print(info) 719 return False 720 return True 721 722 723def disable_doze(ad): 724 """Force the device not in doze mode. 725 726 Args: 727 ad: android device object. 728 729 Returns: 730 True if device is not in doze mode. 731 False otherwise. 732 """ 733 ad.adb.shell("dumpsys deviceidle disable") 734 ad.adb.shell("dumpsys battery reset") 735 adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep") 736 if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): 737 info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) 738 print(info) 739 return False 740 return True 741 742 743def enable_doze_light(ad): 744 """Force the device into doze light mode. 745 746 Args: 747 ad: android device object. 748 749 Returns: 750 True if device is in doze light mode. 751 False otherwise. 752 """ 753 ad.adb.shell("dumpsys battery unplug") 754 ad.droid.goToSleepNow() 755 time.sleep(5) 756 ad.adb.shell("cmd deviceidle enable light") 757 ad.adb.shell("cmd deviceidle step light") 758 adb_shell_result = ad.adb.shell("dumpsys deviceidle get light") 759 if not adb_shell_result.startswith(DozeModeStatus.IDLE): 760 info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) 761 print(info) 762 return False 763 return True 764 765 766def disable_doze_light(ad): 767 """Force the device not in doze light mode. 768 769 Args: 770 ad: android device object. 771 772 Returns: 773 True if device is not in doze light mode. 774 False otherwise. 775 """ 776 ad.adb.shell("dumpsys battery reset") 777 ad.adb.shell("cmd deviceidle disable light") 778 adb_shell_result = ad.adb.shell("dumpsys deviceidle get light") 779 if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): 780 info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) 781 print(info) 782 return False 783 return True 784 785 786def set_ambient_display(ad, new_state): 787 """Set "Ambient Display" in Settings->Display 788 789 Args: 790 ad: android device object. 791 new_state: new state for "Ambient Display". True or False. 792 """ 793 ad.adb.shell( 794 "settings put secure doze_enabled {}".format(1 if new_state else 0)) 795 796 797def set_adaptive_brightness(ad, new_state): 798 """Set "Adaptive Brightness" in Settings->Display 799 800 Args: 801 ad: android device object. 802 new_state: new state for "Adaptive Brightness". True or False. 803 """ 804 ad.adb.shell("settings put system screen_brightness_mode {}".format( 805 1 if new_state else 0)) 806 807 808def set_auto_rotate(ad, new_state): 809 """Set "Auto-rotate" in QuickSetting 810 811 Args: 812 ad: android device object. 813 new_state: new state for "Auto-rotate". True or False. 814 """ 815 ad.adb.shell("settings put system accelerometer_rotation {}".format( 816 1 if new_state else 0)) 817 818 819def set_location_service(ad, new_state): 820 """Set Location service on/off in Settings->Location 821 822 Args: 823 ad: android device object. 824 new_state: new state for "Location service". 825 If new_state is False, turn off location service. 826 If new_state if True, set location service to "High accuracy". 827 """ 828 ad.adb.shell("content insert --uri " 829 " content://com.google.settings/partner --bind " 830 "name:s:network_location_opt_in --bind value:s:1") 831 ad.adb.shell("content insert --uri " 832 " content://com.google.settings/partner --bind " 833 "name:s:use_location_for_services --bind value:s:1") 834 if new_state: 835 ad.adb.shell("settings put secure location_mode 3") 836 else: 837 ad.adb.shell("settings put secure location_mode 0") 838 839 840def set_mobile_data_always_on(ad, new_state): 841 """Set Mobile_Data_Always_On feature bit 842 843 Args: 844 ad: android device object. 845 new_state: new state for "mobile_data_always_on" 846 if new_state is False, set mobile_data_always_on disabled. 847 if new_state if True, set mobile_data_always_on enabled. 848 """ 849 ad.adb.shell("settings put global mobile_data_always_on {}".format( 850 1 if new_state else 0)) 851 852 853def bypass_setup_wizard(ad): 854 """Bypass the setup wizard on an input Android device 855 856 Args: 857 ad: android device object. 858 859 Returns: 860 True if Android device successfully bypassed the setup wizard. 861 False if failed. 862 """ 863 try: 864 ad.adb.shell("am start -n \"com.google.android.setupwizard/" 865 ".SetupWizardExitActivity\"") 866 logging.debug("No error during default bypass call.") 867 except adb.AdbError as adb_error: 868 if adb_error.stdout == "ADB_CMD_OUTPUT:0": 869 if adb_error.stderr and \ 870 not adb_error.stderr.startswith("Error type 3\n"): 871 logging.error("ADB_CMD_OUTPUT:0, but error is %s " % 872 adb_error.stderr) 873 raise adb_error 874 logging.debug("Bypass wizard call received harmless error 3: " 875 "No setup to bypass.") 876 elif adb_error.stdout == "ADB_CMD_OUTPUT:255": 877 # Run it again as root. 878 ad.adb.root_adb() 879 logging.debug("Need root access to bypass setup wizard.") 880 try: 881 ad.adb.shell("am start -n \"com.google.android.setupwizard/" 882 ".SetupWizardExitActivity\"") 883 logging.debug("No error during rooted bypass call.") 884 except adb.AdbError as adb_error: 885 if adb_error.stdout == "ADB_CMD_OUTPUT:0": 886 if adb_error.stderr and \ 887 not adb_error.stderr.startswith("Error type 3\n"): 888 logging.error("Rooted ADB_CMD_OUTPUT:0, but error is " 889 "%s " % adb_error.stderr) 890 raise adb_error 891 logging.debug( 892 "Rooted bypass wizard call received harmless " 893 "error 3: No setup to bypass.") 894 895 # magical sleep to wait for the gservices override broadcast to complete 896 time.sleep(3) 897 898 provisioned_state = int( 899 ad.adb.shell("settings get global device_provisioned")) 900 if provisioned_state != 1: 901 logging.error("Failed to bypass setup wizard.") 902 return False 903 logging.debug("Setup wizard successfully bypassed.") 904 return True 905 906 907def parse_ping_ouput(ad, count, out, loss_tolerance=20): 908 """Ping Parsing util. 909 910 Args: 911 ad: Android Device Object. 912 count: Number of ICMP packets sent 913 out: shell output text of ping operation 914 loss_tolerance: Threshold after which flag test as false 915 Returns: 916 False: if packet loss is more than loss_tolerance% 917 True: if all good 918 """ 919 result = re.search( 920 r"(\d+) packets transmitted, (\d+) received, (\d+)% packet loss", out) 921 if not result: 922 ad.log.info("Ping failed with %s", out) 923 return False 924 925 packet_loss = int(result.group(3)) 926 packet_xmit = int(result.group(1)) 927 packet_rcvd = int(result.group(2)) 928 min_packet_xmit_rcvd = (100 - loss_tolerance) * 0.01 929 if (packet_loss > loss_tolerance 930 or packet_xmit < count * min_packet_xmit_rcvd 931 or packet_rcvd < count * min_packet_xmit_rcvd): 932 ad.log.error("%s, ping failed with loss more than tolerance %s%%", 933 result.group(0), loss_tolerance) 934 return False 935 ad.log.info("Ping succeed with %s", result.group(0)) 936 return True 937 938 939def adb_shell_ping(ad, 940 count=120, 941 dest_ip="www.google.com", 942 timeout=200, 943 loss_tolerance=20): 944 """Ping utility using adb shell. 945 946 Args: 947 ad: Android Device Object. 948 count: Number of ICMP packets to send 949 dest_ip: hostname or IP address 950 default www.google.com 951 timeout: timeout for icmp pings to complete. 952 """ 953 ping_cmd = "ping -W 1" 954 if count: 955 ping_cmd += " -c %d" % count 956 if dest_ip: 957 ping_cmd += " %s" % dest_ip 958 try: 959 ad.log.info("Starting ping test to %s using adb command %s", dest_ip, 960 ping_cmd) 961 out = ad.adb.shell(ping_cmd, timeout=timeout, ignore_status=True) 962 if not parse_ping_ouput(ad, count, out, loss_tolerance): 963 return False 964 return True 965 except Exception as e: 966 ad.log.warning("Ping Test to %s failed with exception %s", dest_ip, e) 967 return False 968 969 970def unzip_maintain_permissions(zip_path, extract_location): 971 """Unzip a .zip file while maintaining permissions. 972 973 Args: 974 zip_path: The path to the zipped file. 975 extract_location: the directory to extract to. 976 """ 977 with zipfile.ZipFile(zip_path, 'r') as zip_file: 978 for info in zip_file.infolist(): 979 _extract_file(zip_file, info, extract_location) 980 981 982def _extract_file(zip_file, zip_info, extract_location): 983 """Extracts a single entry from a ZipFile while maintaining permissions. 984 985 Args: 986 zip_file: A zipfile.ZipFile. 987 zip_info: A ZipInfo object from zip_file. 988 extract_location: The directory to extract to. 989 """ 990 out_path = zip_file.extract(zip_info.filename, path=extract_location) 991 perm = zip_info.external_attr >> 16 992 os.chmod(out_path, perm) 993 994 995def get_directory_size(path): 996 """Computes the total size of the files in a directory, including subdirectories. 997 998 Args: 999 path: The path of the directory. 1000 Returns: 1001 The size of the provided directory. 1002 """ 1003 total = 0 1004 for dirpath, dirnames, filenames in os.walk(path): 1005 for filename in filenames: 1006 total += os.path.getsize(os.path.join(dirpath, filename)) 1007 return total 1008 1009 1010def get_command_uptime(command_regex): 1011 """Returns the uptime for a given command. 1012 1013 Args: 1014 command_regex: A regex that matches the command line given. Must be 1015 pgrep compatible. 1016 """ 1017 pid = job.run('pgrep -f %s' % command_regex).stdout 1018 runtime = '' 1019 if pid: 1020 runtime = job.run('ps -o etime= -p "%s"' % pid).stdout 1021 return runtime 1022 1023 1024def get_process_uptime(process): 1025 """Returns the runtime in [[dd-]hh:]mm:ss, or '' if not running.""" 1026 pid = job.run('pidof %s' % process, ignore_status=True).stdout 1027 runtime = '' 1028 if pid: 1029 runtime = job.run('ps -o etime= -p "%s"' % pid).stdout 1030 return runtime 1031 1032 1033def get_device_process_uptime(adb, process): 1034 """Returns the uptime of a device process.""" 1035 pid = adb.shell('pidof %s' % process, ignore_status=True) 1036 runtime = '' 1037 if pid: 1038 runtime = adb.shell('ps -o etime= -p "%s"' % pid) 1039 return runtime 1040 1041 1042def wait_until(func, timeout_s, condition=True, sleep_s=1.0): 1043 """Executes a function repeatedly until condition is met. 1044 1045 Args: 1046 func: The function pointer to execute. 1047 timeout_s: Amount of time (in seconds) to wait before raising an 1048 exception. 1049 condition: The ending condition of the WaitUntil loop. 1050 sleep_s: The amount of time (in seconds) to sleep between each function 1051 execution. 1052 1053 Returns: 1054 The time in seconds before detecting a successful condition. 1055 1056 Raises: 1057 TimeoutError: If the condition was never met and timeout is hit. 1058 """ 1059 start_time = time.time() 1060 end_time = start_time + timeout_s 1061 count = 0 1062 while True: 1063 count += 1 1064 if func() == condition: 1065 return time.time() - start_time 1066 if time.time() > end_time: 1067 break 1068 time.sleep(sleep_s) 1069 raise TimeoutError('Failed to complete function %s in %d seconds having ' 1070 'attempted %d times.' % (str(func), timeout_s, count)) 1071 1072 1073# Adapted from 1074# https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python 1075# Available under the Creative Commons Attribution-ShareAlike License 1076def levenshtein(string1, string2): 1077 """Returns the Levenshtein distance of two strings. 1078 Uses Dynamic Programming approach, only keeping track of 1079 two rows of the DP table at a time. 1080 1081 Args: 1082 string1: String to compare to string2 1083 string2: String to compare to string1 1084 1085 Returns: 1086 distance: the Levenshtein distance between string1 and string2 1087 """ 1088 1089 if len(string1) < len(string2): 1090 return levenshtein(string2, string1) 1091 1092 if len(string2) == 0: 1093 return len(string1) 1094 1095 previous_row = range(len(string2) + 1) 1096 for i, char1 in enumerate(string1): 1097 current_row = [i + 1] 1098 for j, char2 in enumerate(string2): 1099 insertions = previous_row[j + 1] + 1 1100 deletions = current_row[j] + 1 1101 substitutions = previous_row[j] + (char1 != char2) 1102 current_row.append(min(insertions, deletions, substitutions)) 1103 previous_row = current_row 1104 1105 return previous_row[-1] 1106 1107 1108def string_similarity(s1, s2): 1109 """Returns a similarity measurement based on Levenshtein distance. 1110 1111 Args: 1112 s1: the string to compare to s2 1113 s2: the string to compare to s1 1114 1115 Returns: 1116 result: the similarity metric 1117 """ 1118 lev = levenshtein(s1, s2) 1119 try: 1120 lev_ratio = float(lev) / max(len(s1), len(s2)) 1121 result = (1.0 - lev_ratio) * 100 1122 except ZeroDivisionError: 1123 result = 100 if not s2 else 0 1124 return float(result) 1125 1126 1127def run_concurrent_actions_no_raise(*calls): 1128 """Concurrently runs all callables passed in using multithreading. 1129 1130 Example: 1131 1132 >>> def test_function_1(arg1, arg2): 1133 >>> return arg1, arg2 1134 >>> 1135 >>> def test_function_2(arg1, kwarg='kwarg'): 1136 >>> raise arg1(kwarg) 1137 >>> 1138 >>> run_concurrent_actions_no_raise( 1139 >>> lambda: test_function_1('arg1', 'arg2'), 1140 >>> lambda: test_function_2(IndexError, kwarg='kwarg'), 1141 >>> ) 1142 >>> # Output: 1143 >>> [('arg1', 'arg2'), IndexError('kwarg')] 1144 1145 Args: 1146 *calls: A *args list of argumentless callable objects to be called. Note 1147 that if a function has arguments it can be turned into an 1148 argumentless function via the lambda keyword or functools.partial. 1149 1150 Returns: 1151 An array of the returned values or exceptions received from calls, 1152 respective of the order given. 1153 """ 1154 with ThreadPoolExecutor(max_workers=len(calls)) as executor: 1155 futures = [executor.submit(call) for call in calls] 1156 1157 results = [] 1158 for future in futures: 1159 try: 1160 results.append(future.result()) 1161 except Exception as e: 1162 results.append(e) 1163 return results 1164 1165 1166def run_concurrent_actions(*calls): 1167 """Runs all callables passed in concurrently using multithreading. 1168 1169 Examples: 1170 1171 >>> def test_function_1(arg1, arg2): 1172 >>> print(arg1, arg2) 1173 >>> 1174 >>> def test_function_2(arg1, kwarg='kwarg'): 1175 >>> raise arg1(kwarg) 1176 >>> 1177 >>> run_concurrent_actions( 1178 >>> lambda: test_function_1('arg1', 'arg2'), 1179 >>> lambda: test_function_2(IndexError, kwarg='kwarg'), 1180 >>> ) 1181 >>> 'The above line raises IndexError("kwarg")' 1182 1183 Args: 1184 *calls: A *args list of argumentless callable objects to be called. Note 1185 that if a function has arguments it can be turned into an 1186 argumentless function via the lambda keyword or functools.partial. 1187 1188 Returns: 1189 An array of the returned values respective of the order of the calls 1190 argument. 1191 1192 Raises: 1193 If an exception is raised in any of the calls, the first exception 1194 caught will be raised. 1195 """ 1196 first_exception = None 1197 1198 class WrappedException(Exception): 1199 """Raised when a passed-in callable raises an exception.""" 1200 1201 def call_wrapper(call): 1202 nonlocal first_exception 1203 1204 try: 1205 return call() 1206 except Exception as e: 1207 logging.exception(e) 1208 # Note that there is a potential race condition between two 1209 # exceptions setting first_exception. Even if a locking mechanism 1210 # was added to prevent this from happening, it is still possible 1211 # that we capture the second exception as the first exception, as 1212 # the active thread can swap to the thread that raises the second 1213 # exception. There is no way to solve this with the tools we have 1214 # here, so we do not bother. The effects this issue has on the 1215 # system as a whole are negligible. 1216 if first_exception is None: 1217 first_exception = e 1218 raise WrappedException(e) 1219 1220 with ThreadPoolExecutor(max_workers=len(calls)) as executor: 1221 futures = [executor.submit(call_wrapper, call) for call in calls] 1222 1223 results = [] 1224 for future in futures: 1225 try: 1226 results.append(future.result()) 1227 except WrappedException: 1228 # We do not need to raise here, since first_exception will already 1229 # be set to the first exception raised by these callables. 1230 break 1231 1232 if first_exception: 1233 raise first_exception 1234 1235 return results 1236 1237 1238def test_concurrent_actions(*calls, failure_exceptions=(Exception, )): 1239 """Concurrently runs all passed in calls using multithreading. 1240 1241 If any callable raises an Exception found within failure_exceptions, the 1242 test case is marked as a failure. 1243 1244 Example: 1245 >>> def test_function_1(arg1, arg2): 1246 >>> print(arg1, arg2) 1247 >>> 1248 >>> def test_function_2(kwarg='kwarg'): 1249 >>> raise IndexError(kwarg) 1250 >>> 1251 >>> test_concurrent_actions( 1252 >>> lambda: test_function_1('arg1', 'arg2'), 1253 >>> lambda: test_function_2(kwarg='kwarg'), 1254 >>> failure_exceptions=IndexError 1255 >>> ) 1256 >>> 'raises signals.TestFailure due to IndexError being raised.' 1257 1258 Args: 1259 *calls: A *args list of argumentless callable objects to be called. Note 1260 that if a function has arguments it can be turned into an 1261 argumentless function via the lambda keyword or functools.partial. 1262 failure_exceptions: A tuple of all possible Exceptions that will mark 1263 the test as a FAILURE. Any exception that is not in this list will 1264 mark the tests as UNKNOWN. 1265 1266 Returns: 1267 An array of the returned values respective of the order of the calls 1268 argument. 1269 1270 Raises: 1271 signals.TestFailure if any call raises an Exception. 1272 """ 1273 try: 1274 return run_concurrent_actions(*calls) 1275 except signals.TestFailure: 1276 # Do not modify incoming test failures 1277 raise 1278 except failure_exceptions as e: 1279 raise signals.TestFailure(e) 1280 1281 1282class SuppressLogOutput(object): 1283 """Context manager used to suppress all logging output for the specified 1284 logger and level(s). 1285 """ 1286 def __init__(self, logger=logging.getLogger(), log_levels=None): 1287 """Create a SuppressLogOutput context manager 1288 1289 Args: 1290 logger: The logger object to suppress 1291 log_levels: Levels of log handlers to disable. 1292 """ 1293 1294 self._logger = logger 1295 self._log_levels = log_levels or [ 1296 logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, 1297 logging.CRITICAL 1298 ] 1299 if isinstance(self._log_levels, int): 1300 self._log_levels = [self._log_levels] 1301 self._handlers = copy.copy(self._logger.handlers) 1302 1303 def __enter__(self): 1304 for handler in self._handlers: 1305 if handler.level in self._log_levels: 1306 self._logger.removeHandler(handler) 1307 return self 1308 1309 def __exit__(self, *_): 1310 for handler in self._handlers: 1311 self._logger.addHandler(handler) 1312 1313 1314class BlockingTimer(object): 1315 """Context manager used to block until a specified amount of time has 1316 elapsed. 1317 """ 1318 def __init__(self, secs): 1319 """Initializes a BlockingTimer 1320 1321 Args: 1322 secs: Number of seconds to wait before exiting 1323 """ 1324 self._thread = threading.Timer(secs, lambda: None) 1325 1326 def __enter__(self): 1327 self._thread.start() 1328 return self 1329 1330 def __exit__(self, *_): 1331 self._thread.join() 1332 1333 1334def is_valid_ipv4_address(address): 1335 try: 1336 socket.inet_pton(socket.AF_INET, address) 1337 except AttributeError: # no inet_pton here, sorry 1338 try: 1339 socket.inet_aton(address) 1340 except socket.error: 1341 return False 1342 return address.count('.') == 3 1343 except socket.error: # not a valid address 1344 return False 1345 1346 return True 1347 1348 1349def is_valid_ipv6_address(address): 1350 if '%' in address: 1351 address = address.split('%')[0] 1352 try: 1353 socket.inet_pton(socket.AF_INET6, address) 1354 except socket.error: # not a valid address 1355 return False 1356 return True 1357 1358 1359def merge_dicts(*dict_args): 1360 """ Merges args list of dictionaries into a single dictionary. 1361 1362 Args: 1363 dict_args: an args list of dictionaries to be merged. If multiple 1364 dictionaries share a key, the last in the list will appear in the 1365 final result. 1366 """ 1367 result = {} 1368 for dictionary in dict_args: 1369 result.update(dictionary) 1370 return result 1371 1372 1373def ascii_string(uc_string): 1374 """Converts unicode string to ascii""" 1375 return str(uc_string).encode('ASCII') 1376 1377 1378def get_interface_ip_addresses(comm_channel, interface): 1379 """Gets all of the ip addresses, ipv4 and ipv6, associated with a 1380 particular interface name. 1381 1382 Args: 1383 comm_channel: How to send commands to a device. Can be ssh, adb serial, 1384 etc. Must have the run function implemented. 1385 interface: The interface name on the device, ie eth0 1386 1387 Returns: 1388 A list of dictionaries of the the various IP addresses: 1389 ipv4_private_local_addresses: Any 192.168, 172.16, or 10 1390 addresses 1391 ipv4_public_addresses: Any IPv4 public addresses 1392 ipv6_link_local_addresses: Any fe80:: addresses 1393 ipv6_private_local_addresses: Any fd00:: addresses 1394 ipv6_public_addresses: Any publicly routable addresses 1395 """ 1396 ipv4_private_local_addresses = [] 1397 ipv4_public_addresses = [] 1398 ipv6_link_local_addresses = [] 1399 ipv6_private_local_addresses = [] 1400 ipv6_public_addresses = [] 1401 all_interfaces_and_addresses = comm_channel.run( 1402 'ip -o addr | awk \'!/^[0-9]*: ?lo|link\/ether/ {gsub("/", " "); ' 1403 'print $2" "$4}\'').stdout 1404 ifconfig_output = comm_channel.run('ifconfig %s' % interface).stdout 1405 for interface_line in all_interfaces_and_addresses.split('\n'): 1406 if interface != interface_line.split()[0]: 1407 continue 1408 on_device_ip = ipaddress.ip_address(interface_line.split()[1]) 1409 if on_device_ip.version() == 4: 1410 if on_device_ip.is_private(): 1411 ipv4_private_local_addresses.append(str(on_device_ip)) 1412 elif on_device_ip.is_global(): 1413 ipv4_public_addresses.append(str(on_device_ip)) 1414 elif on_device_ip.version() == 6: 1415 if on_device_ip.is_link_local(): 1416 ipv6_link_local_addresses.append(str(on_device_ip)) 1417 elif on_device_ip.is_private(): 1418 ipv6_private_local_addresses.append(str(on_device_ip)) 1419 elif on_device_ip.is_global(): 1420 ipv6_public_addresses.append(str(on_device_ip)) 1421 return { 1422 'ipv4_private': ipv4_private_local_addresses, 1423 'ipv4_public': ipv4_public_addresses, 1424 'ipv6_link_local': ipv6_link_local_addresses, 1425 'ipv6_private_local': ipv6_private_local_addresses, 1426 'ipv6_public': ipv6_public_addresses 1427 } 1428 1429 1430def get_interface_based_on_ip(comm_channel, desired_ip_address): 1431 """Gets the interface for a particular IP 1432 1433 Args: 1434 comm_channel: How to send commands to a device. Can be ssh, adb serial, 1435 etc. Must have the run function implemented. 1436 desired_ip_address: The IP address that is being looked for on a device. 1437 1438 Returns: 1439 The name of the test interface. 1440 """ 1441 1442 desired_ip_address = desired_ip_address.split('%', 1)[0] 1443 all_ips_and_interfaces = comm_channel.run( 1444 '(ip -o -4 addr show; ip -o -6 addr show) | ' 1445 'awk \'{print $2" "$4}\'').stdout 1446 #ipv4_addresses = comm_channel.run( 1447 # 'ip -o -4 addr show| awk \'{print $2": "$4}\'').stdout 1448 #ipv6_addresses = comm_channel._ssh_session.run( 1449 # 'ip -o -6 addr show| awk \'{print $2": "$4}\'').stdout 1450 #if desired_ip_address in ipv4_addresses: 1451 # ip_addresses_to_search = ipv4_addresses 1452 #elif desired_ip_address in ipv6_addresses: 1453 # ip_addresses_to_search = ipv6_addresses 1454 for ip_address_and_interface in all_ips_and_interfaces.split('\n'): 1455 if desired_ip_address in ip_address_and_interface: 1456 return ip_address_and_interface.split()[1][:-1] 1457 return None 1458 1459 1460def renew_linux_ip_address(comm_channel, interface): 1461 comm_channel.run('sudo ifconfig %s down' % interface) 1462 comm_channel.run('sudo ifconfig %s up' % interface) 1463 comm_channel.run('sudo dhclient -r %s' % interface) 1464 comm_channel.run('sudo dhclient %s' % interface) 1465