Platform/MTReplay: Add automatic platform creation

The new command mtreplay --add-platform $IP will connect to a remote
device and guide the user through the creation of a new platform for
mtreplay and touchpad-tests.
It will automatically pull hw properties and ask the user to pick
the right xorg config files and identifiers.
The new platform info will then be stored in mttools/platforms/

BUG=chromium:353779
TEST=run mtreplay -a $IP

Change-Id: I33f077a3897508c0d2a9d2c1838a8ce81f768db4
Reviewed-on: https://chromium-review.googlesource.com/194976
Reviewed-by: Dennis Kempin <denniskempin@chromium.org>
Tested-by: Dennis Kempin <denniskempin@chromium.org>
Commit-Queue: Dennis Kempin <denniskempin@chromium.org>
diff --git a/mtlib/platform.py b/mtlib/platform.py
index b945ee7..e505c6e 100755
--- a/mtlib/platform.py
+++ b/mtlib/platform.py
@@ -8,6 +8,7 @@
 import re
 import sys
 from xorg_conf import XorgInputClassParser
+from cros_remote import CrOSRemote
 
 # path to current script directory
 script_dir = os.path.dirname(os.path.realpath(__file__))
@@ -15,6 +16,18 @@
 xorg_conf_path = os.path.realpath(os.path.join(script_dir, '..',
                                                '..', 'xorg-conf'))
 
+props_template = """\
+{
+  "gestures": {
+  },
+  "xorg": {
+    "file": "%s",
+    "identifiers": %s
+  },
+  "ignore": [
+  ]
+}"""
+
 class PlatformProperties(object):
   """ A class containing hardware and xorg properties for a platform.
 
@@ -257,3 +270,133 @@
       print 'cannot find matching platform'
       return None
     return result
+
+  @staticmethod
+  def RegisterPlatformFromDevice(ip):
+    # get list of multitouch devices
+    remote = CrOSRemote(ip)
+    devices = remote.ReadCommand(
+        "/opt/google/input/inputcontrol -t multitouch --names",
+        verbose=True)
+    if devices is False:
+      return None
+
+    # Each line has the format:
+    # id: Device Name
+    # devices[*][0] will have the id
+    # devices[*][1] will have the name
+    devices = devices.splitlines()
+    devices = [l.split(":", 1) for l in devices]
+
+    # select one device from list
+    idx = UserSelection([d[1] for d in devices],
+                         "Which device would you like to register?")
+    device_id = devices[idx][0]
+
+    # read hardware properties
+    hwprops = remote.ReadCommand(
+        "/opt/google/input/inputcontrol --id %s --hwprops" % device_id,
+        verbose=True)
+    if not hwprops:
+      print "Please update your device to latest canary or:"
+      print " emerge-${BOARD} inputcontrol"
+      print " cros deploy $DEVICE_IP inputcontrol"
+      return None
+
+    # Asks user to select xorg identifiers from a file
+    def SelectXorgIDs(xorg_file):
+      print "Selecting Xorg identifiers from", xorg_file
+      conf = remote.Read(xorg_file)
+      ids = []
+      for match in re.finditer("Identifier\s+\"([a-zA-Z0-9 ]+)\"", conf):
+        ids.append(match.group(1))
+
+      idxs = UserSelection(ids, "Which xorg identifiers apply to this device?",
+                           allow_multi=True, allow_zero=True)
+      return [ids[i] for i in idxs]
+
+    # first ask user for xorg identifiers from platform specific file,
+    # then for identifiers from generic config file.
+    xorg_file = remote.ReadCommand(
+        "ls /etc/X11/xorg.conf.d/50-touchpad-cmt-*",
+        verbose=True).strip()
+    ids = SelectXorgIDs(xorg_file)
+    if not ids:
+      xorg_file = "/etc/X11/xorg.conf.d/40-touchpad-cmt.conf"
+      ids = SelectXorgIDs(xorg_file)
+      if not ids:
+        print "Please configure the platform properties manually"
+        xorg_file = "todo: add correct xorg conf file"
+        ids = ["todo: add correct xorg identifier"]
+
+    ids_string = "[" + ", ".join(["\"%s\"" % id for id in ids]) + "]"
+    xorg_file = os.path.basename(xorg_file)
+
+    sys.stdout.write("Please name this platform: ")
+    sys.stdout.flush()
+    platform_name = sys.stdin.readline().strip()
+
+    # write platform info to files
+    hwprops_file = os.path.join(platforms_dir, platform_name + ".hwprops")
+    props_file = os.path.join(platforms_dir, platform_name + ".props")
+
+    open(hwprops_file, "w").write(hwprops)
+    open(props_file, "w").write(props_template % (xorg_file, ids_string))
+
+    print "Created files: "
+    print " ", hwprops_file
+    print " ", props_file
+
+    return platform_name
+
+
+def UserSelection(list, msg, allow_multi=False, allow_zero=False):
+  idx = [0]
+  if len(list) == 1:
+    return [0] if allow_multi else 0
+
+  # repeat until user made a valid selection
+  while True:
+
+    # get user input
+    print msg
+    if allow_zero:
+      print "  0: None"
+    for i, item in enumerate(list):
+      print " ", str(i + 1) + ":", item
+    if allow_multi:
+      print "(Separate multiple selections with spaces)"
+    sys.stdout.write('> ')
+    sys.stdout.flush()
+    selection = sys.stdin.readline()
+
+    # validates single input value
+    def CheckSelection(selection):
+      try:
+        idx = int(selection) - 1
+        if allow_zero and idx == -1:
+          return True
+        if idx < 0 or idx >= len(list):
+          print 'Number out of range'
+          return False
+      except:
+        print 'Not a number'
+        return False
+      return True
+
+    if allow_multi:
+      # validate list of values
+      valid = True
+      selections = selection.split(" ")
+      for selection in selections:
+        if not CheckSelection(selection):
+          valid = False
+          break
+      if valid:
+        selections = [int(s) - 1 for s in selections]
+        if -1 in selections:
+          return []
+        return selections
+    else:
+      if CheckSelection(selection):
+        return int(selection) - 1