capsule: Allow multiple connected devices.

Currently, the adb calls in Capsule fail if more than one device is
connected because adb command needs to specify the serial number of the
intended device. Instead, we pass the serial number of the device that
Capsule is running on to crawlpkg so that the commands are issued to the
proper device.

Change-Id: Ied65930bd319868589e28f2a208a667d20101be6
diff --git a/capsule/README.md b/capsule/README.md
index cda6d82..c161a5c 100644
--- a/capsule/README.md
+++ b/capsule/README.md
@@ -28,13 +28,13 @@
 Although the device name is optional with no command line arguments, you must
 specify the device name if you pass in any arguments.
 
-```$ python capsule.py 'emulator-5554'```
+```$ python capsule.py emulator-5554```
 
 If a text file is passed in as a command line argument (with -f or --file),
 Capsule attempts to crawl each app listed in the file (one app per line),
 assuming that all of the packages are already installed on the device.
 
-```$ python capsule.py 'emulator-5554' -f /[PATH TO FILE]/list.txt```
+```$ python capsule.py emulator-5554 -f /[PATH TO FILE]/list.txt```
 
 If a directory is passed in as an argument (with -d or --dir), Capsule installs,
 crawls, and uninstalls each of the apps in the directory in alphabetical order.
diff --git a/capsule/capsule.py b/capsule/capsule.py
index d157c82..75e27f8 100644
--- a/capsule/capsule.py
+++ b/capsule/capsule.py
@@ -15,9 +15,8 @@
 import crawlpkg
 
 ADB_PATH = obtainAdbPath()
