Home | History | Annotate | Download | only in firmware_TouchMTB
      1 # -*- coding: utf-8 -*-
      2 
      3 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """Guide the user to perform gestures. Record and validate the gestures."""
      8 
      9 import fcntl
     10 import glob
     11 import os
     12 import subprocess
     13 import sys
     14 
     15 import common_util
     16 import firmware_log
     17 import firmware_utils
     18 import fuzzy
     19 import mini_color
     20 import mtb
     21 import touchbotII_robot_wrapper as robot_wrapper
     22 import test_conf as conf
     23 import validators
     24 
     25 from firmware_utils import GestureList
     26 
     27 sys.path.append('../../bin/input')
     28 import input_device
     29 
     30 # Include some constants
     31 from firmware_constants import DEV, GV, MODE, OPTIONS, TFK
     32 
     33 
     34 class TestFlow:
     35     """Guide the user to perform gestures. Record and validate the gestures."""
     36 
     37     def __init__(self, device_geometry, device, keyboard, win, parser, output,
     38                  test_version, board, firmware_version, options):
     39         self.device_geometry = device_geometry
     40         self.device = device
     41         self.device_node = self.device.device_node
     42         self.keyboard = keyboard
     43         self.firmware_version = firmware_version
     44         self.output = output
     45         self.board = board
     46         self.test_version = test_version
     47         self.output.print_report('%s' % test_version)
     48         self._get_record_cmd()
     49         self.win = win
     50         self.parser = parser
     51         self.packets = None
     52         self.gesture_file_name = None
     53         self.prefix_space = self.output.get_prefix_space()
     54         self.scores = []
     55         self.mode = options[OPTIONS.MODE]
     56         self.fngenerator_only = options[OPTIONS.FNGENERATOR]
     57         self.iterations = options[OPTIONS.ITERATIONS]
     58         self.replay_dir = options[OPTIONS.REPLAY]
     59         self.resume_dir = options[OPTIONS.RESUME]
     60         self.recording = not any([bool(self.replay_dir), bool(self.resume_dir)])
     61         self.device_type = (DEV.TOUCHSCREEN if options[OPTIONS.TOUCHSCREEN]
     62                                             else DEV.TOUCHPAD)
     63 
     64         self.robot = robot_wrapper.RobotWrapper(self.board, options)
     65         self.robot_waiting = False
     66 
     67         self.gv_count = float('infinity')
     68         gesture_names = self._get_gesture_names()
     69         order = None
     70         if self._is_robot_mode():
     71             order = lambda x: conf.finger_tips_required[x.name]
     72         self.gesture_list = GestureList(gesture_names).get_gesture_list(order)
     73         self._get_all_gesture_variations(options[OPTIONS.SIMPLIFIED])
     74 
     75         self.init_flag = False
     76         self.system_device = self._non_blocking_open(self.device_node)
     77         self.evdev_device = input_device.InputEvent()
     78         self.screen_shot = firmware_utils.ScreenShot(self.geometry_str)
     79         self.mtb_evemu = mtb.MtbEvemu(device)
     80 
     81         self._rename_old_log_and_html_files()
     82         self._set_static_prompt_messages()
     83         self.gesture_image_name = None
     84         self.gesture_continues_flag = False
     85         self.use_existent_event_file_flag = False
     86 
     87     def __del__(self):
     88         self.system_device.close()
     89 
     90     def _rename_old_log_and_html_files(self):
     91         """When in replay or resume mode, rename the old log and html files."""
     92         if self.replay_dir or self.resume_dir:
     93             for file_type in ['*.log', '*.html']:
     94                 path_names = os.path.join(self.output.log_dir, file_type)
     95                 for old_path_name in glob.glob(path_names):
     96                     new_path_name = '.'.join([old_path_name, 'old'])
     97                     os.rename(old_path_name, new_path_name)
     98 
     99     def _is_robot_mode(self):
    100         return self.robot.is_robot_action_mode() or self.mode == MODE.ROBOT_SIM
    101 
    102     def _get_gesture_names(self):
    103         """Determine the gesture names based on the mode."""
    104         if self.mode == MODE.QUICKSTEP:
    105             return conf.gesture_names_quickstep
    106         elif self.mode == MODE.NOISE:
    107             return conf.gesture_names_noise_extended
    108         elif self._is_robot_mode():
    109             # The mode could be MODE.ROBOT or MODE.ROBOT_SIM.
    110             # The same gesture names list is used in both modes.
    111             return conf.gesture_names_robot[self.device_type]
    112         elif self.mode == MODE.MANUAL:
    113             # Define the manual list which is gesture_names_complete:
    114             # gesture_names_robot - gesture_names_equipment_required
    115             manual_set = (set(conf.gesture_names_complete[self.device_type]) -
    116                           set(conf.gesture_names_robot[self.device_type]))
    117             return list(manual_set - set(conf.gesture_names_fngenerator_required))
    118 
    119         elif self.mode == MODE.CALIBRATION:
    120             return conf.gesture_names_calibration
    121         else:
    122             # Filter out tests that need a function generator for COMPLETE mode
    123             # unless they've indicated that they have one
    124             return [n for n in conf.gesture_names_complete[self.device_type]
    125                     if (self.fngenerator_only or
    126                         n not in conf.gesture_names_fngenerator_required)]
    127 
    128     def _non_blocking_open(self, filename):
    129         """Open the file in non-blocing mode."""
    130         fd = open(filename)
    131         fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
    132         return fd
    133 
    134     def _non_blocking_read(self, dev, fd):
    135         """Non-blocking read on fd."""
    136         try:
    137             dev.read(fd)
    138             event = (dev.tv_sec, dev.tv_usec, dev.type, dev.code, dev.value)
    139         except Exception, e:
    140             event = None
    141         return event
    142 
    143     def _reopen_system_device(self):
    144         """Close the device and open a new one."""
    145         self.system_device.close()
    146         self.system_device = open(self.device_node)
    147         self.system_device = self._non_blocking_open(self.device_node)
    148 
    149     def _set_static_prompt_messages(self):
    150         """Set static prompt messages."""
    151         # Prompt for next gesture.
    152         self._prompt_next = (
    153                 "Press SPACE to save this file and go to next test,\n"
    154                 "      'm'   to save this file and record again,\n"
    155                 "      'd'   to delete this file and try again,\n"
    156                 "      'x'   to discard this file and exit.")
    157 
    158         # Prompt to see test result through timeout callback.
    159         self._prompt_result = (
    160                 "Perform the gesture now.\n"
    161                 "See the test result on the right after finger lifted.\n"
    162                 "Or press 'x' to exit.")
    163 
    164     def _get_prompt_abnormal_gestures(self, warn_msg):
    165         """Prompt for next gesture."""
    166         prompt = '\n'.join(
    167                 ["It is very likely that you perform a WRONG gesture!",
    168                  warn_msg,
    169                  "Press 'd'   to delete this file and try again (recommended),",
    170                  "      SPACE to save this file if you are sure it's correct,",
    171                  "      'x'   to discard this file and exit."])
    172         return prompt
    173 
    174     def _get_prompt_no_data(self):
    175         """Prompt to remind user of performing gestures."""
    176         prompt = ("You need to perform the specified gestures "
    177                   "before pressing SPACE.\n")
    178         return prompt + self._prompt_result
    179 
    180     def _get_record_cmd(self):
    181         """Get the device event record command."""
    182         # Run mtplot with settings to disable clearing the display if the robot
    183         # clicks the pad, and adding a visible click indicator in the output
    184         self.record_program = 'mtplot -s1 -c0 -m0'
    185         if not common_util.program_exists(self.record_program):
    186             msg = 'Error: the program "%s" does not exist in $PATH.'
    187             self.output.print_report(msg % self.record_program)
    188             exit(1)
    189 
    190         display_name = firmware_utils.get_display_name()
    191         self.geometry_str = '%dx%d+%d+%d' % self.device_geometry
    192         format_str = '%s %s -d %s -g %s'
    193         self.record_cmd = format_str % (self.record_program,
    194                                         self.device_node,
    195                                         display_name,
    196                                         self.geometry_str)
    197         self.output.print_report('Record program: %s' % self.record_cmd)
    198 
    199     def _span_seq(self, seq1, seq2):
    200         """Span sequence seq1 over sequence seq2.
    201 
    202         E.g., seq1 = (('a', 'b'), 'c')
    203               seq2 = ('1', ('2', '3'))
    204               res = (('a', 'b', '1'), ('a', 'b', '2', '3'),
    205                      ('c', '1'), ('c', '2', '3'))
    206         E.g., seq1 = ('a', 'b')
    207               seq2 = ('1', '2', '3')
    208               res  = (('a', '1'), ('a', '2'), ('a', '3'),
    209                       ('b', '1'), ('b', '2'), ('b', '3'))
    210         E.g., seq1 = (('a', 'b'), ('c', 'd'))
    211               seq2 = ('1', '2', '3')
    212               res  = (('a', 'b', '1'), ('a', 'b', '2'), ('a', 'b', '3'),
    213                       ('c', 'd', '1'), ('c', 'd', '2'), ('c', 'd', '3'))
    214         """
    215         to_list = lambda s: list(s) if isinstance(s, tuple) else [s]
    216         return tuple(tuple(to_list(s1) + to_list(s2)) for s1 in seq1
    217                                                       for s2 in seq2)
    218 
    219     def span_variations(self, seq):
    220         """Span the variations of a gesture."""
    221         if seq is None:
    222             return (None,)
    223         elif isinstance(seq[0], tuple):
    224             return reduce(self._span_seq, seq)
    225         else:
    226             return seq
    227 
    228     def _stop(self):
    229         """Terminate the recording process."""
    230         self.record_proc.poll()
    231         # Terminate the process only when it was not terminated yet.
    232         if self.record_proc.returncode is None:
    233             self.record_proc.terminate()
    234             self.record_proc.wait()
    235         self.output.print_window('')
    236 
    237     def _get_gesture_image_name(self):
    238         """Get the gesture file base name without file extension."""
    239         filepath = os.path.splitext(self.gesture_file_name)[0]
    240         self.gesture_image_name = filepath + '.png'
    241         return filepath
    242 
    243     def _close_gesture_file(self):
    244         """Close the gesture file."""
    245         if self.gesture_file.closed:
    246             return
    247 
    248         filename = self.gesture_file.name
    249         self.gesture_file.close()
    250 
    251         # Strip off the header of the gesture file.
    252         #
    253         # Input driver version is 1.0.1
    254         # Input device ID: bus 0x18 vendor 0x0 product 0x0 version 0x0
    255         # Input device name: "Atmel maXTouch Touchpad"
    256         # ...
    257         # Testing ... (interrupt to exit)
    258         # Event: time 519.855, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID),
    259         #                                       value 884
    260         #
    261         tmp_filename = filename + '.tmp'
    262         os.rename(filename, tmp_filename)
    263         with open(tmp_filename) as src_f:
    264             with open(filename, 'w') as dst_f:
    265                 for line in src_f:
    266                     if line.startswith('Event:'):
    267                         dst_f.write(line)
    268         os.remove(tmp_filename)
    269 
    270     def _stop_record_and_post_image(self):
    271         """Terminate the recording process."""
    272         if self.record_new_file:
    273             self._close_gesture_file()
    274             self.screen_shot.dump_root(self._get_gesture_image_name())
    275             self.record_proc.terminate()
    276             self.record_proc.wait()
    277         else:
    278             self._get_gesture_image_name()
    279         self.win.set_image(self.gesture_image_name)
    280 
    281     def _create_prompt(self, test, variation):
    282         """Create a color prompt."""
    283         prompt = test.prompt
    284         if isinstance(variation, tuple):
    285             subprompt = reduce(lambda s1, s2: s1 + s2,
    286                                tuple(test.subprompt[s] for s in variation))
    287         elif variation is None or test.subprompt is None:
    288             subprompt = None
    289         else:
    290             subprompt = test.subprompt[variation]
    291 
    292         if subprompt is None:
    293             color_prompt = prompt
    294             monochrome_prompt = prompt
    295         else:
    296             color_prompt = mini_color.color_string(prompt, '{', '}', 'green')
    297             color_prompt = color_prompt.format(*subprompt)
    298             monochrome_prompt = prompt.format(*subprompt)
    299 
    300         color_msg_format = mini_color.color_string('\n<%s>:\n%s%s', '<', '>',
    301                                                    'blue')
    302         color_msg = color_msg_format % (test.name, self.prefix_space,
    303                                         color_prompt)
    304         msg = '%s: %s' % (test.name, monochrome_prompt)
    305 
    306         glog = firmware_log.GestureLog()
    307         glog.name = test.name
    308         glog.variation = variation
    309         glog.prompt = monochrome_prompt
    310 
    311         return (msg, color_msg, glog)
    312 
    313     def _choice_exit(self):
    314         """Procedure to exit."""
    315         self._stop()
    316         if os.path.exists(self.gesture_file_name):
    317             os.remove(self.gesture_file_name)
    318             self.output.print_report(self.deleted_msg)
    319 
    320     def _stop_record_and_rm_file(self):
    321         """Stop recording process and remove the current gesture file."""
    322         self._stop()
    323         if os.path.exists(self.gesture_file_name):
    324             os.remove(self.gesture_file_name)
    325             self.output.print_report(self.deleted_msg)
    326 
    327     def _create_gesture_file_name(self, gesture, variation):
    328         """Create the gesture file name based on its variation.
    329 
    330         Examples of different levels of file naming:
    331             Primary name:
    332                 pinch_to_zoom.zoom_in-lumpy-fw_11.27
    333             Root name:
    334                 pinch_to_zoom.zoom_in-lumpy-fw_11.27-manual-20130221_050510
    335             Base name:
    336                 pinch_to_zoom.zoom_in-lumpy-fw_11.27-manual-20130221_050510.dat
    337         """
    338         if variation is None:
    339             gesture_name = gesture.name
    340         else:
    341             if type(variation) is tuple:
    342                 name_list = [gesture.name,] + list(variation)
    343             else:
    344                 name_list = [gesture.name, variation]
    345             gesture_name = '.'.join(name_list)
    346 
    347         self.primary_name = conf.filename.sep.join([
    348                 gesture_name,
    349                 self.board,
    350                 conf.fw_prefix + self.firmware_version])
    351         root_name = conf.filename.sep.join([
    352                 self.primary_name,
    353                 self.mode,
    354                 firmware_utils.get_current_time_str()])
    355         basename = '.'.join([root_name, conf.filename.ext])
    356         return basename
    357 
    358     def _add_scores(self, new_scores):
    359         """Add the new scores of a single gesture to the scores list."""
    360         if new_scores is not None:
    361             self.scores += new_scores
    362 
    363     def _final_scores(self, scores):
    364         """Print the final score."""
    365         # Note: conf.score_aggregator uses a function in fuzzy module.
    366         final_score = eval(conf.score_aggregator)(scores)
    367         self.output.print_report('\nFinal score: %s\n' % str(final_score))
    368 
    369     def _robot_action(self):
    370         """Control the robot to perform the action."""
    371         if self._is_robot_mode() or self.robot.is_manual_noise_test_mode():
    372             self.robot.configure_noise(self.gesture, self.variation)
    373 
    374         if self._is_robot_mode():
    375             self.robot.control(self.gesture, self.variation)
    376             # Once the script terminates start a timeout to clean up if one
    377             # hasn't already been set to keep the test suite from hanging.
    378             if not self.gesture_begins_flag:
    379                 self.win.register_timeout_add(self.gesture_timeout_callback,
    380                                               self.gesture.timeout)
    381 
    382     def _handle_user_choice_save_after_parsing(self, next_gesture=True):
    383         """Handle user choice for saving the parsed gesture file."""
    384         self.output.print_window('')
    385         if self.use_existent_event_file_flag or self.recording:
    386             if self.saved_msg:
    387                 self.output.print_report(self.saved_msg)
    388             if self.new_scores:
    389                 self._add_scores(self.new_scores)
    390             self.output.report_html.insert_image(self.gesture_image_name)
    391             self.output.report_html.flush()
    392         # After flushing to report_html, reset the gesture_image_name so that
    393         # it will not be reused by next gesture variation accidentally.
    394         self.gesture_image_name = None
    395 
    396         if self._pre_setup_this_gesture_variation(next_gesture=next_gesture):
    397             # There are more gestures.
    398             self._setup_this_gesture_variation()
    399             self._robot_action()
    400         else:
    401             # No more gesture.
    402             self._final_scores(self.scores)
    403             self.output.stop()
    404             self.output.report_html.stop()
    405             self.win.stop()
    406         self.packets = None
    407 
    408     def _handle_user_choice_discard_after_parsing(self):
    409         """Handle user choice for discarding the parsed gesture file."""
    410         self.output.print_window('')
    411         self._setup_this_gesture_variation()
    412         self._robot_action()
    413         self.packets = None
    414 
    415     def _handle_user_choice_exit_after_parsing(self):
    416         """Handle user choice to exit after the gesture file is parsed."""
    417         self._stop_record_and_rm_file()
    418         self.output.stop()
    419         self.output.report_html.stop()
    420         self.win.stop()
    421 
    422     def check_for_wrong_number_of_fingers(self, details):
    423         flag_found = False
    424         try:
    425             position = details.index('CountTrackingIDValidator')
    426         except ValueError as e:
    427             return None
    428 
    429         # An example of the count of tracking IDs:
    430         #     '    count of trackid IDs: 1'
    431         number_tracking_ids = int(details[position + 1].split()[-1])
    432         # An example of the criteria_str looks like:
    433         #     '    criteria_str: == 2'
    434         criteria = int(details[position + 2].split()[-1])
    435         if number_tracking_ids < criteria:
    436             print '  CountTrackingIDValidator: '
    437             print '  number_tracking_ids: ', number_tracking_ids
    438             print '  criteria: ', criteria
    439             print '  number_tracking_ids should be larger!'
    440             msg = 'Number of Tracking IDs should be %d instead of %d'
    441             return msg % (criteria, number_tracking_ids)
    442         return None
    443 
    444     def _empty_packets_is_legal_result(self):
    445         return ('tap' in self.gesture.name and self._is_robot_mode())
    446 
    447     def _handle_user_choice_validate_before_parsing(self):
    448         """Handle user choice for validating before gesture file is parsed."""
    449         # Parse the device events. Make sure there are events.
    450         self.packets = self.parser.parse_file(self.gesture_file_name)
    451         if self.packets or self._empty_packets_is_legal_result():
    452             # Validate this gesture and get the results.
    453             (self.new_scores, msg_list, vlogs) = validators.validate(
    454                     self.packets, self.gesture, self.variation)
    455 
    456             # If the number of tracking IDs is less than the expected value,
    457             # the user probably made a wrong gesture.
    458             error = self.check_for_wrong_number_of_fingers(msg_list)
    459             if error:
    460                 prompt = self._get_prompt_abnormal_gestures(error)
    461                 color = 'red'
    462             else:
    463                 prompt = self._prompt_next
    464                 color = 'black'
    465 
    466             self.output.print_window(msg_list)
    467             self.output.buffer_report(msg_list)
    468             self.output.report_html.insert_validator_logs(vlogs)
    469             self.win.set_prompt(prompt, color=color)
    470             print prompt
    471             self._stop_record_and_post_image()
    472         else:
    473             self.win.set_prompt(self._get_prompt_no_data(), color='red')
    474 
    475     def _handle_user_choice_exit_before_parsing(self):
    476         """Handle user choice to exit before the gesture file is parsed."""
    477         self._close_gesture_file()
    478         self._handle_user_choice_exit_after_parsing()
    479 
    480     def _is_parsing_gesture_file_done(self):
    481         """Is parsing the gesture file done?"""
    482         return self.packets is not None
    483 
    484     def _is_arrow_key(self, choice):
    485         """Is this an arrow key?"""
    486         return (choice in TFK.ARROW_KEY_LIST)
    487 
    488     def user_choice_callback(self, fd, condition):
    489         """A callback to handle the key pressed by the user.
    490 
    491         This is the primary GUI event-driven method handling the user input.
    492         """
    493         choice = self.keyboard.get_key_press_event(fd)
    494         if choice:
    495             self._handle_keyboard_event(choice)
    496         return True
    497 
    498     def _handle_keyboard_event(self, choice):
    499         """Handle the keyboard event."""
    500         if self._is_arrow_key(choice):
    501             self.win.scroll(choice)
    502         elif self.robot_waiting:
    503             # The user wants the robot to start its action.
    504             if choice in (TFK.SAVE, TFK.SAVE2):
    505                 self.robot_waiting = False
    506                 self._robot_action()
    507             # The user wants to exit.
    508             elif choice == TFK.EXIT:
    509                 self._handle_user_choice_exit_after_parsing()
    510         elif self._is_parsing_gesture_file_done():
    511             # Save this gesture file and go to next gesture.
    512             if choice in (TFK.SAVE, TFK.SAVE2):
    513                 self._handle_user_choice_save_after_parsing()
    514             # Save this file and perform the same gesture again.
    515             elif choice == TFK.MORE:
    516                 self._handle_user_choice_save_after_parsing(next_gesture=False)
    517             # Discard this file and perform the gesture again.
    518             elif choice == TFK.DISCARD:
    519                 self._handle_user_choice_discard_after_parsing()
    520             # The user wants to exit.
    521             elif choice == TFK.EXIT:
    522                 self._handle_user_choice_exit_after_parsing()
    523             # The user presses any wrong key.
    524             else:
    525                 self.win.set_prompt(self._prompt_next, color='red')
    526         else:
    527             if choice == TFK.EXIT:
    528                 self._handle_user_choice_exit_before_parsing()
    529             # The user presses any wrong key.
    530             else:
    531                 self.win.set_prompt(self._prompt_result, color='red')
    532 
    533     def _get_all_gesture_variations(self, simplified):
    534         """Get all variations for all gestures."""
    535         gesture_variations_list = []
    536         self.variations_dict = {}
    537         for gesture in self.gesture_list:
    538             variations_list = []
    539             variations = self.span_variations(gesture.variations)
    540             for variation in variations:
    541                 gesture_variations_list.append((gesture, variation))
    542                 variations_list.append(variation)
    543                 if simplified:
    544                     break
    545             self.variations_dict[gesture.name] = variations_list
    546         self.gesture_variations = iter(gesture_variations_list)
    547 
    548     def gesture_timeout_callback(self):
    549         """A callback watching whether a gesture has timed out."""
    550         if self.replay_dir:
    551             # There are event files to replay for this gesture variation.
    552             if self.use_existent_event_file_flag:
    553                 self._handle_user_choice_validate_before_parsing()
    554             self._handle_user_choice_save_after_parsing(next_gesture=True)
    555             return False
    556 
    557         # A gesture is stopped only when two conditions are met simultaneously:
    558         # (1) there are no reported packets for a timeout interval, and
    559         # (2) the number of tracking IDs is 0.
    560         elif (self.gesture_continues_flag or
    561             not self.mtb_evemu.all_fingers_leaving()):
    562             self.gesture_continues_flag = False
    563             return True
    564 
    565         else:
    566             self._handle_user_choice_validate_before_parsing()
    567             self.win.remove_event_source(self.gesture_file_watch_tag)
    568             if self._is_robot_mode():
    569                 self._handle_keyboard_event(TFK.SAVE)
    570             return False
    571 
    572     def gesture_file_watch_callback(self, fd, condition, evdev_device):
    573         """A callback to watch the device input."""
    574         # Read the device node continuously until end
    575         event = True
    576         while event:
    577             event = self._non_blocking_read(evdev_device, fd)
    578             if event:
    579                 self.mtb_evemu.process_event(event)
    580 
    581         self.gesture_continues_flag = True
    582         if (not self.gesture_begins_flag):
    583             self.gesture_begins_flag = True
    584             self.win.register_timeout_add(self.gesture_timeout_callback,
    585                                           self.gesture.timeout)
    586         return True
    587 
    588     def init_gesture_setup_callback(self, widget, event):
    589         """A callback to set up environment before a user starts a gesture."""
    590         if not self.init_flag:
    591             self.init_flag = True
    592             self._pre_setup_this_gesture_variation()
    593             self._setup_this_gesture_variation()
    594             self._robot_action()
    595 
    596     def _get_existent_event_files(self):
    597         """Get the existent event files that starts with the primary_name."""
    598         primary_pathnames = os.path.join(self.output.log_dir,
    599                                          self.primary_name + '*.dat')
    600         self.primary_gesture_files = glob.glob(primary_pathnames)
    601         # Reverse sorting the file list so that we could pop from the tail.
    602         self.primary_gesture_files.sort()
    603         self.primary_gesture_files.reverse()
    604 
    605     def _use_existent_event_file(self):
    606         """If the replay flag is set in the command line, and there exists a
    607         file(s) with the same primary name, then use the existent file(s)
    608         instead of recording a new one.
    609         """
    610         if self.primary_gesture_files:
    611             self.gesture_file_name = self.primary_gesture_files.pop()
    612             return True
    613         return False
    614 
    615     def _pre_setup_this_gesture_variation(self, next_gesture=True):
    616         """Get gesture, variation, filename, prompt, etc."""
    617         next_gesture_first_time = False
    618         if next_gesture:
    619             if self.gv_count < self.iterations:
    620                 self.gv_count += 1
    621             else:
    622                 self.gv_count = 1
    623                 gesture_variation = next(self.gesture_variations, None)
    624                 if gesture_variation is None:
    625                     return False
    626                 self.gesture, self.variation = gesture_variation
    627                 next_gesture_first_time = True
    628 
    629         basename = self._create_gesture_file_name(self.gesture, self.variation)
    630         if next_gesture_first_time:
    631             self._get_existent_event_files()
    632 
    633         if self.replay_dir or self.resume_dir:
    634             self.use_existent_event_file_flag = self._use_existent_event_file()
    635 
    636         if ((not self.replay_dir and not self.resume_dir) or
    637                 (self.resume_dir and not self.use_existent_event_file_flag)):
    638             self.gesture_file_name = os.path.join(self.output.log_dir, basename)
    639             self.saved_msg = '(saved: %s)\n' % self.gesture_file_name
    640             self.deleted_msg = '(deleted: %s)\n' % self.gesture_file_name
    641         else:
    642             self.saved_msg = None
    643             self.deleted_msg = None
    644         self.new_scores = None
    645 
    646         if self.robot.is_robot_action_mode() or self.robot.is_manual_noise_test_mode():
    647             self.robot.turn_off_noise()
    648 
    649         (msg, color_msg, glog) = self._create_prompt(self.gesture,
    650                                                      self.variation)
    651         self.win.set_gesture_name(msg)
    652         self.output.report_html.insert_gesture_log(glog)
    653         print color_msg
    654         self.output.print_report(color_msg)
    655         return True
    656 
    657     def _setup_this_gesture_variation(self):
    658         """Set up the recording process or use an existent event data file."""
    659         if self.replay_dir:
    660             self.record_new_file = False
    661             self.win.register_timeout_add(self.gesture_timeout_callback, 0)
    662             return
    663 
    664         if self.resume_dir and self.use_existent_event_file_flag:
    665             self.record_new_file = False
    666             self._handle_user_choice_validate_before_parsing()
    667             self._handle_keyboard_event(TFK.SAVE)
    668             return
    669 
    670         # Initiate the MtbSanityValidator. Note that this should be done each
    671         # time just before recording the gesture file since it requires a
    672         # snapshot of the input device before any finger touching the device.
    673         self.gesture.mtb_sanity_validator = validators.MtbSanityValidator()
    674 
    675         # Now, we will record a new gesture event file.
    676         # Fork a new process for mtplot. Add io watch for the gesture file.
    677         self.record_new_file = True
    678         self.gesture_file = open(self.gesture_file_name, 'w')
    679         self.record_proc = subprocess.Popen(self.record_cmd.split(),
    680                                             stdout=self.gesture_file)
    681 
    682         # Watch if data come in to the monitored file.
    683         self.gesture_begins_flag = False
    684         self._reopen_system_device()
    685         self.gesture_file_watch_tag = self.win.register_io_add_watch(
    686                 self.gesture_file_watch_callback, self.system_device,
    687                 self.evdev_device)
    688