Merge changes I8be872f3,I13fe65bf
am: 29db248522

Change-Id: I64b90ca69e2cbb39bb809130a8dcab9ad4490d57
diff --git a/at-factory-tool/atft.py b/at-factory-tool/atft.py
index f50aeed..a6eeaba 100644
--- a/at-factory-tool/atft.py
+++ b/at-factory-tool/atft.py
@@ -3110,7 +3110,7 @@
     evt = Event(self.print_event, wx.ID_ANY, msg)
     wx.QueueEvent(self, evt)
 
-  def _StartOperation(self, operation, target):
+  def _StartOperation(self, operation, target, show_alert=True):
     if not target:
       self.PauseRefresh()
       return True
@@ -3120,9 +3120,11 @@
       self.PauseRefresh()
       return True
 
-    self._SendAlertEvent(
-        'Target: ' + str(target) + ' is currently in another operation: '  +
-        target.operation + '. Please try again later')
+    if show_alert:
+      self._SendAlertEvent(
+          'Unable to start operation: ' + operation + ', ' +
+          'Target: ' + str(target) + ' is currently in another operation: '  +
+          target.operation + '. Please try again later')
     return False
 
   def _EndOperation(self, target):
@@ -3898,10 +3900,9 @@
     self.auto_prov_lock.acquire()
     serial = target.serial_number
     i = 0
-    while not ProvisionStatus.isFailed(target.provision_status):
+    while True:
       target = self.atft_manager.GetTargetDevice(serial)
-      if not target:
-        # The target disappear somehow.
+      if not target or ProvisionStatus.isFailed(target.provision_status):
         break
       if not self.auto_prov:
         # Auto provision mode exited.
@@ -4069,7 +4070,7 @@
       # Should not reach here.
       return False
     operation = 'ATFA device prepare and download ' + file_type + ' file'
-    if not self._StartOperation(operation, atfa_dev):
+    if not self._StartOperation(operation, atfa_dev, show_alert):
       return False
     try:
       filepath = filepath.encode('utf-8')
diff --git a/at-factory-tool/atft_unittest.py b/at-factory-tool/atft_unittest.py
index 06d458f..d6926ad 100644
--- a/at-factory-tool/atft_unittest.py
+++ b/at-factory-tool/atft_unittest.py
@@ -687,6 +687,106 @@
     self.assertEqual(ProvisionStatus.REBOOT_FAILED, test_dev1.provision_status)
     mock_atft._SendOperationSucceedEvent.assert_not_called()
 
