Exemplo n.º 1
0
    def __init__(self,
                 device_serial,
                 is_emulator=True,
                 output_dir=None,
                 use_hierarchy_viewer=False):
        """
        create a device
        :param device_serial: serial number of target device
        :param is_emulator: boolean, type of device, True for emulator, False for real device
        :return:
        """
        self.logger = logging.getLogger("Device")

        self.serial = device_serial
        self.is_emulator = is_emulator
        self.adb = None
        self.telnet = None
        self.monkeyrunner = None
        self.view_client = None
        self.settings = {}
        self.display_info = None
        self.sdk_version = None
        self.release_version = None
        self.ro_debuggable = None
        self.ro_secure = None

        self.output_dir = output_dir
        if self.output_dir is None:
            self.output_dir = os.path.abspath("droidbot_out")
        if not os.path.exists(self.output_dir):
            os.mkdir(self.output_dir)

        self.use_hierarchy_viewer = use_hierarchy_viewer

        if self.is_emulator:
            self.adb_enabled = True
            self.telnet_enabled = True
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True
        else:
            self.adb_enabled = True
            self.telnet_enabled = False
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True

        self.is_connected = False
        self.connect()
        self.get_sdk_version()
        self.get_release_version()
        self.get_ro_secure()
        self.get_ro_debuggable()
        self.get_display_info()
        self.logcat = self.redirect_logcat()
        from state_monitor import StateMonitor
        self.state_monitor = StateMonitor(device=self)
        self.state_monitor.start()
        self.unlock()
    def __init__(self, action_server):
        # action server for getting requests from AI
        self._action_server = action_server

        # current state of motion coordinator
        self._task = None
        self._first_task_seen = False

        # used for timeouts & extrapolating
        self._time_of_last_task = None
        self._timeout_vel_sent = False

        # to keep things thread safe
        self._lock = threading.RLock()

        # handles monitoring of state of drone
        self._state_monitor = StateMonitor()

        # handles communicating between tasks and LLM
        self._task_command_handler = TaskCommandHandler()

        self._idle_obstacle_avoider = IdleObstacleAvoider()
        self._avoid_magnitude = rospy.get_param("~obst_avoid_magnitude")
        self._kickout_distance = rospy.get_param('~kickout_distance')
        self._new_task_distance = rospy.get_param('~new_task_distance')
        self._safe_distance = rospy.get_param('~safe_distance')

        # safety
        self._safety_client = SafetyClient('motion_command_coordinator')
        self._safety_land_complete = False
        self._safety_land_requested = False

        # Create action client to request a safety landing
        self._action_client = actionlib.SimpleActionClient(
            "motion_planner_server", QuadMoveAction)
        try:
            # update rate for motion coordinator
            self._update_rate = rospy.get_param('~update_rate')
            # task timeout values
            self._task_timeout = rospy.Duration(
                rospy.get_param('~task_timeout'))
            # startup timeout
            self._startup_timeout = rospy.Duration(
                rospy.get_param('~startup_timeout'))
        except KeyError as e:
            rospy.logerr('Could not lookup a parameter for motion coordinator')
            raise
Exemplo n.º 3
0
    def __init__(self, device_serial, is_emulator=True, output_dir=None):
        """
        create a device
        :param device_serial: serial number of target device
        :param is_emulator: boolean, type of device, True for emulator, False for real device
        :return:
        """
        self.logger = logging.getLogger('Device')

        self.serial = device_serial
        # is_emulator 0 for real device, 1 for emulator
        self.is_emulator = is_emulator
        self.adb = None
        self.telnet = None
        self.monkeyrunner = None
        self.view_client = None
        self.settings = {}
        self.display_info = None

        self.output_dir = output_dir
        if self.output_dir is None:
            self.output_dir = os.path.abspath("droidbot_out")
        if not os.path.exists(self.output_dir):
            os.mkdir(self.output_dir)

        if self.is_emulator:
            self.adb_enabled = True
            self.telnet_enabled = True
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True
        else:
            self.adb_enabled = True
            self.telnet_enabled = False
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True

        self.is_connected = False
        self.connect()
        self.get_display_info()
        self.logcat = self.redirect_logcat()
        from state_monitor import StateMonitor
        self.state_monitor = StateMonitor(device=self)
        self.state_monitor.start()
