Implemented Agent Handling

Change-Id: I172e71d2a570bf957ad5ea06513f8c49fae6b2bc
diff --git a/utils/python/controllers/android_device.py b/utils/python/controllers/android_device.py
index a24b34e..333040b 100644
--- a/utils/python/controllers/android_device.py
+++ b/utils/python/controllers/android_device.py
@@ -21,6 +21,9 @@
 import os
 import time
 import traceback
+import threading
+import socket
+import Queue
 
 from vts.runners.host import logger as vts_logger
 from vts.runners.host import signals
@@ -28,8 +31,10 @@
 from vts.utils.python.controllers import adb
 from vts.utils.python.controllers import event_dispatcher
 from vts.utils.python.controllers import fastboot
-
+from vts.runners.host.tcp_client import vts_tcp_client
 from vts.utils.python.mirror import hal_mirror
+from vts.runners.host import errors
+import subprocess
 
 VTS_CONTROLLER_CONFIG_NAME = "AndroidDevice"
 VTS_CONTROLLER_REFERENCE_NAME = "android_devices"
@@ -40,6 +45,14 @@
 ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!"
 ANDROID_DEVICE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!"
 
+# Target-side directory where the VTS binaries are uploaded
+DEFAULT_AGENT_BASE_DIR = "/data/local/tmp"
+# Time for which the current is put on sleep when the client is unable to
+# make a connection.
+THREAD_SLEEP_TIME = 1
+# Max number of attempts that the client can make to connect to the agent
+MAX_AGENT_CONNECT_RETRIES = 10
+
 
 class AndroidDeviceError(signals.ControllerError):
     pass
@@ -281,7 +294,16 @@
                            (to send commands and receive responses).
         host_callback_port: the host-side port for agent to runner sessions
                             (to get callbacks from agent).
+        background_thread: a thread that runs in background to upload the
+            agent on the device.
+        queue: an instance of queue.Queue that keeps main thread on wait unless
+            the background_thread indicates it to move.
+        base_dir: target-side directory where the VTS binaries are uploaded.
     """
+    background_thread = None
+    background_thread_proceed = False
+    base_dir = None
+    queue = Queue.Queue()
 
     def __init__(self, serial="", device_port=5001, device_callback_port=5010):
         self.serial = serial
@@ -469,3 +491,100 @@
         self.rootAdb()
         if has_adb_log:
             self.startAdbLogcat()
+
+    def startAgent(self):
+        """ To start agent.
+
+        This function starts the target side native agent and is persisted
+        throughout the test run. This process is handled by the VTS runner lib.
+        """
+        # to ensure that only one instance of this agent runs
+        if(self.background_thread is not None):
+            logging.error(
+                "Another instance of background_thread is already "
+                "running.")
+            return
+
+        background_thread = threading.Thread(target=self.runAgent)
+        # Exit the server thread when the main thread terminates
+        background_thread.daemon = True
+        background_thread.start()
+
+        # wait for the flag from child thread
+        self.queue.get(block=True, timeout=None)
+
+        client = vts_tcp_client.VtsTcpClient()
+
+        # Ensure that the connection succeeds before it moves forward.
+        for _ in range(MAX_AGENT_CONNECT_RETRIES):
+            try:
+                time.sleep(THREAD_SLEEP_TIME)  # put current thread on sleep
+                response = client.Connect(
+                    command_port=self.host_command_port,
+                    callback_port=self.host_callback_port)
+
+                if response:
+                    return
+            except socket.error as e:
+                pass
+
+        # Throw error if client is unable to make a connection
+        raise errors.VtsTcpClientCreationError(
+              "Couldn't connect to %s:%s" % (
+                                    vts_tcp_client.TARGET_IP,
+                                    self.host_command_port))
+
+    def runAgent(self):
+        """This functions runs the child thread that runs the agent."""
+
+        # kill the existing instance of agent in DUT
+        commands = ["killall vts_hal_agent > /dev/null 2&>1",
+                    "killall fuzzer32 > /dev/null 2&>1",
+                    "killall fuzzer64 > /dev/null 2&>1"]
+
+        for cmd in commands:
+            try:
+                self.adb.shell(cmd)
+            except adb.AdbError as e:
+                logging.info('Exception occurred in command: %s', cmd)
+
+        cmd = '{}{}{} {}{} {}{} {}{} {}{}'.format(
+            'LD_LIBRARY_PATH=',
+            DEFAULT_AGENT_BASE_DIR, "/64",
+            DEFAULT_AGENT_BASE_DIR, "/64/vts_hal_agent",
+            DEFAULT_AGENT_BASE_DIR, "/32/fuzzer32",
+            DEFAULT_AGENT_BASE_DIR, "/64/fuzzer64",
+            DEFAULT_AGENT_BASE_DIR, "/spec")
+
+        self.queue.put('flag')
+        self.adb.shell(cmd)
+
+        # This should never happen.
+        logging.exception("Agent Terminated!")
+
+    def stopAgent(self):
+        """Stop the agent running on target.
+
+        This function stops the target side native agent which is persisted
+        throughout the test run. Obtain the process ID for the agent running
+        on DUT and then kill the process. This assumes each target device runs
+        only one VTS agent at a time.
+
+        """
+        # TODO: figure out if this function is called from unregisterControllers
+        cmd = 'adb shell pgrep vts_hal_agent'
+        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
+        (out, err) = proc.communicate()
+        out = out.strip()
+        processList = out.split('\n')
+
+        # Return if multiple agents are running on the device
+        if len(processList) > 1:
+            logging.error("Multiple instances of vts_hal_agent running on "
+                "device.")
+            return
+
+        # Kill the processes corresponding to the agent
+        for pid in processList:
+            cmd = '{} {}'.format('adb shell kill -9', pid)
+            subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)