+  def mockGetTargetDeviceDisappear(self, dev):
+    # If the device disappear, return None as target device.
+    if self.target_device_disapper:
+      return None
+    else:
+      return dev
+
+  def mockDeviceRebootTimeout(self, target, auto_prov):
+    # After reboot, the device disappear.
+    self.target_device_disapper = True
+
+  # Test device reboot timeout and disappear from device list.
+  def testHandleStateTransitionRebootTimeout(self):
+    mock_atft = MockAtft()
+    self.target_device_disapper = False
+    mock_atft._SendOperationSucceedEvent = MagicMock()
+    test_dev1 = TestDeviceInfo(self.TEST_SERIAL1, self.TEST_LOCATION1,
+                               ProvisionStatus.WAITING)
+    mock_atft._FuseVbootKeyTarget = MagicMock()
+    mock_atft._FuseVbootKeyTarget.side_effect = self.mockDeviceRebootTimeout
+    mock_atft._FusePermAttrTarget = MagicMock()
+    mock_atft._FusePermAttrTarget.side_effect = (
+        lambda target=mock_atft, state=ProvisionStatus.FUSEATTR_SUCCESS:
+        self.MockStateChange(target, state))
+    mock_atft._LockAvbTarget = MagicMock()
+    mock_atft._LockAvbTarget.side_effect = (
+        lambda target=mock_atft, state=ProvisionStatus.LOCKAVB_SUCCESS:
+        self.MockStateChange(target, state))
+    mock_atft._ProvisionTarget = MagicMock()
+    mock_atft._ProvisionTarget.side_effect = (
+        lambda target, is_som_key, state=ProvisionStatus.PROVISION_SUCCESS:
+            self.MockStateChange(target, state))
+    mock_atft.auto_dev_serials = [self.TEST_SERIAL1]
+    mock_atft.auto_prov = True
+    mock_atft.atft_manager = MagicMock()
+    mock_atft.atft_manager.GetTargetDevice = MagicMock()
+    mock_atft.atft_manager.GetTargetDevice.side_effect = (
+        lambda serial, dev=test_dev1: self.mockGetTargetDeviceDisappear(dev))
+    mock_atft._HandleStateTransition(test_dev1)
+    self.assertEqual(
+        None, mock_atft.atft_manager.GetTargetDevice(self.TEST_SERIAL1))
+    mock_atft._SendOperationSucceedEvent.assert_not_called()
+
+  def mockGetTargetDeviceFuseVbootFailed(self, dev):
+    # If the device disappear, return None as target device.
+    if self.target_device_fuse_failed:
+      new_device = TestDeviceInfo(
+          dev.serial_number, dev.location, ProvisionStatus.FUSEVBOOT_FAILED)
+      return new_device
+    else:
+      return dev
+
+  def mockDeviceFuseVbootFailed(self, target, auto_prov):
+    # After reboot, the device disappear.
+    self.target_device_fuse_failed = True
+    target.provision_status = ProvisionStatus.REBOOT_ING
+
+  # Test fuse vboot key change target device state by creating a new target
+  # device instead of modifying the original one's state.
+  def testHandleStateTransitionTargetDeviceChange(self):
+    mock_atft = MockAtft()
+    self.target_device_fuse_failed = False
+    mock_atft._SendOperationSucceedEvent = MagicMock()
+    test_dev1 = TestDeviceInfo(self.TEST_SERIAL1, self.TEST_LOCATION1,
+                               ProvisionStatus.WAITING)
+    mock_atft._FuseVbootKeyTarget = MagicMock()
+    mock_atft._FuseVbootKeyTarget.side_effect = self.mockDeviceFuseVbootFailed
+    mock_atft._FusePermAttrTarget = MagicMock()
+    mock_atft._FusePermAttrTarget.side_effect = (
+        lambda target=mock_atft, state=ProvisionStatus.FUSEATTR_SUCCESS:
+        self.MockStateChange(target, state))
+    mock_atft._LockAvbTarget = MagicMock()
+    mock_atft._LockAvbTarget.side_effect = (
+        lambda target=mock_atft, state=ProvisionStatus.LOCKAVB_SUCCESS:
+        self.MockStateChange(target, state))
+    mock_atft._ProvisionTarget = MagicMock()
+    mock_atft._ProvisionTarget.side_effect = (
+        lambda target, is_som_key, state=ProvisionStatus.PROVISION_SUCCESS:
+            self.MockStateChange(target, state))
+    mock_atft.auto_dev_serials = [self.TEST_SERIAL1]
+    mock_atft.auto_prov = True
+    mock_atft.atft_manager = MagicMock()
+    mock_atft.atft_manager.GetTargetDevice = MagicMock()
+    mock_atft.atft_manager.GetTargetDevice.side_effect = (
+        lambda serial, dev=test_dev1: self.mockGetTargetDeviceFuseVbootFailed(
+            dev))
+    mock_atft._HandleStateTransition(test_dev1)
+
+    # The old target device is still in REBOOT_ING state.
+    self.assertEqual(ProvisionStatus.REBOOT_ING, test_dev1.provision_status)
+
+    # The new device is in FUSEVBOOT_FAILED state
+    self.assertEqual(
+        ProvisionStatus.FUSEVBOOT_FAILED,
+        mock_atft.atft_manager.GetTargetDevice(
+            self.TEST_SERIAL1).provision_status)
+    mock_atft._SendOperationSucceedEvent.assert_not_called()
+    # Next operation should not execute.
+    mock_atft._FusePermAttrTarget.assert_not_called()
+
   def testHandleStateTransitionFuseAttrFail(self):
     mock_atft = MockAtft()
     mock_atft._SendOperationSucceedEvent = MagicMock()