Exemplo n.º 4
0
class Device(object):
    """
    this class describes a connected device
    """

    def __init__(self, device_serial, is_emulator=True, output_dir=None):
        """
        create a device
        :param device_serial: serial number of target device
        :param is_emulator: boolean, type of device, True for emulator, False for real device
        :return:
        """
        self.logger = logging.getLogger("Device")

        self.serial = device_serial
        # is_emulator 0 for real device, 1 for emulator
        self.is_emulator = is_emulator
        self.adb = None
        self.telnet = None
        self.monkeyrunner = None
        self.view_client = None
        self.settings = {}
        self.display_info = None
        self.sdk_version = None
        self.release_version = None
        self.ro_debuggable = None
        self.ro_secure = None

        self.output_dir = output_dir
        if self.output_dir is None:
            self.output_dir = os.path.abspath("droidbot_out")
        if not os.path.exists(self.output_dir):
            os.mkdir(self.output_dir)

        if self.is_emulator:
            self.adb_enabled = True
            self.telnet_enabled = True
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True
        else:
            self.adb_enabled = True
            self.telnet_enabled = False
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True

        self.is_connected = False
        self.connect()
        self.get_sdk_version()
        self.get_release_version()
        self.get_ro_secure()
        self.get_ro_debuggable()
        self.get_display_info()
        self.logcat = self.redirect_logcat()
        from state_monitor import StateMonitor
        self.state_monitor = StateMonitor(device=self)
        self.state_monitor.start()
        self.unlock()
        # assert self.display_info is not None
        # self.check_connectivity()
        # print self.is_emulator, self.host, self.port

    def redirect_logcat(self, output_dir=None):
        if output_dir is None:
            output_dir = self.output_dir
        logcat_file = open("%s/logcat.log" % output_dir, "w")
        import subprocess
        subprocess.check_call(["adb", "-s", self.serial, "logcat", "-c"])
        logcat = subprocess.Popen(["adb", "-s", self.serial, "logcat", "-v", "threadtime"],
                                  stdin=subprocess.PIPE,
                                  stdout=logcat_file)
        return logcat

    def check_connectivity(self):
        """
        check if the device is available
        :return: Ture for available, False for not
        """
        try:
            # try connecting to device
            self.logger.info("checking connectivity...")
            result = True

            if self.adb_enabled and self.adb and self.adb.check_connectivity():
                self.logger.info("ADB is connected")
            else:
                self.logger.warning("ADB is not connected")
                result = False

            if self.telnet_enabled and self.telnet and self.telnet.check_connectivity():
                self.logger.info("Telnet is connected")
            else:
                self.logger.warning("Telnet is not connected")
                result = False

            if self.monkeyrunner_enabled and self.monkeyrunner and self.monkeyrunner.check_connectivity():
                self.logger.info("monkeyrunner is connected")
            else:
                self.logger.warning("monkeyrunner is not connected")
                result = False

            if self.view_client_enabled and self.view_client:
                self.logger.info("view_client is connected")
            else:
                self.logger.warning("view_client is not connected")
                result = False
            return result
        except:
            return False

    def wait_for_device(self):
        """
        wait until the device is fully booted
        :return:
        """
        self.logger.info("waiting for device")
        try:
            subprocess.check_call(["adb", "-s", self.serial, "wait-for-device"])
            while True:
                out = subprocess.check_output(["adb", "-s", self.serial, "shell",
                                               "getprop", "init.svc.bootanim"]).split()[0]
                if out == "stopped":
                    break
                time.sleep(3)
        except:
            self.logger.warning("error waiting for device")

    def connect(self):
        """
        connect this device via adb, telnet and monkeyrunner
        :return:
        """
        # wait for emulator to start
        self.wait_for_device()
        if self.adb_enabled:
            self.get_adb()

        if self.telnet_enabled:
            self.get_telnet()

        if self.monkeyrunner_enabled:
            self.get_monkeyrunner()

        if self.view_client_enabled:
            self.get_view_client()

        time.sleep(3)
        self.is_connected = True

    def disconnect(self):
        """
        disconnect current device
        :return:
        """
        self.is_connected = False
        if self.adb:
            self.adb.disconnect()
        if self.telnet:
            self.telnet.disconnect()
        if self.monkeyrunner:
            self.monkeyrunner.disconnect()
        if self.view_client:
            self.view_client.disconnect()
        self.logcat.terminate()
        self.state_monitor.stop()

    def get_telnet(self):
        """
        get telnet connection of the device
        note that only emulator have telnet connection
        """
        if self.telnet_enabled and self.telnet is None:
            from adapter.telnet import TelnetConsole, TelnetException
            try:
                self.telnet = TelnetConsole(self)
            except TelnetException:
                self.logger.warning("Cannot connect to telnet.")
        return self.telnet

    def get_adb(self):
        """
        get adb connection of the device
        """
        if self.adb_enabled and self.adb is None:
            from adapter.adb import ADB
            self.adb = ADB(self)
        return self.adb

    def get_monkeyrunner(self):
        """
        get monkeyrunner connection of the device
        :return:
        """
        if self.monkeyrunner_enabled and self.monkeyrunner is None:
            from adapter.monkey_runner import MonkeyRunner
            self.monkeyrunner = MonkeyRunner(self)
        return self.monkeyrunner

    def get_view_client(self):
        """
        get view_client connection of the device
        :return:
        """
        if self.view_client_enabled and self.view_client is None:
            from adapter.viewclient import ViewClient
            self.view_client = ViewClient(self, forceviewserveruse=False)
        return self.view_client

    def is_foreground(self, app):
        """
        check if app is in foreground of device
        :param app: App
        :return: boolean
        """
        if isinstance(app, str):
            package_name = app
        elif isinstance(app, App):
            package_name = app.get_package_name()
        else:
            return False

        top_activity_name = self.get_top_activity_name()
        if top_activity_name is None:
            return False
        return top_activity_name.startswith(package_name)

    def get_sdk_version(self):
        """
        Get version of current SDK
        """
        if self.sdk_version is None:
            self.sdk_version = self.get_adb().get_sdk_version()
        return self.sdk_version

    def get_release_version(self):
        """
        Get version of current SDK
        """
        if self.release_version is None:
            self.release_version = self.get_adb().get_release_version()
        return self.release_version

    def get_ro_secure(self):
        if self.ro_secure is None:
            self.ro_secure = self.get_adb().get_ro_secure()
        return self.ro_secure

    def get_ro_debuggable(self):
        if self.ro_debuggable is None:
            self.ro_debuggable = self.get_adb().get_release_version()
        return self.ro_debuggable

    def get_display_info(self, refresh=True):
        """
        get device display infomation, including width, height, and density
        :return: dict, display_info
        @param refresh: if set to True, refresh the display info instead of using the old values
        """
        if self.display_info is None or refresh:
            self.display_info = self.get_adb().getDisplayInfo()
        return self.display_info

    def get_width(self, refresh=False):
        display_info = self.get_display_info(refresh=refresh)
        width = 0
        if "width" in display_info:
            width = display_info["width"]
        elif not refresh:
            width = self.get_width(refresh=True)
        else:
            self.logger.warning("get_width: width not in display_info")
        return width

    def get_height(self, refresh=False):
        display_info = self.get_display_info(refresh=refresh)
        height = 0
        if "height" in display_info:
            height = display_info["height"]
        elif not refresh:
            height = self.get_width(refresh=True)
        else:
            self.logger.warning("get_height: height not in display_info")
        return height

    def unlock(self):
        """
        unlock screen
        skip first-use tutorials
        etc
        :return:
        """
        assert self.get_adb() is not None

        # unlock screen
        self.get_adb().unlock()

        # DONE skip first-use turorials, we don't have to

    def add_env(self, env):
        """
        set env to the device
        :param env: instance of AppEnv
        """
        self.logger.info("deploying env: %s" % env)
        env.deploy(self)

    def add_contact(self, contact_data):
        """
        add a contact to device
        :param contact_data: dict of contact, should have keys like name, phone, email
        :return:
        """
        assert self.get_adb() is not None
        contact_intent = Intent(prefix="start",
                                action="android.intent.action.INSERT",
                                mime_type="vnd.android.cursor.dir/contact",
                                extra_string=contact_data)
        self.send_intent(intent=contact_intent)
        time.sleep(2)
        self.get_adb().press("BACK")
        time.sleep(2)
        self.get_adb().press("BACK")
        return True

    def receive_call(self, phone=DEFAULT_NUM):
        """
        simulate a income phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm call %s" % phone)

    def cancel_call(self, phone=DEFAULT_NUM):
        """
        cancel phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm cancel %s" % phone)

    def accept_call(self, phone=DEFAULT_NUM):
        """
        accept phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm accept %s" % phone)

    def call(self, phone=DEFAULT_NUM):
        """
        simulate a outcome phonecall
        :param phone: str, phonenum
        :return:
        """
        call_intent = Intent(prefix='start',
                             action="android.intent.action.CALL",
                             data_uri="tel:%s" % phone)
        return self.send_intent(intent=call_intent)

    def send_sms(self, phone=DEFAULT_NUM, content=DEFAULT_CONTENT):
        """
        send a SMS
        :param phone: str, phone number of receiver
        :param content: str, content of sms
        :return:
        """
        send_sms_intent = Intent(prefix='start',
                                 action="android.intent.action.SENDTO",
                                 data_uri="sms:%s" % phone,
                                 extra_string={'sms_body': content},
                                 extra_boolean={'exit_on_sent': 'true'})
        self.send_intent(intent=send_sms_intent)
        time.sleep(2)
        self.get_adb().press('66')
        return True

    def receive_sms(self, phone=DEFAULT_NUM, content=DEFAULT_CONTENT):
        """
        receive a SMS
        :param phone: str, phone number of sender
        :param content: str, content of sms
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("sms send %s '%s'" % (phone, content))

    def set_gps(self, x, y):
        """
        set GPS positioning to x,y
        :param x: float
        :param y: float
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("geo fix %s %s" % (x, y))

    def set_continuous_gps(self, center_x, center_y, delta_x, delta_y):
        import threading
        gps_thread = threading.Thread(
            target=self.set_continuous_gps_blocked,
            args=(center_x, center_y, delta_x, delta_y))
        gps_thread.start()
        return True

    def set_continuous_gps_blocked(self, center_x, center_y, delta_x, delta_y):
        """
        simulate GPS on device via telnet
        this method is blocked
        @param center_x: x coordinate of GPS position
        @param center_y: y coordinate of GPS position
        @param delta_x: range of x coordinate
        @param delta_y: range of y coordinate
        """
        import random
        while self.is_connected:
            x = random.random() * delta_x * 2 + center_x - delta_x
            y = random.random() * delta_y * 2 + center_y - delta_y
            self.set_gps(x, y)
            time.sleep(3)

    def get_settings(self):
        """
        get device settings via adb
        """
        db_name = "/data/data/com.android.providers.settings/databases/settings.db"

        system_settings = {}
        out = self.get_adb().shell("sqlite3 %s \"select * from %s\"" % (db_name, "system"))
        out_lines = out.splitlines()
        for line in out_lines:
            segs = line.split('|')
            if len(segs) != 3:
                continue
            system_settings[segs[1]] = segs[2]

        secure_settings = {}
        out = self.get_adb().shell("sqlite3 %s \"select * from %s\"" % (db_name, "secure"))
        out_lines = out.splitlines()
        for line in out_lines:
            segs = line.split('|')
            if len(segs) != 3:
                continue
            secure_settings[segs[1]] = segs[2]

        self.settings['system'] = system_settings
        self.settings['secure'] = secure_settings
        return self.settings

    def change_settings(self, table_name, name, value):
        """
        dangerous method, by calling this, change settings.db in device
        be very careful for sql injection
        :param table_name: table name to work on, usually it is system or secure
        :param name: settings name to set
        :param value: settings value to set
        """
        db_name = "/data/data/com.android.providers.settings/databases/settings.db"

        self.get_adb().shell("sqlite3 %s \"update '%s' set value='%s' where name='%s'\""
                             % (db_name, table_name, value, name))
        return True

    def send_intent(self, intent):
        """
        send an intent to device via am (ActivityManager)
        :param intent: instance of Intent
        :return:
        """
        assert self.get_adb() is not None
        assert intent is not None
        if isinstance(intent, Intent):
            cmd = intent.get_cmd()
        else:
            cmd = intent
        return self.get_adb().shell(cmd)

    def send_event(self, event):
        """
        send one event to device
        :param event: the event to be sent
        :return:
        """
        self.logger.info("sending event: %s" % event)
        event.send(self)

    def start_app(self, app):
        """
        start an app on the device
        :param app: instance of App, or str of package name
        :return:
        """
        if isinstance(app, str):
            package_name = app
        elif isinstance(app, App):
            package_name = app.get_package_name()
            if app.get_main_activity():
                package_name += "/%s" % app.get_main_activity()
        else:
            self.logger.warning("unsupported param " + app + " with type: ", type(app))
            return
        intent = Intent(suffix=package_name)
        self.send_intent(intent)

    def get_top_activity_name(self):
        """
        Get current activity
        """
        data = self.get_adb().shell("dumpsys activity top").splitlines()
        regex = re.compile("\s*ACTIVITY ([A-Za-z0-9_.]+)/([A-Za-z0-9_.]+)")
        m = regex.search(data[1])
        if m:
            return m.group(1) + "/" + m.group(2)
        return None

    def get_service_names(self):
        """
        get current running services
        :return: list of services
        """
        services = []
        dat = self.get_adb().shell('dumpsys activity services')
        lines = dat.splitlines()
        service_re = re.compile('^.+ServiceRecord{.+ ([A-Za-z0-9_.]+)/.([A-Za-z0-9_.]+)}')

        for line in lines:
            m = service_re.search(line)
            if m:
                package = m.group(1)
                service = m.group(2)
                services.append("%s/%s" % (package, service))
        return services

    def get_focused_window_name(self):
        return self.get_adb().getFocusedWindowName()

    def get_package_path(self, package_name):
        """
        get installation path of a package (app)
        :param package_name:
        :return: package path of app in device
        """
        dat = self.get_adb().shell('pm path %s' % package_name)
        package_path_re = re.compile('^package:(.+)$')
        m = package_path_re.match(dat)
        if m:
            path = m.group(1)
            return path.strip()
        return None

    def start_activity_via_monkey(self, package):
        """
        use monkey to start activity
        @param package: package name of target activity
        """
        cmd = 'monkey'
        if package:
            cmd += ' -p %s' % package
        out = self.get_adb().shell(cmd)
        if re.search(r"(Error)|(Cannot find 'App')", out, re.IGNORECASE | re.MULTILINE):
            raise RuntimeError(out)

    def install_app(self, app):
        """
        install an app to device
        @param app: instance of App
        @return:
        """
        assert isinstance(app, App)
        subprocess.check_call(["adb", "-s", self.serial, "uninstall", app.get_package_name()],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        subprocess.check_call(["adb", "-s", self.serial, "install", app.app_path],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        package_info_file_name = "%s/dumpsys_package_%s.txt" % (self.output_dir, app.get_package_name())
        package_info_file = open(package_info_file_name, "w")
        subprocess.check_call(["adb", "-s", self.serial, "shell",
                               "dumpsys", "package", app.get_package_name()], stdout=package_info_file)
        package_info_file.close()

    def uninstall_app(self, app):
        assert isinstance(app, App)
        subprocess.check_call(["adb", "-s", self.serial, "uninstall", app.get_package_name()],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def get_app_pid(self, app):
        package = app.get_package_name()

        name2pid = {}
        ps_out = self.get_adb().shell(["ps", "-t"])
        ps_out_lines = ps_out.splitlines()
        ps_out_head = ps_out_lines[0].split()
        if ps_out_head[1] != "PID" or ps_out_head[-1] != "NAME":
            self.logger.warning("ps command output format error: %s" % ps_out_head)
        for ps_out_line in ps_out_lines[1:]:
            segs = ps_out_line.split()
            if len(segs) < 4:
                continue
            pid = int(segs[1])
            name = segs[-1]
            name2pid[name] = pid

        if package in name2pid:
            return name2pid[package]

        possible_pids = []
        for name in name2pid:
            if name.startswith(package):
                possible_pids.append(name2pid[name])
        if len(possible_pids) > 0:
            return min(possible_pids)

        return None

    def push_file(self, local_file, remote_dir="/sdcard/"):
        """
        push file/directory to target_dir
        :param local_file: path to file/directory in host machine
        :param remote_dir: path to target directory in device
        :return:
        """
        if not os.path.exists(local_file):
            self.logger.warning("push_file file does not exist: %s" % local_file)
        self.get_adb().run_cmd(["push", local_file, remote_dir])

    def pull_file(self, remote_file, local_file):
        self.get_adb().run_cmd(["pull", remote_file, local_file])

    def take_screenshot(self):
        image = None

        received = self.get_adb().shell("screencap -p").replace("\r\n", "\n")
        import StringIO
        stream = StringIO.StringIO(received)

        try:
            from PIL import Image
            image = Image.open(stream)
        except IOError as e:
            self.logger.warning("exception in take_screenshot: %s" % e)
        return image

    def get_current_state(self):
        self.logger.info("getting current device state...")
        current_state = None
        try:
            view_client_views = self.dump_views()
            foreground_activity = self.get_top_activity_name()
            background_services = self.get_service_names()
            screenshot = self.take_screenshot()
            self.logger.info("finish getting current device state...")
            current_state = DeviceState(self,
                                        view_client_views=view_client_views,
                                        foreground_activity=foreground_activity,
                                        background_services=background_services,
                                        screenshot=screenshot)
        except Exception as e:
            self.logger.warning("exception in get_current_state: %s" % e)
            import traceback
            traceback.print_exc()
        return current_state

    def view_touch(self, x, y):
        self.get_adb().touch(x, y)

    def view_long_touch(self, x, y, duration=2000):
        """
        Long touches at (x, y)
        @param duration: duration in ms
        This workaround was suggested by U{HaMi<http://stackoverflow.com/users/2571957/hami>}
        """
        self.get_adb().longTouch(x, y, duration)

    def view_drag(self, (x0, y0), (x1, y1), duration):
        """
        Sends drag event n PX (actually it's using C{input swipe} command.
        @param (x0, y0): starting point in PX
        @param (x1, y1): ending point in PX
        @param duration: duration of the event in ms
        """
        self.get_adb().drag((x0, y0), (x1, y1), duration)
Exemplo n.º 5
0
class Device(object):
    """
    this class describes a connected device
    """
    def __init__(self, device_serial, is_emulator=True, output_dir=None):
        """
        create a device
        :param device_serial: serial number of target device
        :param is_emulator: boolean, type of device, True for emulator, False for real device
        :return:
        """
        self.logger = logging.getLogger('Device')

        self.serial = device_serial
        # is_emulator 0 for real device, 1 for emulator
        self.is_emulator = is_emulator
        self.adb = None
        self.telnet = None
        self.monkeyrunner = None
        self.view_client = None
        self.settings = {}
        self.display_info = None

        self.output_dir = output_dir
        if self.output_dir is None:
            self.output_dir = os.path.abspath("droidbot_out")
        if not os.path.exists(self.output_dir):
            os.mkdir(self.output_dir)

        if self.is_emulator:
            self.adb_enabled = True
            self.telnet_enabled = True
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True
        else:
            self.adb_enabled = True
            self.telnet_enabled = False
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True

        self.is_connected = False
        self.connect()
        self.get_display_info()
        self.logcat = self.redirect_logcat()
        from state_monitor import StateMonitor
        self.state_monitor = StateMonitor(device=self)
        self.state_monitor.start()
        # assert self.display_info is not None
        # self.check_connectivity()
        # print self.is_emulator, self.host, self.port

    def redirect_logcat(self, output_dir=None):
        if output_dir is None:
            output_dir = self.output_dir
        logcat_file = open("%s/logcat.log" % output_dir, "w")
        import subprocess
        subprocess.check_call(["adb", "-s", self.serial, "logcat", "-c"])
        logcat = subprocess.Popen(["adb", "-s", self.serial, "logcat", "-v", "threadtime"],
                                  stdin=subprocess.PIPE,
                                  stdout=logcat_file)
        return logcat

    def check_connectivity(self):
        """
        check if the device is available
        :return: Ture for available, False for not
        """
        try:
            # try connecting to device
            self.logger.info("checking connectivity...")
            result = True

            if self.adb_enabled and self.adb and self.adb.checkConnected():
                self.logger.info("ADB is connected")
            else:
                self.logger.warning("ADB is not connected")
                result = False

            if self.telnet_enabled and self.telnet and self.telnet.check_connectivity():
                self.logger.info("Telnet is connected")
            else:
                self.logger.warning("Telnet is not connected")
                result = False

            if self.monkeyrunner_enabled and self.monkeyrunner and self.monkeyrunner.check_connectivity():
                self.logger.info("monkeyrunner is connected")
            else:
                self.logger.warning("monkeyrunner is not connected")
                result = False

            if self.view_client_enabled and self.view_client:
                self.logger.info("view_client is connected")
            else:
                self.logger.warning("view_client is not connected")
                result = False
            return result
        except:
            return False

    def wait_for_device(self):
        """
        wait until the device is fully booted
        :return:
        """
        self.logger.info("waiting for device")
        try:
            subprocess.check_call(["adb", "-s", self.serial, "wait-for-device"])
            while True:
                out = subprocess.check_output(["adb", "-s", self.serial, "shell",
                                               "getprop", "init.svc.bootanim"]).split()[0]
                if out == "stopped":
                    break
                time.sleep(3)
        except:
            self.logger.warning("error waiting for device")

    def connect(self):
        """
        connect this device via adb, telnet and monkeyrunner
        :return:
        """
        try:
            # wait for emulator to start
            self.wait_for_device()
            if self.adb_enabled:
                self.get_adb()

            if self.telnet_enabled:
                self.get_telnet()

            if self.monkeyrunner_enabled:
                self.get_monkeyrunner()

            if self.view_client_enabled:
                self.get_view_client()

            time.sleep(3)
            self.is_connected = True

        except connection.TelnetException:
            self.logger.warning("Cannot connect to telnet.")

    def disconnect(self):
        """
        disconnect current device
        :return:
        """
        self.is_connected = False
        if self.adb:
            self.adb.close()
        if self.telnet:
            self.telnet.disconnect()
        if self.monkeyrunner:
            self.monkeyrunner.disconnect()
        if self.view_client:
            self.view_client = None
        self.logcat.terminate()
        self.state_monitor.stop()

    def get_telnet(self):
        """
        get telnet connection of the device
        note that only emulator have telnet connection
        """
        if self.telnet_enabled and self.telnet is None:
            self.telnet = connection.TelnetConsole(self)
        return self.telnet

    def get_adb(self):
        """
        get adb connection of the device
        """
        if self.adb_enabled and self.adb is None:
            # use adbclient class in com.dtmilano.adb.adbclient
            self.adb, self.serial = ViewClient.connectToDeviceOrExit(verbose=True, serialno=self.serial)
        return self.adb

    def get_monkeyrunner(self):
        """
        get monkeyrunner connection of the device
        :return:
        """
        if self.monkeyrunner_enabled and self.monkeyrunner is None:
            self.monkeyrunner = connection.MonkeyRunner(self)
        return self.monkeyrunner

    def get_view_client(self):
        """
        get view_client connection of the device
        :return:
        """
        if self.view_client_enabled and self.view_client is None:
            kwargs = {'startviewserver': True,
                      'autodump': False,
                      'forceviewserveruse': True,
                      'ignoreuiautomatorkilled': True}
            self.view_client = ViewClient(self.adb, self.serial, **kwargs)
        return self.view_client

    def is_foreground(self, app):
        """
        check if app is in foreground of device
        :param app: App
        :return: boolean
        """
        if isinstance(app, str):
            package_name = app
        elif isinstance(app, App):
            package_name = app.get_package_name()
        else:
            return False

        focused_window_name = self.get_adb().getTopActivityName()
        if focused_window_name is None:
            return False
        return focused_window_name.startswith(package_name)

    def get_display_info(self):
        """
        get device display infomation, including width, height, and density
        :return: dict, display_info
        """
        if self.display_info is None:
            self.display_info = self.get_adb().getDisplayInfo()
        return self.display_info

    def device_prepare(self):
        """
        unlock screen
        skip first-use tutorials
        etc
        :return:
        """
        assert self.get_adb() is not None
        assert self.get_view_client() is not None

        # unlock screen
        self.get_adb().unlock()

        # DOWN skip first-use turorials, we don't have to

    def add_env(self, env):
        """
        set env to the device
        :param env: instance of AppEnv
        """
        self.logger.info("deploying env: %s" % env)
        env.deploy(self)

    def add_contact(self, contact_data):
        """
        add a contact to device
        :param contact_data: dict of contact, should have keys like name, phone, email
        :return:
        """
        assert self.get_adb() is not None
        contact_intent = Intent(prefix="start",
                                action="android.intent.action.INSERT",
                                mime_type="vnd.android.cursor.dir/contact",
                                extra_string=contact_data)
        self.send_intent(intent=contact_intent)
        time.sleep(2)
        self.get_adb().press("BACK")
        time.sleep(2)
        self.get_adb().press("BACK")
        return True

    def receive_call(self, phone=DEFAULT_NUM):
        """
        simulate a income phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm call %s" % phone)

    def cancel_call(self, phone=DEFAULT_NUM):
        """
        cancel phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm cancel %s" % phone)

    def accept_call(self, phone=DEFAULT_NUM):
        """
        accept phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm accept %s" % phone)

    def call(self, phone=DEFAULT_NUM):
        """
        simulate a outcome phonecall
        :param phone: str, phonenum
        :return:
        """
        call_intent = Intent(prefix='start',
                             action="android.intent.action.CALL",
                             data_uri="tel:%s" % phone)
        return self.send_intent(intent=call_intent)

    def send_sms(self, phone=DEFAULT_NUM, content=DEFAULT_CONTENT):
        """
        send a SMS
        :param phone: str, phone number of receiver
        :param content: str, content of sms
        :return:
        """
        send_sms_intent = Intent(prefix='start',
                                 action="android.intent.action.SENDTO",
                                 data_uri="sms:%s" % phone,
                                 extra_string={'sms_body': content},
                                 extra_boolean={'exit_on_sent': 'true'})
        self.send_intent(intent=send_sms_intent)
        time.sleep(2)
        self.get_adb().press('66')
        return True

    def receive_sms(self, phone=DEFAULT_NUM, content=DEFAULT_CONTENT):
        """
        receive a SMS
        :param phone: str, phone number of sender
        :param content: str, content of sms
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("sms send %s '%s'" % (phone, content))

    def set_gps(self, x, y):
        """
        set GPS positioning to x,y
        :param x: float
        :param y: float
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("geo fix %s %s" % (x, y))

    def set_continuous_gps(self, center_x, center_y, delta_x, delta_y):
        import threading
        gps_thread = threading.Thread(
            target=self.set_continuous_gps_blocked,
            args=(center_x, center_y, delta_x, delta_y))
        gps_thread.start()
        return True

    def set_continuous_gps_blocked(self, center_x, center_y, delta_x, delta_y):
        """
        simulate GPS on device via telnet
        this method is blocked
        @param center_x: x coordinate of GPS position
        @param center_y: y coordinate of GPS position
        @param delta_x: range of x coordinate
        @param delta_y: range of y coordinate
        """
        import random
        while self.is_connected:
            x = random.random() * delta_x * 2 + center_x - delta_x
            y = random.random() * delta_y * 2 + center_y - delta_y
            self.set_gps(x, y)
            time.sleep(3)

    def get_settings(self):
        """
        get device settings via adb
        """
        db_name = "/data/data/com.android.providers.settings/databases/settings.db"

        system_settings = {}
        out = self.get_adb().shell("sqlite3 %s \"select * from %s\"" % (db_name, "system"))
        out_lines = out.splitlines()
        for line in out_lines:
            segs = line.split('|')
            if len(segs) != 3:
                continue
            system_settings[segs[1]] = segs[2]

        secure_settings = {}
        out = self.get_adb().shell("sqlite3 %s \"select * from %s\"" % (db_name, "secure"))
        out_lines = out.splitlines()
        for line in out_lines:
            segs = line.split('|')
            if len(segs) != 3:
                continue
            secure_settings[segs[1]] = segs[2]

        self.settings['system'] = system_settings
        self.settings['secure'] = secure_settings
        return self.settings

    def change_settings(self, table_name, name, value):
        """
        dangerous method, by calling this, change settings.db in device
        be very careful for sql injection
        :param table_name: table name to work on, usually it is system or secure
        :param name: settings name to set
        :param value: settings value to set
        """
        db_name = "/data/data/com.android.providers.settings/databases/settings.db"

        self.get_adb().shell("sqlite3 %s \"update '%s' set value='%s' where name='%s'\""
                             % (db_name, table_name, value, name))
        return True

    def send_intent(self, intent):
        """
        send an intent to device via am (ActivityManager)
        :param intent: instance of Intent
        :return:
        """
        assert self.get_adb() is not None
        assert intent is not None
        cmd = intent.get_cmd()
        return self.get_adb().shell(cmd)

    def send_event(self, event):
        """
        send one event to device
        :param event: the event to be sent
        :return:
        """
        self.logger.info("sending event: %s" % event)
        event.send(self)

    def start_app(self, app):
        """
        start an app on the device
        :param app: instance of App, or str of package name
        :return:
        """
        if isinstance(app, str):
            package_name = app
        elif isinstance(app, App):
            package_name = app.get_package_name()
            if app.get_main_activity():
                package_name += "/%s" % app.get_main_activity()
        else:
            self.logger.warning("unsupported param " + app + " with type: ", type(app))
            return
        intent = Intent(suffix=package_name)
        self.send_intent(intent)

    def get_service_names(self):
        """
        get current running services
        :return: list of services
        """
        services = []
        dat = self.get_adb().shell('dumpsys activity services')
        lines = dat.splitlines()
        service_re = re.compile('^.+ServiceRecord{.+ ([A-Za-z0-9_.]+)/.([A-Za-z0-9_.]+)}')

        for line in lines:
            m = service_re.search(line)
            if m:
                package = m.group(1)
                service = m.group(2)
                services.append("%s/%s" % (package, service))
        return services

    def get_package_path(self, package_name):
        """
        get installation path of a package (app)
        :param package_name:
        :return: package path of app in device
        """
        dat = self.get_adb().shell('pm path %s' % package_name)
        package_path_re = re.compile('^package:(.+)$')
        m = package_path_re.match(dat)
        if m:
            path = m.group(1)
            return path.strip()
        return None

    def start_activity_via_monkey(self, package):
        """
        use monkey to start activity
        @param package: package name of target activity
        """
        cmd = 'monkey'
        if package:
            cmd += ' -p %s' % package
        out = self.get_adb().shell(cmd)
        if re.search(r"(Error)|(Cannot find 'App')", out, re.IGNORECASE | re.MULTILINE):
            raise RuntimeError(out)

    def install_app(self, app):
        """
        install an app to device
        @param app: instance of App
        @return:
        """
        assert isinstance(app, App)
        subprocess.check_call(["adb", "-s", self.serial, "uninstall", app.get_package_name()],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        subprocess.check_call(["adb", "-s", self.serial, "install", app.app_path],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        package_info_file_name = "%s/dumpsys_package_%s.txt" % (self.output_dir, app.get_package_name())
        package_info_file = open(package_info_file_name, "w")
        subprocess.check_call(["adb", "-s", self.serial, "shell",
                               "dumpsys", "package", app.get_package_name()], stdout=package_info_file)
        package_info_file.close()

    def uninstall_app(self, app):
        assert isinstance(app, App)
        subprocess.check_call(["adb", "-s", self.serial, "uninstall", app.get_package_name()],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def get_current_state(self):
        self.logger.info("getting current device state...")
        try:
            view_client_views = self.get_view_client().dump()
            foreground_activity = self.get_adb().getTopActivityName()
            background_services = self.get_service_names()
            snapshot = self.get_adb().takeSnapshot(reconnect=True)
            self.logger.info("finish getting current device state...")
            return DeviceState(self,
                               view_client_views=view_client_views,
                               foreground_activity=foreground_activity,
                               background_services=background_services,
                               snapshot=snapshot)
        except Exception as e:
            self.logger.warning(e)
            return None
Exemplo n.º 6
0
class Device(object):
    """
    this class describes a connected device
    """
    def __init__(self, device_serial, is_emulator=True, output_dir=None):
        """
        create a device
        :param device_serial: serial number of target device
        :param is_emulator: boolean, type of device, True for emulator, False for real device
        :return:
        """
        self.logger = logging.getLogger('Device')

        self.serial = device_serial
        # is_emulator 0 for real device, 1 for emulator
        self.is_emulator = is_emulator
        self.adb = None
        self.telnet = None
        self.monkeyrunner = None
        self.view_client = None
        self.settings = {}
        self.display_info = None

        self.output_dir = output_dir
        if self.output_dir is None:
            self.output_dir = os.path.abspath("droidbot_out")
        if not os.path.exists(self.output_dir):
            os.mkdir(self.output_dir)

        if self.is_emulator:
            self.adb_enabled = True
            self.telnet_enabled = True
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True
        else:
            self.adb_enabled = True
            self.telnet_enabled = False
            self.monkeyrunner_enabled = False
            self.view_client_enabled = True

        self.is_connected = False
        self.connect()
        self.get_display_info()
        self.logcat = self.redirect_logcat()
        from state_monitor import StateMonitor
        self.state_monitor = StateMonitor(device=self)
        self.state_monitor.start()
        # assert self.display_info is not None
        # self.check_connectivity()
        # print self.is_emulator, self.host, self.port

    def redirect_logcat(self, output_dir=None):
        if output_dir is None:
            output_dir = self.output_dir
        logcat_file = open("%s/logcat.log" % output_dir, "w")
        import subprocess
        subprocess.check_call(["adb", "-s", self.serial, "logcat", "-c"])
        logcat = subprocess.Popen(
            ["adb", "-s", self.serial, "logcat", "-v", "threadtime"],
            stdin=subprocess.PIPE,
            stdout=logcat_file)
        return logcat

    def check_connectivity(self):
        """
        check if the device is available
        :return: Ture for available, False for not
        """
        try:
            # try connecting to device
            self.logger.info("checking connectivity...")
            result = True

            if self.adb_enabled and self.adb and self.adb.checkConnected():
                self.logger.info("ADB is connected")
            else:
                self.logger.warning("ADB is not connected")
                result = False

            if self.telnet_enabled and self.telnet and self.telnet.check_connectivity(
            ):
                self.logger.info("Telnet is connected")
            else:
                self.logger.warning("Telnet is not connected")
                result = False

            if self.monkeyrunner_enabled and self.monkeyrunner and self.monkeyrunner.check_connectivity(
            ):
                self.logger.info("monkeyrunner is connected")
            else:
                self.logger.warning("monkeyrunner is not connected")
                result = False

            if self.view_client_enabled and self.view_client:
                self.logger.info("view_client is connected")
            else:
                self.logger.warning("view_client is not connected")
                result = False
            return result
        except:
            return False

    def wait_for_device(self):
        """
        wait until the device is fully booted
        :return:
        """
        self.logger.info("waiting for device")
        try:
            subprocess.check_call(
                ["adb", "-s", self.serial, "wait-for-device"])
            while True:
                out = subprocess.check_output([
                    "adb", "-s", self.serial, "shell", "getprop",
                    "init.svc.bootanim"
                ]).split()[0]
                if out == "stopped":
                    break
                time.sleep(3)
        except:
            self.logger.warning("error waiting for device")

    def connect(self):
        """
        connect this device via adb, telnet and monkeyrunner
        :return:
        """
        try:
            # wait for emulator to start
            self.wait_for_device()
            if self.adb_enabled:
                self.get_adb()

            if self.telnet_enabled:
                self.get_telnet()

            if self.monkeyrunner_enabled:
                self.get_monkeyrunner()

            if self.view_client_enabled:
                self.get_view_client()

            time.sleep(3)
            self.is_connected = True

        except connection.TelnetException:
            self.logger.warning("Cannot connect to telnet.")

    def disconnect(self):
        """
        disconnect current device
        :return:
        """
        self.is_connected = False
        if self.adb:
            self.adb.close()
        if self.telnet:
            self.telnet.disconnect()
        if self.monkeyrunner:
            self.monkeyrunner.disconnect()
        if self.view_client:
            self.view_client = None
        self.logcat.terminate()
        self.state_monitor.stop()

    def get_telnet(self):
        """
        get telnet connection of the device
        note that only emulator have telnet connection
        """
        if self.telnet_enabled and self.telnet is None:
            self.telnet = connection.TelnetConsole(self)
        return self.telnet

    def get_adb(self):
        """
        get adb connection of the device
        """
        if self.adb_enabled and self.adb is None:
            # use adbclient class in com.dtmilano.adb.adbclient
            self.adb, self.serial = ViewClient.connectToDeviceOrExit(
                verbose=True, serialno=self.serial)
        return self.adb

    def get_monkeyrunner(self):
        """
        get monkeyrunner connection of the device
        :return:
        """
        if self.monkeyrunner_enabled and self.monkeyrunner is None:
            self.monkeyrunner = connection.MonkeyRunner(self)
        return self.monkeyrunner

    def get_view_client(self):
        """
        get view_client connection of the device
        :return:
        """
        if self.view_client_enabled and self.view_client is None:
            kwargs = {
                'startviewserver': True,
                'autodump': False,
                'forceviewserveruse': True,
                'ignoreuiautomatorkilled': True
            }
            self.view_client = ViewClient(self.adb, self.serial, **kwargs)
        return self.view_client

    def is_foreground(self, app):
        """
        check if app is in foreground of device
        :param app: App
        :return: boolean
        """
        if isinstance(app, str):
            package_name = app
        elif isinstance(app, App):
            package_name = app.get_package_name()
        else:
            return False

        focused_window_name = self.get_adb().getTopActivityName()
        if focused_window_name is None:
            return False
        return focused_window_name.startswith(package_name)

    def get_display_info(self):
        """
        get device display infomation, including width, height, and density
        :return: dict, display_info
        """
        if self.display_info is None:
            self.display_info = self.get_adb().getDisplayInfo()
        return self.display_info

    def device_prepare(self):
        """
        unlock screen
        skip first-use tutorials
        etc
        :return:
        """
        assert self.get_adb() is not None
        assert self.get_view_client() is not None

        # unlock screen
        self.get_adb().unlock()

        # DOWN skip first-use turorials, we don't have to

    def add_env(self, env):
        """
        set env to the device
        :param env: instance of AppEnv
        """
        self.logger.info("deploying env: %s" % env)
        env.deploy(self)

    def add_contact(self, contact_data):
        """
        add a contact to device
        :param contact_data: dict of contact, should have keys like name, phone, email
        :return:
        """
        assert self.get_adb() is not None
        contact_intent = Intent(prefix="start",
                                action="android.intent.action.INSERT",
                                mime_type="vnd.android.cursor.dir/contact",
                                extra_string=contact_data)
        self.send_intent(intent=contact_intent)
        time.sleep(2)
        self.get_adb().press("BACK")
        time.sleep(2)
        self.get_adb().press("BACK")
        return True

    def receive_call(self, phone=DEFAULT_NUM):
        """
        simulate a income phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm call %s" % phone)

    def cancel_call(self, phone=DEFAULT_NUM):
        """
        cancel phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm cancel %s" % phone)

    def accept_call(self, phone=DEFAULT_NUM):
        """
        accept phonecall
        :param phone: str, phonenum
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("gsm accept %s" % phone)

    def call(self, phone=DEFAULT_NUM):
        """
        simulate a outcome phonecall
        :param phone: str, phonenum
        :return:
        """
        call_intent = Intent(prefix='start',
                             action="android.intent.action.CALL",
                             data_uri="tel:%s" % phone)
        return self.send_intent(intent=call_intent)

    def send_sms(self, phone=DEFAULT_NUM, content=DEFAULT_CONTENT):
        """
        send a SMS
        :param phone: str, phone number of receiver
        :param content: str, content of sms
        :return:
        """
        send_sms_intent = Intent(prefix='start',
                                 action="android.intent.action.SENDTO",
                                 data_uri="sms:%s" % phone,
                                 extra_string={'sms_body': content},
                                 extra_boolean={'exit_on_sent': 'true'})
        self.send_intent(intent=send_sms_intent)
        time.sleep(2)
        self.get_adb().press('66')
        return True

    def receive_sms(self, phone=DEFAULT_NUM, content=DEFAULT_CONTENT):
        """
        receive a SMS
        :param phone: str, phone number of sender
        :param content: str, content of sms
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("sms send %s '%s'" % (phone, content))

    def set_gps(self, x, y):
        """
        set GPS positioning to x,y
        :param x: float
        :param y: float
        :return:
        """
        assert self.get_telnet() is not None
        return self.get_telnet().run_cmd("geo fix %s %s" % (x, y))

    def set_continuous_gps(self, center_x, center_y, delta_x, delta_y):
        import threading
        gps_thread = threading.Thread(target=self.set_continuous_gps_blocked,
                                      args=(center_x, center_y, delta_x,
                                            delta_y))
        gps_thread.start()
        return True

    def set_continuous_gps_blocked(self, center_x, center_y, delta_x, delta_y):
        """
        simulate GPS on device via telnet
        this method is blocked
        @param center_x: x coordinate of GPS position
        @param center_y: y coordinate of GPS position
        @param delta_x: range of x coordinate
        @param delta_y: range of y coordinate
        """
        import random
        while self.is_connected:
            x = random.random() * delta_x * 2 + center_x - delta_x
            y = random.random() * delta_y * 2 + center_y - delta_y
            self.set_gps(x, y)
            time.sleep(3)

    def get_settings(self):
        """
        get device settings via adb
        """
        db_name = "/data/data/com.android.providers.settings/databases/settings.db"

        system_settings = {}
        out = self.get_adb().shell("sqlite3 %s \"select * from %s\"" %
                                   (db_name, "system"))
        out_lines = out.splitlines()
        for line in out_lines:
            segs = line.split('|')
            if len(segs) != 3:
                continue
            system_settings[segs[1]] = segs[2]

        secure_settings = {}
        out = self.get_adb().shell("sqlite3 %s \"select * from %s\"" %
                                   (db_name, "secure"))
        out_lines = out.splitlines()
        for line in out_lines:
            segs = line.split('|')
            if len(segs) != 3:
                continue
            secure_settings[segs[1]] = segs[2]

        self.settings['system'] = system_settings
        self.settings['secure'] = secure_settings
        return self.settings

    def change_settings(self, table_name, name, value):
        """
        dangerous method, by calling this, change settings.db in device
        be very careful for sql injection
        :param table_name: table name to work on, usually it is system or secure
        :param name: settings name to set
        :param value: settings value to set
        """
        db_name = "/data/data/com.android.providers.settings/databases/settings.db"

        self.get_adb().shell(
            "sqlite3 %s \"update '%s' set value='%s' where name='%s'\"" %
            (db_name, table_name, value, name))
        return True

    def send_intent(self, intent):
        """
        send an intent to device via am (ActivityManager)
        :param intent: instance of Intent
        :return:
        """
        assert self.get_adb() is not None
        assert intent is not None
        cmd = intent.get_cmd()
        return self.get_adb().shell(cmd)

    def send_event(self, event):
        """
        send one event to device
        :param event: the event to be sent
        :return:
        """
        self.logger.info("sending event: %s" % event)
        event.send(self)

    def start_app(self, app):
        """
        start an app on the device
        :param app: instance of App, or str of package name
        :return:
        """
        if isinstance(app, str):
            package_name = app
        elif isinstance(app, App):
            package_name = app.get_package_name()
            if app.get_main_activity():
                package_name += "/%s" % app.get_main_activity()
        else:
            self.logger.warning("unsupported param " + app + " with type: ",
                                type(app))
            return
        intent = Intent(suffix=package_name)
        self.send_intent(intent)

    def get_service_names(self):
        """
        get current running services
        :return: list of services
        """
        services = []
        dat = self.get_adb().shell('dumpsys activity services')
        lines = dat.splitlines()
        service_re = re.compile(
            '^.+ServiceRecord{.+ ([A-Za-z0-9_.]+)/.([A-Za-z0-9_.]+)}')

        for line in lines:
            m = service_re.search(line)
            if m:
                package = m.group(1)
                service = m.group(2)
                services.append("%s/%s" % (package, service))
        return services

    def get_package_path(self, package_name):
        """
        get installation path of a package (app)
        :param package_name:
        :return: package path of app in device
        """
        dat = self.get_adb().shell('pm path %s' % package_name)
        package_path_re = re.compile('^package:(.+)$')
        m = package_path_re.match(dat)
        if m:
            path = m.group(1)
            return path.strip()
        return None

    def start_activity_via_monkey(self, package):
        """
        use monkey to start activity
        @param package: package name of target activity
        """
        cmd = 'monkey'
        if package:
            cmd += ' -p %s' % package
        out = self.get_adb().shell(cmd)
        if re.search(r"(Error)|(Cannot find 'App')", out,
                     re.IGNORECASE | re.MULTILINE):
            raise RuntimeError(out)

    def install_app(self, app):
        """
        install an app to device
        @param app: instance of App
        @return:
        """
        assert isinstance(app, App)
        subprocess.check_call(
            ["adb", "-s", self.serial, "uninstall",
             app.get_package_name()],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        subprocess.check_call(
            ["adb", "-s", self.serial, "install", app.app_path],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)

        package_info_file_name = "%s/dumpsys_package_%s.txt" % (
            self.output_dir, app.get_package_name())
        package_info_file = open(package_info_file_name, "w")
        subprocess.check_call([
            "adb", "-s", self.serial, "shell", "dumpsys", "package",
            app.get_package_name()
        ],
                              stdout=package_info_file)
        package_info_file.close()

    def uninstall_app(self, app):
        assert isinstance(app, App)
        subprocess.check_call(
            ["adb", "-s", self.serial, "uninstall",
             app.get_package_name()],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)

    def get_current_state(self):
        self.logger.info("getting current device state...")
        try:
            view_client_views = self.get_view_client().dump()
            foreground_activity = self.get_adb().getTopActivityName()
            background_services = self.get_service_names()
            snapshot = self.get_adb().takeSnapshot(reconnect=True)
            self.logger.info("finish getting current device state...")
            return DeviceState(self,
                               view_client_views=view_client_views,
                               foreground_activity=foreground_activity,
                               background_services=background_services,
                               snapshot=snapshot)
        except Exception as e:
            self.logger.warning(e)
            return None
def run_test(load_balancer, workload, debug):
    sm = StateMonitor()
    create_count = 0  # Keeps track of how many creates went through
    remove_count = 0  # Keeps track of how many removes went through
    with open(workload) as f:
        # returns (None, sm.failed) if there are any errors
        # otherwise runs through the workload
        for line in tqdm.tqdm(f, total=get_num_lines(workload)):

            # Parse command
            command, arg = line.split()

            if command == 'create':

                if debug: print('\n{} {}'.format(command, arg))

                try:
                    check_valid(sm, load_balancer.shards, debug)
                except Error as e:
                    print('\nbefore create {}\n{}'.format(create_count, e))
                    return None, sm.failed

                load_balancer.add_shard(arg)

                try:
                    check_valid(sm, load_balancer.shards, debug)
                except Error as e:
                    print('\nafter create {}\n{}'.format(create_count, e))
                    return None, sm.failed

                create_count += 1

            elif command == 'remove':

                if debug: print('\n{} {}'.format(command, arg))

                try:
                    check_valid(sm, load_balancer.shards, debug)
                except Error as e:
                    print('\nbefore remove {}\n{}'.format(create_count, e))
                    return None, sm.failed

                load_balancer.remove_shard(arg)

                try:
                    check_valid(sm, load_balancer.shards, debug)
                except Error as e:
                    print('\nafter remove {}\n{}'.format(create_count, e))
                    return None, sm.failed

                remove_count += 1

            elif command == 'put':

                load_balancer.put(arg)
                sm.put(arg)

        # Final check
        try:
            check_valid(sm, load_balancer.shards, debug)
        except Error as e:
            print('\nat the end\n{}'.format(e))
            return None, sm.failed

        stats = sm.get_stats(load_balancer.shards, debug)
        return stats, sm.failed
class MotionCommandCoordinator(object):
    def __init__(self, action_server):
        # action server for getting requests from AI
        self._action_server = action_server

        # current state of motion coordinator
        self._task = None
        self._first_task_seen = False

        # used for timeouts & extrapolating
        self._time_of_last_task = None
        self._timeout_vel_sent = False

        # to keep things thread safe
        self._lock = threading.RLock()

        # handles monitoring of state of drone
        self._state_monitor = StateMonitor()

        # handles communicating between tasks and LLM
        self._task_command_handler = TaskCommandHandler()

        self._idle_obstacle_avoider = IdleObstacleAvoider()
        self._avoid_magnitude = rospy.get_param("~obst_avoid_magnitude")
        self._kickout_distance = rospy.get_param('~kickout_distance')
        self._new_task_distance = rospy.get_param('~new_task_distance')
        self._safe_distance = rospy.get_param('~safe_distance')

        # safety
        self._safety_client = SafetyClient('motion_command_coordinator')
        self._safety_land_complete = False
        self._safety_land_requested = False

        # Create action client to request a safety landing
        self._action_client = actionlib.SimpleActionClient(
            "motion_planner_server", QuadMoveAction)
        try:
            # update rate for motion coordinator
            self._update_rate = rospy.get_param('~update_rate')
            # task timeout values
            self._task_timeout = rospy.Duration(
                rospy.get_param('~task_timeout'))
            # startup timeout
            self._startup_timeout = rospy.Duration(
                rospy.get_param('~startup_timeout'))
        except KeyError as e:
            rospy.logerr('Could not lookup a parameter for motion coordinator')
            raise

    def run(self):
        # rate limiting of updates of motion coordinator
        rate = rospy.Rate(self._update_rate)

        # waiting for dependencies to be ready
        while rospy.Time.now() == rospy.Time(0) and not rospy.is_shutdown():
            rospy.sleep(0.005)
        if rospy.is_shutdown():
            raise rospy.ROSInterruptException()
        start_time = rospy.Time.now()
        if not self._action_client.wait_for_server(self._startup_timeout):
            raise IARCFatalSafetyException(
                'Motion Coordinator could not initialize action client')
        self._state_monitor.wait_until_ready(self._startup_timeout -
                                             (rospy.Time.now() - start_time))
        self._task_command_handler.wait_until_ready(self._startup_timeout -
                                                    (rospy.Time.now() -
                                                     start_time))
        self._idle_obstacle_avoider.wait_until_ready(self._startup_timeout -
                                                     (rospy.Time.now() -
                                                      start_time))
        AbstractTask().topic_buffer.wait_until_ready(self._startup_timeout -
                                                     (rospy.Time.now() -
                                                      start_time))

        # forming bond with safety client
        if not self._safety_client.form_bond():
            raise IARCFatalSafetyException(
                'Motion Coordinator could not form bond with safety client')

        while not rospy.is_shutdown():
            with self._lock:
                # Exit immediately if fatal
                if self._safety_client.is_fatal_active():
                    raise IARCFatalSafetyException(
                        'Safety Client is fatal active')
                elif self._safety_land_complete:
                    return

                # Land if put into safety mode
                if self._safety_client.is_safety_active(
                ) and not self._safety_land_requested:
                    # Request landing
                    goal = QuadMoveGoal(movement_type="land", preempt=True)
                    self._action_client.send_goal(
                        goal, done_cb=self._safety_task_complete_callback)
                    rospy.logwarn(
                        'motion coordinator attempting to execute safety land')
                    self._safety_land_requested = True
                    self._state_monitor.signal_safety_active()

                # set the time of last task to now if we have not seen a task yet
                if not self._first_task_seen:
                    self._time_of_last_task = rospy.Time.now()
                    self._first_task_seen = True

                closest_obstacle_dist = self._idle_obstacle_avoider.get_distance_to_obstacle(
                )

                if self._task is None and self._action_server.has_new_task():
                    new_task = self._action_server.get_new_task()

                    if not self._state_monitor.check_transition(new_task):
                        rospy.logerr(
                            'Illegal task transition request requested in motion coordinator. Aborting requested task.'
                        )
                        self._action_server.set_aborted()
                    elif not closest_obstacle_dist >= self._new_task_distance:
                        rospy.logerr(
                            'Attempt to start task too close to obstacle.' +
                            ' Aborting requested task.')
                        self._action_server.set_aborted()
                    else:
                        self._time_of_last_task = None
                        self._task = new_task
                        self._task_command_handler.new_task(
                            new_task, self._get_current_transition())

                if self._task is not None:
                    task_canceled = False
                    if self._action_server.is_canceled():
                        task_canceled = self._task_command_handler.cancel_task(
                        )

                    if not task_canceled and closest_obstacle_dist < self._kickout_distance:
                        # Abort current task
                        task_canceled = self._task_command_handler.abort_task(
                            'Too close to obstacle')

                    # if canceling task did not result in an error
                    if not task_canceled:
                        self._task_command_handler.run()

                    task_state = self._task_command_handler.get_state()

                    # handles state of task, motion coordinator, and action server
                    if isinstance(task_state, task_states.TaskCanceled):
                        self._action_server.set_canceled()
                        rospy.logwarn('Task was canceled')
                        self._task = None
                    elif isinstance(task_state, task_states.TaskAborted):
                        rospy.logwarn('Task aborted with: %s', task_state.msg)
                        self._action_server.set_aborted()
                        self._task = None
                    elif isinstance(task_state, task_states.TaskFailed):
                        rospy.logwarn('Task failed with: %s', task_state.msg)
                        self._action_server.set_succeeded(False)
                        self._task = None
                    elif isinstance(task_state, task_states.TaskDone):
                        self._action_server.set_succeeded(True)
                        self._task = None
                    elif not isinstance(task_state, task_states.TaskRunning):
                        rospy.logerr(
                            "Invalid task state returned, aborting task")
                        self._action_server.set_aborted()
                        self._task = None
                        task_state = task_states.TaskAborted(
                            msg='Invalid task state returned')

                    # as soon as we set a task to None, start time
                    # and send ending state to State Monitor
                    if self._task is None:
                        self._time_of_last_task = rospy.Time.now()
                        self._timeout_vel_sent = False
                        self._state_monitor.set_last_task_end_state(task_state)
                # No task is running, run obstacle avoider
                else:
                    vel = AbstractTask.topic_buffer.get_linear_motion_profile_generator(
                    ).expected_point_at_time(
                        rospy.Time.now()).motion_point.twist.linear
                    vel_vec_2d = np.array([vel.x, vel.y], dtype=np.float)
                    avoid_vector, acceleration = self._idle_obstacle_avoider.get_safest(
                        vel_vec_2d)
                    avoid_twist = TwistStamped()
                    avoid_twist.header.stamp = rospy.Time.now()
                    avoid_twist.twist.linear.x = avoid_vector[0]
                    avoid_twist.twist.linear.y = avoid_vector[1]
                    self._task_command_handler.send_timeout(
                        avoid_twist, acceleration=acceleration)
                    rospy.logwarn_throttle(
                        1.0, 'Task running timeout. Running obstacle avoider')

            rate.sleep()

    # fills out the Intermediary State for the task
    def _get_current_transition(self):
        state = TransitionData()
        state.last_twist = self._task_command_handler.get_last_twist()
        state.last_task_ending_state = self._task_command_handler.get_state()
        state.timeout_sent = self._timeout_vel_sent
        return self._state_monitor.fill_out_transition(state)

    # callback for safety task completition
    def _safety_task_complete_callback(self, status, response):
        with self._lock:
            if response.success:
                rospy.logwarn(
                    'Motion Coordinator supposedly safely landed the aircraft')
            else:
                rospy.logerr('Motion Coordinator did not safely land aircraft')
            self._safety_land_complete = True