• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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