-# os.environ['ANDROID_ADB_SERVER_PORT'] = '5554'
 HELP_MSG = ('Capsule usage:\n'
-            "python capsule.py 'DEVICE_NAME' [flag] <argument>\n"
+            "python capsule.py DEVICE_SERIAL [flag] <argument>\n"
             'No command line flags -- crawl current package\n'
             '-d or --dir /PATH_TO_APKS/  -- install and run APKS from a '
             'directory.\n'
@@ -83,19 +82,29 @@
   should_crawl = False
   package_list = []
 
-  kwargs1 = {'verbose': True, 'ignoresecuredevice': True}
-  kwargs2 = {'startviewserver': True, 'forceviewserveruse': True,
-             'autodump': False, 'ignoreuiautomatorkilled': True}
-  device, serialno = ViewClient.connectToDeviceOrExit(**kwargs1)
-  vc = ViewClient(device, serialno, **kwargs2)
+  try:
+    kwargs1 = {'verbose': True, 'ignoresecuredevice': True}
+    kwargs2 = {'startviewserver': True, 'forceviewserveruse': True,
+               'autodump': False, 'ignoreuiautomatorkilled': True}
+    device, serialno = ViewClient.connectToDeviceOrExit(**kwargs1)
+    vc = ViewClient(device, serialno, **kwargs2)
+  except (RuntimeError, subprocess.CalledProcessError):
+    print 'Error, device not found or not specified.'
+    print HELP_MSG
+    sys.exit()
+
+  if any(a in sys.argv for a in ['-h', '--help']):
+    print HELP_MSG
+    sys.exit()
 
   # User only specified emulator name or nothing at all.
   if len(sys.argv) <= 2:
     print 'No command line arguments, crawling currently launched app.'
-    crawlpkg.crawl_package(vc, device)
+    crawlpkg.crawl_package(vc, device, serialno)
   # Command line argument is only valid if the user entered the filename,
   # emulator name, one option flag, and one argument.
-  elif len(sys.argv) == 3 and sys.argv[2] == '-h' or sys.argv[2] == '--help':
+  elif len(sys.argv) == 3:
+    print 'Invalid argument structure.'
     print HELP_MSG
   elif len(sys.argv) >= 4:
     try:
@@ -108,7 +117,7 @@
     # This infrastructure allows us to add additional command line argument
     # possibilities easily.
     for opt, arg in opts:
-      if opt in ('-d', '-dir'):
+      if opt in ('-d', '--dir'):
         uninstall = True
         package_list = load_pkgs_from_dir(arg)
       elif opt in ('-f', '--file'):
@@ -137,8 +146,9 @@
         package_name = package.split('/')[-1]
         # Make sure the package is installed on the device by checking it
         # against installed third-party packages.
-        installed_pkgs = subprocess.check_output([ADB_PATH, 'shell',
-                                                  'pm', 'list packages', '-3'])
+        installed_pkgs = subprocess.check_output([ADB_PATH, '-s', serialno,
+                                                  'shell', 'pm',
+                                                  'list packages', '-3'])
         if package_name not in installed_pkgs:
           print 'Cannot find the package on the device.'
           break
@@ -153,16 +163,17 @@
         print 'Crawling ' + package_name
         should_crawl = True
         # Launch the app.
-        subprocess.call([ADB_PATH, 'shell', 'monkey', '-p', package_name, '-c',
-                         'android.intent.category.LAUNCHER', '1'])
+        subprocess.call([ADB_PATH, '-s', serialno, 'shell', 'monkey', '-p',
+                         package_name, '-c', 'android.intent.category.LAUNCHER',
+                         '1'])
         time.sleep(5)
         if '.apk' in package:
-          subprocess.call([ADB_PATH, 'install', '-r', package])
-        crawlpkg.crawl_package(vc, device, package_name)
+          subprocess.call([ADB_PATH, '-s', serialno, 'install', '-r', package])
+        crawlpkg.crawl_package(vc, device, serialno, package_name)
 
         if uninstall:
           print 'uninstall' + package_name
-          subprocess.call([ADB_PATH, 'uninstall', package_name])
+          subprocess.call([ADB_PATH, '-s', serialno, 'uninstall', package_name])
 
   else:
     print 'Invalid number of command line arguments.'
diff --git a/capsule/crawlpkg.py b/capsule/crawlpkg.py
index 6ae8a6d..7430b82 100644
--- a/capsule/crawlpkg.py
+++ b/capsule/crawlpkg.py
@@ -27,6 +27,7 @@
 GONE = 0x8
 
 ADB_PATH = obtainAdbPath()
+SERIAL_NO = ''
 BACK_BUTTON = 'back button'
 # Return a unique string if the package is not the focused window. Since
 # activities cannot have spaces, we ensure that no activity will be named this.
@@ -243,9 +244,12 @@
   screen_name = activity + '-' + first_frag + '-' + str(file_num) + '.png'
   screen_path = os.path.join(directory, screen_name)
   # device.shell() does not work for taking/pulling screencaps.
-  subprocess.call([ADB_PATH, 'shell', 'screencap', '/sdcard/' + screen_name])
-  subprocess.call([ADB_PATH, 'pull', '/sdcard/' + screen_name, screen_path])
-  subprocess.call([ADB_PATH, 'shell', 'rm', '/sdcard/' + screen_name])
+  subprocess.call([ADB_PATH, '-s', SERIAL_NO, 'shell', 'screencap',
+                   '/sdcard/' + screen_name])
+  subprocess.call([ADB_PATH, '-s', SERIAL_NO, 'pull', '/sdcard/' + screen_name,
+                   screen_path])
+  subprocess.call([ADB_PATH, '-s', SERIAL_NO, 'shell', 'rm',
+                   '/sdcard/' + screen_name])
   # Returns the filename & num so that the screenshot can be accessed
   # programatically.
   return screen_path, file_num
@@ -644,9 +648,12 @@
   return logged_in
 
 
-def crawl_package(vc, device, package_name=None):
+def crawl_package(vc, device, serialno, package_name=None):
   """Crawl package. Explore blindly, then return to unexplored layouts."""
 
+  global SERIAL_NO
+  SERIAL_NO = serialno
+
   set_device_dimens(vc, device)
   # Layout map stores all Layouts that we have seen, while the still_exploring
   # consists of only Layouts that have not been exhaustively explored yet (or
@@ -698,9 +705,11 @@
     print 'Route from root to ' + l.get_name()
 
     # Restart the app with its initial screen.
-    subprocess.call([ADB_PATH, 'shell', 'am force-stop', package_name])
-    subprocess.call([ADB_PATH, 'shell', 'monkey', '-p', package_name, '-c',
-                     'android.intent.category.LAUNCHER', '1'])
+    subprocess.call([ADB_PATH, '-s', SERIAL_NO, 'shell', 'am force-stop',
+                     package_name])
+    subprocess.call([ADB_PATH, '-s', SERIAL_NO, 'shell', 'monkey', '-p',
+                     package_name, '-c', 'android.intent.category.LAUNCHER',
+                     '1'])
     time.sleep(5)
 
     if path: