1#!/usr/bin/env python
2#
3# Copyright (C) 2020 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#
17
18import logging
19import subprocess
20import time
21
22from threading import Timer
23
24from vts.testcases.vndk import utils
25
26SYSPROP_DEV_BOOTCOMPLETE = "dev.bootcomplete"
27SYSPROP_SYS_BOOT_COMPLETED = "sys.boot_completed"
28
29
30class Shell(object):
31    """This class to wrap adb shell command."""
32
33    def __init__(self, serial_number):
34        self._serial_number = serial_number
35
36    def Execute(self, *args):
37        """Executes a command.
38
39        Args:
40            args: Strings, the arguments.
41
42        Returns:
43            Stdout as a string, stderr as a string, and return code as an
44            integer.
45        """
46        cmd = ["adb", "-s", self._serial_number, "shell"]
47        cmd.extend(args)
48        return RunCommand(cmd)
49
50
51class ADB(object):
52    """This class to wrap adb command."""
53
54    def __init__(self, serial_number):
55        self._serial_number = serial_number
56
57    def Execute(self, cmd_list, timeout=None):
58        """Executes a command.
59
60        Args:
61            args: Strings, the arguments.
62
63        Returns:
64            Stdout as a string, stderr as a string, and return code as an
65            integer.
66        """
67        cmd = ["adb", "-s", self._serial_number]
68        cmd.extend(cmd_list)
69        return RunCommand(cmd, timeout)
70
71class AndroidDevice(utils.AndroidDevice):
72    """This class controls the device via adb commands."""
73
74    def __init__(self, serial_number):
75        super(AndroidDevice, self).__init__(serial_number)
76        self._serial_number = serial_number
77        self.shell = Shell(serial_number)
78        self.adb = ADB(serial_number)
79
80    def GetPermission(self, filepath):
81        """Get file permission."""
82        out, err, r_code = self.shell.Execute('stat -c %%a %s' % filepath)
83        if r_code != 0 or err.strip():
84            raise IOError("`stat -c %%a '%s'` stdout: %s\nstderr: %s" %
85                          (filepath, out, err))
86        return out.strip()
87
88    def WaitForBootCompletion(self, timeout=None):
89        """Get file permission."""
90        start = time.time()
91        cmd = ['wait-for-device']
92        self.adb.Execute(cmd, timeout)
93        while not self.isBootCompleted():
94            if time.time() - start >= timeout:
95                logging.error("Timeout while waiting for boot completion.")
96                return False
97            time.sleep(1)
98        return True
99
100    def isBootCompleted(self):
101        """Checks whether the device has booted.
102
103        Returns:
104            True if booted, False otherwise.
105        """
106        try:
107            if (self._GetProp(SYSPROP_SYS_BOOT_COMPLETED) == '1' and
108                    self._GetProp(SYSPROP_DEV_BOOTCOMPLETE) == '1'):
109                return True
110        except Exception as e:
111            # adb shell calls may fail during certain period of booting
112            # process, which is normal. Ignoring these errors.
113            pass
114        return False
115
116    def IsShutdown(self, timeout=0):
117        """Checks whether the device has booted.
118
119        Returns:
120            True if booted, False otherwise.
121        """
122        start = time.time()
123        while (time.time() - start) <= timeout:
124            if not self.isBootCompleted():
125                return True
126            time.sleep(1)
127        return self.isBootCompleted()
128
129    def Root(self, timeout=None):
130        cmd = ["root"]
131        try:
132            self.adb.Execute(cmd, timeout=timeout)
133            return True
134        except subprocess.CalledProcessError as e:
135            logging.exception(e)
136        return False
137
138    def ReadFileContent(self, filepath):
139        """Read the content of a file and perform assertions.
140
141        Args:
142            filepath: string, path to file
143
144        Returns:
145            string, content of file
146        """
147        cmd = "cat %s" % filepath
148        out, err, r_code = self.shell.Execute(cmd)
149
150        # checks the exit code
151        if r_code != 0 or err.strip():
152            raise IOError("%s: Error happened while reading the file due to %s."
153                          % (filepath, err))
154        return out
155
156
157def RunCommand(cmd, timeout=None):
158    kill = lambda process:process.kill()
159    proc = subprocess.Popen(args=cmd,
160                            stderr=subprocess.PIPE,
161                            stdout=subprocess.PIPE)
162    _timer = Timer(timeout, kill, [proc])
163    try:
164        _timer.start()
165        (out, err) = proc.communicate()
166    finally:
167        _timer.cancel()
168
169    try:
170        return out.decode('UTF-8'), err.decode('UTF-8'), proc.returncode
171    except UnicodeDecodeError:
172        # ProcUidCpuPowerTimeInStateTest, ProcUidCpuPowerConcurrentActiveTimeTest,
173        # and ProcUidCpuPowerConcurrentPolicyTimeTest output could not be decode
174        # to UTF-8.
175        return out.decode('ISO-8859-1'), err.decode('ISO-8859-1'), proc.returncode
176