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):
130        try:
131            self.adb.Execute(["root"])
132            RETRIES = 3
133            for i in range(RETRIES):
134                self.adb.Execute(["wait-for-device"])
135                # Verify that we haven't raced with the exit of the old,
136                # non-root adbd
137                out, err, r_code = self.shell.Execute("id -un")
138                if r_code == 0 and not err.strip() and out.strip() == "root":
139                    return True
140                time.sleep(1)
141        except subprocess.CalledProcessError as e:
142            logging.exception(e)
143        return False
144
145    def ReadFileContent(self, filepath):
146        """Read the content of a file and perform assertions.
147
148        Args:
149            filepath: string, path to file
150
151        Returns:
152            string, content of file
153        """
154        cmd = "cat %s" % filepath
155        out, err, r_code = self.shell.Execute(cmd)
156
157        # checks the exit code
158        if r_code != 0 or err.strip():
159            raise IOError("%s: Error happened while reading the file due to %s."
160                          % (filepath, err))
161        return out
162
163
164def RunCommand(cmd, timeout=None):
165    kill = lambda process:process.kill()
166    proc = subprocess.Popen(args=cmd,
167                            stderr=subprocess.PIPE,
168                            stdout=subprocess.PIPE)
169    _timer = Timer(timeout, kill, [proc])
170    try:
171        _timer.start()
172        (out, err) = proc.communicate()
173    finally:
174        _timer.cancel()
175
176    try:
177        return out.decode('UTF-8'), err.decode('UTF-8'), proc.returncode
178    except UnicodeDecodeError:
179        # ProcUidCpuPowerTimeInStateTest, ProcUidCpuPowerConcurrentActiveTimeTest,
180        # and ProcUidCpuPowerConcurrentPolicyTimeTest output could not be decode
181        # to UTF-8.
182        return out.decode('ISO-8859-1'), err.decode('ISO-8859-1'), proc.returncode
183