mtlib/platform: Updated platform matching

The platform matching was written at a time when evdev logs might not have
a header containing axis information.
Since now all log files contain this information, it will help us to do much
more accurate matching by using absinfo fields only.
This removes a lot of code that was necessary to guess values from the content
of the log or from xorg properties.

BUG=chromium:474263
TEST=manual testing by running mtreplay on a couple of log files

Change-Id: I12a7c9707bfa4fb175397338216e6ec67f51f8e1
Reviewed-on: https://chromium-review.googlesource.com/264066
Reviewed-by: Andrew de los Reyes <adlr@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 0b12320..71589c8 100755
--- a/mtlib/platform.py
+++ b/mtlib/platform.py
@@ -3,15 +3,16 @@
 # found in the LICENSE file.
 #
 """ This module manages the platform properties in mttools/platforms. """
-from cros_remote import CrOSRemote
-from util import AskUser
-from xorg_conf import XorgInputClassParser
-from util import ExecuteException
+from collections import namedtuple
 import json
 import os
 import re
 import sys
 
+from cros_remote import CrOSRemote
+from util import AskUser, ExecuteException
+from xorg_conf import XorgInputClassParser
+
 # path to current script directory
 script_dir = os.path.dirname(os.path.realpath(__file__))
 platforms_dir = os.path.realpath(os.path.join(script_dir, '..', 'platforms'))
@@ -29,6 +30,8 @@
   ]
 }"""
 
+AbsInfo = namedtuple("AbsInfo", ("min", "max", "res"))
+
 class PlatformProperties(object):
   """ A class containing hardware and xorg properties for a platform.
 
@@ -37,8 +40,7 @@
   'platforms_dir' directory.
   """
   def __init__(self, platform=None, log=None):
-    self.required_axis = []
-    self.has_axis = []
+    self.absinfos = {}
     self.device_class = "touchpad"
     self.properties = {}
     self.ignore_properties = []
@@ -50,75 +52,31 @@
       self.xorg_parser = XorgInputClassParser()
       self._ParseHWProperties(open(self.hwprops_file).read())
       self._ParseProperties(open(self.props_file).read())
-      self._UpdateDimensions()
     elif log:
       self.name = ''
       if log.evdev:
         self._ParseEvdevLog(log.evdev)
-      self._ParseActivityLog(log.activity)
-
-
-  def _ParseActivityLog(self, activity_data):
-    """ Parse property information from an activity log."""
-    activity = json.loads(activity_data)
-    self.properties = activity['properties']
-
-    hwprops = activity['hardwareProperties']
-    self.x_min = int(hwprops['left'])
-    self.x_max = int(hwprops['right'])
-    self.x_res = int(hwprops['xResolution'])
-    self.y_min = int(hwprops['top'])
-    self.y_max = int(hwprops['bottom'])
-    self.y_res = int(hwprops['yResolution'])
 
   def _ParseEvdevLog(self, evdev_data):
     # Look for embedded hwproperties in header. Format:
     # absinfo: axis min max 0 0 res
-    abs_regex = 5 * ' ([-0-9]+)'
-    xregex = re.compile('# absinfo: 53' + abs_regex)
-    xmatch = xregex.search(evdev_data)
-    self.x_min = int(xmatch.group(1))
-    self.x_max = int(xmatch.group(2))
-    self.x_res = int(xmatch.group(5))
-
-    yregex = re.compile('# absinfo: 54' + abs_regex)
-    ymatch = yregex.search(evdev_data)
-    self.y_min = int(ymatch.group(1))
-    self.y_max = int(ymatch.group(2))
-    self.y_res = int(ymatch.group(5))
-
-    axis_regex = re.compile('# absinfo: ([0-9]+)')
-    for match in axis_regex.finditer(evdev_data):
-      self.has_axis.append(int(match.group(1)))
-
-    # look for axes used in the log itself.
-    # The format of ABS (0003) reports is:
-    # timestamp 0003 axis value
-    report_regex = re.compile(' 0003 ([0-9a-f]{4}) ([0-9a-f]+)')
-    for match in report_regex.finditer(evdev_data):
-      axis = int(match.group(1), 16)
-      if axis not in self.required_axis:
-        self.required_axis.append(axis)
-
+    values_regex = 6 * ' ([-0-9]+)'
+    abs_regex = re.compile('# absinfo:' + values_regex)
+    for match in abs_regex.finditer(evdev_data):
+      axis = int(match.group(1))
+      info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
+                     res=int(match.group(6)))
+      self.absinfos[axis] = info
 
   def _ParseHWProperties(self, data):
     """Parse x and y dimensions and resolution from hwprops file."""
-    abs_regex = 5 * ' ([0-9\\-]+)'
-    xregex = re.compile('A: 35' + abs_regex)
-    xmatch = xregex.search(data)
-    self.x_min = int(xmatch.group(1))
-    self.x_max = int(xmatch.group(2))
-    self.x_res = int(xmatch.group(5))
-
-    yregex = re.compile('A: 36' + abs_regex)
-    ymatch = yregex.search(data)
-    self.y_min = int(ymatch.group(1))
-    self.y_max = int(ymatch.group(2))
-    self.y_res = int(ymatch.group(5))
-
-    axis_regex = re.compile('A: ([0-9a-f]+)')
-    for match in axis_regex.finditer(data):
-      self.has_axis.append(int(match.group(1), 16))
+    values_regex = 6 * ' ([0-9\\-a-fA-F]+)'
+    abs_regex = re.compile('A:' + values_regex)
+    for match in abs_regex.finditer(data):
+      axis = int(match.group(1), 16)
+      info = AbsInfo(min=int(match.group(2)), max=int(match.group(3)),
+                     res=int(match.group(6)))
+      self.absinfos[axis] = info
 
   def _ParseProperties(self, data):
     """ Parse properties from file and inject xorg properties. """
@@ -165,36 +123,6 @@
       if prop in self.properties:
         del self.properties[prop]
 
-  def _UpdateDimensions(self):
-    """ Update x/y min/max with xorg properties.
-
-    CMT allows hardware properties to be overwritten by xorg properties.
-    Do the same in this class.
-    """
-    if 'Active Area Left' in self.properties:
-      self.x_min = int(self.properties['Active Area Left'])
-    if 'Active Area Right' in self.properties:
-      self.x_max = int(self.properties['Active Area Right'])
-    if 'Active Area Top' in self.properties:
-      self.y_min = int(self.properties['Active Area Top'])
-    if 'Active Area Bottom' in self.properties:
-      self.y_max = int(self.properties['Active Area Bottom'])
-
-    if 'Horizontal Resolution' in self.properties:
-      self.x_res = int(self.properties['Horizontal Resolution'])
-    if 'Vertical Resolution' in self.properties:
-      self.y_res = int(self.properties['Vertical Resolution'])
-
-    if 'SemiMT Non Linear Area Left' in self.properties:
-      self.x_min = int(self.properties['SemiMT Non Linear Area Left'])
-    if 'SemiMT Non Linear Area Right' in self.properties:
-      self.x_max = int(self.properties['SemiMT Non Linear Area Right'])
-    if 'SemiMT Non Linear Area Top' in self.properties:
-      self.y_min = int(self.properties['SemiMT Non Linear Area Top'])
-    if 'SemiMT Non Linear Area Bottom' in self.properties:
-      self.y_max = int(self.properties['SemiMT Non Linear Area Bottom'])
-
-
   def Match(self, other, loose, debug=False):
     """ Compare properties and return similarity.
 
