Fix mojo_ tools on the first run after adb kill-server.

Fixes domokit/devtools#47.

R=qsr@chromium.org
BUG=

Review URL: https://codereview.chromium.org/1381863004 .

Cr-Mirrored-From: https://github.com/domokit/mojo
Cr-Mirrored-Commit: c3e3eac077c5cbb6250e50d3630f8c45b1fce693
diff --git a/devtoolslib/android_shell.py b/devtoolslib/android_shell.py
index a4a7d16..2833138 100644
--- a/devtoolslib/android_shell.py
+++ b/devtoolslib/android_shell.py
@@ -38,6 +38,9 @@
 
 _MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell'
 
+# Used to parse the output of `adb devices`.
+_ADB_DEVICES_HEADER = 'List of devices attached'
+
 
 _logger = logging.getLogger()
 
@@ -63,6 +66,24 @@
   netstat_output = subprocess.check_output(['netstat'])
   return _find_available_port(netstat_output)
 
+def parse_adb_devices_output(adb_devices_output):
+  """Parses the output of the `adb devices` command, returning a dictionary
+  mapping device id to the status of the device, as printed by `adb devices`.
+  """
+  # Split into lines skipping empty ones.
+  lines = [line.strip() for line in adb_devices_output.split('\n')
+           if line.strip()]
+
+  if _ADB_DEVICES_HEADER not in lines:
+    return None
+
+  # The header can be preceeded by output informing of adb server being spawned,
+  # but all non-empty lines after the header describe connected devices.
+  device_specs = lines[lines.index(_ADB_DEVICES_HEADER) + 1:]
+  split_specs = [spec.split() for spec in device_specs]
+  return {split_spec[0]: split_spec[1] for split_spec in split_specs
+          if len(split_spec) == 2}
+
 
 class AndroidShell(Shell):
   """Wrapper around Mojo shell running on an Android device.
@@ -213,25 +234,24 @@
     """
     adb_devices_output = subprocess.check_output(
         self._adb_command(['devices']))
-    # Skip the header line, strip empty lines at the end.
-    device_list = [line.strip() for line in adb_devices_output.split('\n')[1:]
-                   if line.strip()]
+    devices = parse_adb_devices_output(adb_devices_output)
+
+    if not devices:
+      return False, 'No devices connected.'
 
     if self.target_device:
-      if any([line.startswith(self.target_device) and
-              line.endswith('device') for line in device_list]):
+      if (self.target_device in devices and
+          devices[self.target_device] == 'device'):
         return True, None
       else:
-        return False, 'Cannot connect to the selected device.'
+        return False, ('Cannot connect to the selected device, status: ' +
+                       devices[self.target_device])
 
-    if len(device_list) > 1:
+    if len(devices) > 1:
       return False, ('More than one device connected and target device not '
                      'specified.')
 
-    if not len(device_list):
-      return False, 'No devices connected.'
-
-    if not device_list[0].endswith('device'):
+    if not devices.itervalues().next() == 'device':
       return False, 'Connected device is not available.'
 
     if require_root and not self._run_adb_as_root():
diff --git a/devtoolslib/android_shell_unittest.py b/devtoolslib/android_shell_unittest.py
new file mode 100644
index 0000000..caf2483
--- /dev/null
+++ b/devtoolslib/android_shell_unittest.py
@@ -0,0 +1,61 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Tests for the Android shell abstraction."""
+
+import imp
+import os.path
+import sys
+import unittest
+
+try:
+  imp.find_module("devtoolslib")
+except ImportError:
+  sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from devtoolslib.android_shell import parse_adb_devices_output
+
+class AndroidShellTest(unittest.TestCase):
+  """Tests the Android shell abstraction."""
+
+  def test_parse_adb_devices_output(self):
+    """Tests parsing of the `adb devices` output."""
+    one_device_output = """\
+List of devices attached
+42424242        device
+
+"""
+    results = parse_adb_devices_output(one_device_output)
+    self.assertEquals(1, len(results))
+    self.assertTrue('42424242' in results)
+    self.assertEquals('device', results['42424242'])
+
+    multi_devices_output = """\
+List of devices attached
+42424242        device
+42424243        offline
+42424244        device
+
+"""
+    results = parse_adb_devices_output(multi_devices_output)
+    self.assertEquals(3, len(results))
+    self.assertTrue('42424242' in results)
+    self.assertEquals('device', results['42424242'])
+    self.assertTrue('42424243' in results)
+    self.assertEquals('offline', results['42424243'])
+    self.assertTrue('42424244' in results)
+    self.assertEquals('device', results['42424244'])
+
+    # Output produced when adb server needs to be started.
+    adb_server_startup_output = """\
+* daemon not running. starting it now on port 5037 *
+* daemon started successfully *
+List of devices attached
+42424242        device
+
+"""
+    results = parse_adb_devices_output(adb_server_startup_output)
+    self.assertEquals(1, len(results))
+    self.assertTrue('42424242' in results)
+    self.assertEquals('device', results['42424242'])