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