text: # Copyright 2019 The MACE Authors. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  import copy
  import os
  import re
  import subprocess
  import random
  import tempfile
  
  from utils import util
  
  
  def execute(cmd, verbose=True):
      print("CMD> %s" % cmd)
      p = subprocess.Popen([cmd],
                           shell=True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           stdin=subprocess.PIPE,
                           universal_newlines=True)
  
      if not verbose:
          if p.wait() != 0:
              raise Exception("errorcode: %s" % p.returncode)
          return p.stdout.read()
  
      buf = []
  
      while p.poll() is None:
          line = p.stdout.readline().strip()
          if verbose:
              print(line)
          buf.append(line)
  
      for l in p.stdout:
          line = l.strip()
          if verbose:
              print(line)
          buf.append(line)
  
      if p.returncode != 0:
          raise Exception("errorcode: %s" % p.returncode)
  
      return "\n".join(buf)
  
  
  class Device(object):
      def __init__(self, device_id, target_abi):
          self._device_id = device_id
          self._target_abi = target_abi
  
      def install(self, target, install_dir, install_deps=False):
          pass
  
      def run(self, target):
          pass
  
      def pull(self, target, out_dir):
          pass
  
      def mkdir(self, dirname):
          pass
  
      def info(self):
          pass
  
  
  class HostDevice(Device):
      def __init__(self, device_id, target_abi):
          super(HostDevice, self).__init__(device_id, target_abi)
  
      @staticmethod
      def list_devices():
          return ["host"]
  
      def install(self, target, install_dir, install_deps=False):
          install_dir = os.path.abspath(install_dir)
  
          if install_dir.strip() and install_dir != os.path.dirname(target.path):
              execute("mkdir -p %s" % install_dir)
              execute("cp %s %s" % (target.path, install_dir))
              for lib in target.libs:
                  execute("cp %s %s" % (lib, install_dir))
  
              target.path = "%s/%s" % (install_dir,
                                       os.path.basename(target.path))
              target.libs = ["%s/%s" % (install_dir, os.path.basename(lib))
                             for lib in target.libs]
  
          target.envs.append("LD_LIBRARY_PATH=%s" % install_dir)
  
          return target
  
      def run(self, target):
          execute(str(target))
  
      def pull(self, target, out_dir):
          out_dir = os.path.abspath(out_dir)
  
          if out_dir.strip() and out_dir != os.path.dirname(target.path):
              execute("cp -r %s %s" % (target.path, out_dir))
  
      def mkdir(self, dirname):
          execute("mkdir -p %s" % dirname)
  
  
  class AndroidDevice(Device):
      def __init__(self, device_id, target_abi):
          super(AndroidDevice, self).__init__(device_id, target_abi)
  
      @staticmethod
      def list_devices():
          out = execute("adb devices")
          serialno_list = out.strip().split('\n')[1:]
          serialno_list = [tuple(pair.split('\t')) for pair in serialno_list]
          devices = []
          for serialno in serialno_list:
              if not serialno[1].startswith("no permissions"):
                  devices.append(serialno[0])
  
          return devices
  
      def install(self, target, install_dir, install_deps=False):
          install_dir = os.path.abspath(install_dir)
          sn = self._device_id
  
          execute("adb -s %s shell mkdir -p %s" % (sn, install_dir))
          if os.path.isdir(target.path):
              execute("adb -s %s push %s/* %s" % (sn, target.path, install_dir),
                      False)
          else:
              execute("adb -s %s push %s %s" % (sn, target.path, install_dir),
                      False)
  
          for lib in target.libs:
              execute("adb -s %s push %s %s" % (sn, lib, install_dir), False)
  
          device_target = copy.deepcopy(target)
          device_target.path = "%s/%s" % (install_dir,
                                          os.path.basename(target.path))
          device_target.libs = ["%s/%s" % (install_dir, os.path.basename(lib))
                                for lib in target.libs]
          device_target.envs.append("LD_LIBRARY_PATH=%s" % install_dir)
  
          if install_deps:
              self.install_common_libs_for_target(target, install_dir)
  
          return device_target
  
      def install_common_libs_for_target(self, target, install_dir):
          sn = self._device_id
          dep_so_libs = execute(os.environ["ANDROID_NDK_HOME"] + "/ndk-depends "
                                + target.path)
          lib_file = ""
          for dep in dep_so_libs.split("\n"):
              if dep == "libgnustl_shared.so":
                  lib_file = "%s/sources/cxx-stl/gnu-libstdc++/4.9/libs/" \
                             "%s/libgnustl_shared.so" \
                             % (os.environ["ANDROID_NDK_HOME"], self._target_abi)
              elif dep == "libc++_shared.so":
                  lib_file = "%s/sources/cxx-stl/llvm-libc++/libs/" \
                             "%s/libc++_shared.so" \
                             % (os.environ["ANDROID_NDK_HOME"], self._target_abi)
  
          if lib_file:
              execute("adb -s %s push %s %s" % (sn, lib_file, install_dir),
                      False)
  
      def run(self, target):
          tmpdirname = tempfile.mkdtemp()
          cmd_file_path = tmpdirname + "/cmd.sh"
          with open(cmd_file_path, "w") as cmd_file:
              cmd_file.write(str(target))
          target_dir = os.path.dirname(target.path)
          execute("adb -s %s push %s %s" % (self._device_id,
                                            cmd_file_path,
                                            target_dir))
  
          out = execute("adb -s %s shell sh %s" % (self._device_id,
                                                   target_dir + "/cmd.sh"))
          # May have false positive using the following error word
          for line in out.split("\n")[:-10]:
              if ("Aborted" in line
                      or "FAILED" in line or "Segmentation fault" in line):
                  raise Exception(line)
  
      def pull(self, target, out_dir):
          sn = self._device_id
          execute("adb -s %s pull %s %s" % (sn, target.path, out_dir), False)
  
      def mkdir(self, dirname):
          sn = self._device_id
          execute("adb -s %s shell mkdir -p %s" % (sn, dirname))
  
      def info(self):
          sn = self._device_id
          output = execute("adb -s %s shell getprop" % sn, False)
          raw_props = output.split("\n")
          props = {}
          p = re.compile(r'\[(.+)\]: \[(.+)\]')
          for raw_prop in raw_props:
              m = p.match(raw_prop)
              if m:
                  props[m.group(1)] = m.group(2)
          return props
  
  
  class ArmLinuxDevice(Device):
      devices = {}
  
      def __init__(self, device_id, target_abi):
          super(ArmLinuxDevice, self).__init__(device_id, target_abi)
  
      @staticmethod
      def list_devices():
          device_ids = []
          print("!!!", ArmLinuxDevice.devices)
          for dev_name, dev_info in ArmLinuxDevice.devices.items():
              address = dev_info["address"]
              username = dev_info["username"]
              device_ids.append("%s@%s" % (username, address))
          return device_ids
  
      @staticmethod
      def set_devices(devices):
          ArmLinuxDevice.devices = devices
  
      def install(self, target, install_dir, install_deps=False):
          install_dir = os.path.abspath(install_dir)
          ip = self._device_id
  
          execute("ssh %s mkdir -p %s" % (ip, install_dir))
          execute("scp -r %s %s:%s" % (target.path, ip, install_dir))
          for lib in target.libs:
              execute("scp -r %s:%s" % (lib, install_dir))
  
          target.path = "%s/%s" % (install_dir, os.path.basename(target.path))
          target.libs = ["%s/%s" % (install_dir, os.path.basename(lib))
                         for lib in target.libs]
          target.envs.append("LD_LIBRARY_PATH=%s" % install_dir)
  
          return target
  
      def run(self, target):
          execute("ssh %s %s" % (self._device_id, target))
  
      def pull(self, target, out_dir):
          sn = self._device_id
          execute("scp -r %s:%s %s" % (sn, target.path, out_dir))
  
      def mkdir(self, dirname):
          sn = self._device_id
          execute("ssh %s mkdir -p %s" % (sn, dirname))
  
  
  def device_class(target_abi):
      device_dispatch = {
          "host": "HostDevice",
          "armeabi-v7a": "AndroidDevice",
          "arm64-v8a": "AndroidDevice",
          "arm-linux-gnueabihf": "ArmLinuxDevice",
          "aarch64-linux-gnu": "ArmLinuxDevice"
      }
  
      if target_abi not in device_dispatch:
          raise ValueError(
              "target_abi should be one of %s" % device_dispatch.keys())
  
      return globals()[device_dispatch[target_abi]]
  
  
  def crete_device(target_abi, device_id=None):
      return device_class(target_abi)(device_id, target_abi)
  
  
  def choose_devices(target_abi, target_ids):
      device_clazz = device_class(target_abi)
      devices = device_clazz.list_devices()
  
      if target_ids == "all":
          run_devices = devices
      elif target_ids == "random":
          unlocked_devices = [dev for dev in devices if
                              not util.is_device_locked(dev)]
          if unlocked_devices:
              run_devices = [random.choice(unlocked_devices)]
          else:
              run_devices = [random.choice(devices)]
      else:
          device_id_list = [dev.strip() for dev in target_ids.split(",")]
          run_devices = [dev for dev in device_id_list if dev in devices]
  
      return run_devices
  