Introduce command handlers for adb and aapt

Change-Id: I76e79405e3a6ff5efeeded3ff8c47e3d70f48cc0
diff --git a/deploy.py b/deploy.py
index d70e26d..590e150 100755
--- a/deploy.py
+++ b/deploy.py
@@ -1,13 +1,119 @@
 #!/usr/bin/env python3
 
+import os
 import time
 from uiautomator import Device
 import sys
 import subprocess
 import pathlib
 
+
+PREBUILTS_PATH = '../../vendor/smartviser/viser/prebuilts/apk'
+
+PREBUILT_APKS = [
+    'com.smartviser.demogame.apk',
+    'com.lunarlabs.panda.proxy.apk',
+    'com.lunarlabs.panda.apk',
+]
+
+
+def adb(*args, serial = None):
+    """Run ADB command attached to serial.
+
+    Example:
+    >>> process = adb('shell', 'getprop', 'ro.build.fingerprint', serial='cc60c021')
+    >>> process.returncode
+    0
+    >>> process.stdout.strip()
+    'Fairphone/FP2/FP2:6.0.1/FP2-gms-18.02.0/FP2-gms-18.02.0:user/release-keys'
+
+    :param *args:
+        List of options to ADB (including command).
+    :param str serial:
+        Identifier for ADB connection to device.
+    :returns subprocess.CompletedProcess:
+        Completed process.
+    """
+
+    # Make sure the adb server is started to avoid the infamous "out of date"
+    # message that pollutes stdout.
+    subprocess.run(
+        ['adb', 'start-server'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+        universal_newlines=True)
+
+    command = ['adb']
+    if serial:
+        command += ['-s', serial]
+    if args:
+        command += list(args)
+    return subprocess.run(
+        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+        universal_newlines=True)
+
+
+def aapt(*args):
+    """Run an AAPT command.
+
+    :param *args:
+        The AAPT command with its options.
+    :returns subprocess.CompletedProcess:
+        Completed process.
+    """
+    command = ['aapt']
+    if args:
+        command += list(args)
+    return subprocess.run(
+        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+        universal_newlines=True)
+
+
+def uninstall_apk(serial, filename, prebuilts_dir):
+    """Uninstall apk from prebuilts_dir on device."""
+    ret = aapt('dump', 'badging', '{}/{}'.format(prebuilts_dir, filename))
+    if 0 < ret.returncode:
+        print('Could retrieve app `{}` info, error was: {}'.format(
+            filename, ret.stderr), file=sys.stderr)
+    else:
+        package = None
+        for line in ret.stdout.splitlines():
+            if line.startswith('package'):
+                for token in line.split(' '):
+                    if token.startswith('name='):
+                        # Extract the package name out of the token
+                        # (name='some.package.name')
+                        package = token[6:-1]
+                        break
+        if not package:
+            print('Could not find package of app `{}`'.format(filename),
+                file=sys.stderr)
+        else:
+            print('Uninstalling `{}`'.format(package))
+            ret = adb('uninstall', package, serial=serial)
+            if 0 < ret.returncode:
+                print('Could not uninstall app `{}`, error was: {}'.format(
+                    filename, ret.stderr), file=sys.stderr)
+            else:
+                print('App `{}` uninstalled.'.format(filename))
+
+
+def install_apk(serial, filename, prebuilts_dir):
+    """Install apk from prebuilts_dir on device."""
+    print('Installing {}.'.format(filename))
+    path = os.path.join(prebuilts_dir, filename)
+    ret = adb('install', '-r', path, serial=serial)
+    if 0 < ret.returncode:
+        print('Could not install app `{}`, error was: {}'.format(
+            filename, ret.stderr), file=sys.stderr)
+    else:
+        print('App `{}` installed.'.format(filename))
+
+
 # Prepare the DUT
-def prepare_dut(serial, scenarios_dir, data_dir, apps):
+def prepare_dut(serial, scenarios_dir, data_dir, prebuilts_dir):
+    # Uninstall the smartviser apps
+    for app in PREBUILT_APKS:
+        uninstall_apk(serial, app, prebuilts_dir)
+
     # Copy the scenarios
     ret = subprocess.run(['adb', '-s', serial, 'push', scenarios_dir,
             '/sdcard/viser'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
@@ -29,15 +135,9 @@
         print('Scenarios data pushed.')
 
     # Install the smartviser apps
-    for app in apps:
-        ret = subprocess.run(['adb', '-s', serial, 'install', '-r', app],
-                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
-                universal_newlines=True)
-        if 0 < ret.returncode:
-            print('Could not install app `{}`, error was: {}'.format(app,
-                    ret.stderr), file=sys.stderr)
-        else:
-            print('App `{}` installed.'.format(app))
+    for app in PREBUILT_APKS:
+        install_apk(serial, app, prebuilts_dir)
+
 
 # Grant the permissions through the UI
 def configure_perms(dut):
@@ -143,11 +243,7 @@
         dut = Device(serial)
 
         # Push the scenarios, their data, and install the apps
-        prepare_dut(serial, '../scenarios', '../scenarios-data', [
-            '../../vendor/smartviser/viser/prebuilts/apk/com.smartviser.demogame.apk',
-            '../../vendor/smartviser/viser/prebuilts/apk/com.lunarlabs.panda.proxy.apk',
-            '../../vendor/smartviser/viser/prebuilts/apk/com.lunarlabs.panda.apk'
-        ])
+        prepare_dut(serial, '../scenarios', '../scenarios-data', PREBUILTS_PATH)
 
         # Start the viser app
         ret = subprocess.run(['adb', '-s', serial, 'shell', 'monkey', '-p',