@@ -205,44 +133,32 @@
     prevent property adjustments to cause platforms to be mismatched.
     """
     scores = []
-    def compare(a, b, what):
-      value = abs(float(a) - float(b))
-      if value > 0:
-        value = min(1, value / max(abs(float(a)), abs(float(b))))
-      scores.append(1-value)
-      if debug:
-        print "%s: %s == %s" % (what, str(a), str(b))
-    def compare_attr(what):
-      compare(getattr(self, what), getattr(other, what), what)
-    def compare_prop(what):
-      if what not in self.properties or what not in other.properties:
-        scores.append(0)
-      else:
-        compare(self.properties[what], other.properties[what], what)
-    def check_axis(required, available):
-      for axis in required:
-        if axis not in available:
-          scores.append(0)
-          return
+    def compare_absinfo_prop(a, b, field):
+      a_value = float(getattr(a, field)) if a else None
+      b_value = float(getattr(b, field)) if b else None
 
-    compare_attr('x_min')
-    compare_attr('x_max')
-    compare_attr('x_res')
-    compare_attr('y_min')
-    compare_attr('y_max')
-    compare_attr('y_res')
-    if not loose:
-      compare_prop('Pressure Calibration Offset')
+      score = 0.0
+      if a_value is not None and b_value is not None:
+        delta = abs(float(a_value) - float(b_value))
+        if delta > 0:
+          score = 1.0 - min(1.0, delta / max(abs(a_value), abs(b_value)))
+        else:
+          score = 1.0
+      scores.append(score)
 
-    if self.required_axis:
+      a_str = str(a_value) if a_value is not None else "N/A"
+      b_str = str(b_value) if b_value is not None else "N/A"
       if debug:
-        print "axis:", self.required_axis, "in", other.has_axis
-      check_axis(self.required_axis, other.has_axis)
+        print "  %s: %.2f (%s == %s)" % (field, score, a_str, b_str)
 
-    if other.required_axis:
+    for axis in set(self.absinfos.keys() + other.absinfos.keys()):
       if debug:
-        print "axis:", other.required_axis, "in", self.has_axis
-      check_axis(other.required_axis, self.has_axis)
+        print "axis %d:" % axis
+      self_absinfo = self.absinfos.get(axis, None)
+      other_absinfo = other.absinfos.get(axis, None)
+      compare_absinfo_prop(self_absinfo, other_absinfo, "min")
+      compare_absinfo_prop(self_absinfo, other_absinfo, "max")
+      compare_absinfo_prop(self_absinfo, other_absinfo, "res")
 
     return reduce(lambda x, y: (x * y), scores)
 
@@ -271,7 +187,7 @@
     properties = PlatformProperties(log=log)
     for name, platform in self.platforms.items():
       if debug:
-        print "#" * 10, name
+        print "-" * 30, name
       score = platform.Match(properties, loose, debug)
       if debug:
         print name, "score =", score