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