[ATFT] Add TEST_MODE and PROVISION_STEPS to config.

Include TEST_MODE option and PROVISION_STEPS in the configuration
file. These two are optional arguments. TEST_MODE is set to false
by default. If TEST_MODE is set to true, no prerequisite step check
would be done, user could do the provision step by any order. User
could also modify PROVISION_STEP to change the provision steps that
would be executed in the auto provisioning mode.

Change-Id: I48de20d4f05d1af9ef85e22b3e70e4013f445ce6
Test: Manual test and unit test.
Bug: b/79164608
diff --git a/at-factory-tool/atft.py b/at-factory-tool/atft.py
index 4fb1c9a..470554a 100644
--- a/at-factory-tool/atft.py
+++ b/at-factory-tool/atft.py
@@ -46,11 +46,6 @@
   from serialmapperwin import SerialMapper
 
 
-# If this is set to True, no prerequisites would be checked against manual
-# operation, such as you can do key provisioning before fusing the vboot key.
-TEST_MODE = False
-
-
 class AtftException(Exception):
   """The exception class to include device and operation information.
   """
@@ -376,6 +371,14 @@
     self.REBOOT_TIMEOUT = 0
     self.PRODUCT_ATTRIBUTE_FILE_EXTENSION = '*.atpa'
 
+    # If this is set to True, no prerequisites would be checked against manual
+    # operation, such as you can do key provisioning before fusing the vboot key.
+    self.TEST_MODE = False
+
+    # The steps included in the auto provisioning process.
+    self.PROVISION_STEPS = ["FuseVbootKey", "FusePermAttr", "LockAvb",
+                            "Provision"]
+
     config_file_path = os.path.join(self._GetCurrentPath(), self.CONFIG_FILE)
     if not os.path.exists(config_file_path):
       return None
@@ -398,6 +401,10 @@
       self.REBOOT_TIMEOUT = float(configs['REBOOT_TIMEOUT'])
       self.PRODUCT_ATTRIBUTE_FILE_EXTENSION = str(
           configs['PRODUCT_ATTRIBUTE_FILE_EXTENSION'])
+      if 'TEST_MODE' in configs:
+        self.TEST_MODE = configs['TEST_MODE']
+      if 'PROVISION_STEPS' in configs:
+        self.PROVISION_STEPS = configs['PROVISION_STEPS']
     except (KeyError, ValueError):
       return None
 
@@ -1242,6 +1249,27 @@
     self.StopRefresh()
     self.Destroy()
 
+  def _is_provision_success(self, target):
+    """Check if the target device has successfully finished provision steps.
+
+    Args:
+      target: The target device.
+    Returns:
+      success if the target device has already gone through the provision steps
+      successfully.
+    """
+    if len(self.PROVISION_STEPS) == 0:
+      return True;
+    if self.PROVISION_STEPS[-1] == 'Provision':
+      return target.provision_state.provisioned
+    if self.PROVISION_STEPS[-1] == 'LockAvb':
+      return target.provision_state.avb_locked
+    if self.PROVISION_STEPS[-1] == 'FusePermAttr':
+      return target.provision_state.avb_perm_attr_set
+    if self.PROVISION_STEPS[-1] == 'FuseVbootKey':
+      return target.provision_state.bootloader_locked
+    return True;
+
   def _HandleAutoProv(self):
     """Do the state transition for devices if in auto provisioning mode.
 
@@ -1249,7 +1277,7 @@
     # All idle devices -> waiting.
     for target_dev in self.atft_manager.target_devs:
       if (target_dev.serial_number not in self.auto_dev_serials and
-          target_dev.provision_status != ProvisionStatus.PROVISION_SUCCESS and
+          not self._is_provision_success(target_dev) and
           not ProvisionStatus.isFailed(target_dev.provision_status)
           ):
         self.auto_dev_serials.append(target_dev.serial_number)
@@ -1603,7 +1631,7 @@
       if not target:
         continue
       # Start state could be IDLE or FUSEVBOOT_FAILED
-      if (TEST_MODE or not target.provision_state.bootloader_locked):
+      if (self.TEST_MODE or not target.provision_state.bootloader_locked):
         target.provision_status = ProvisionStatus.WAITING
         pending_targets.append(target)
       else:
@@ -1721,7 +1749,7 @@
       # Start state could be FUSEVBOOT_SUCCESS or REBOOT_SUCCESS
       # or FUSEATTR_FAILED
       # Note: Reboot to check vboot is optional, user can skip that manually.
-      if (TEST_MODE or (
+      if (self.TEST_MODE or (
             target.provision_state.bootloader_locked and
             not target.provision_state.avb_perm_attr_set
           )):
@@ -1767,7 +1795,7 @@
       if not target:
         continue
       # Start state could be FUSEATTR_SUCCESS or LOCKAVB_FAIELD
-      if (TEST_MODE or(
+      if (self.TEST_MODE or(
             target.provision_state.bootloader_locked and
             target.provision_state.avb_perm_attr_set and
             not target.provision_state.avb_locked
@@ -1892,7 +1920,7 @@
         continue
       pending_targets.append(target_dev)
       status = target_dev.provision_status
-      if (TEST_MODE or (
+      if (self.TEST_MODE or (
           target_dev.provision_state.bootloader_locked and
           target_dev.provision_state.avb_perm_attr_set and
           target_dev.provision_state.avb_locked and
@@ -1946,6 +1974,7 @@
     """
     self.auto_prov_lock.acquire()
     serial = target.serial_number
+    i = 0
     while not ProvisionStatus.isFailed(target.provision_status):
       target = self.atft_manager.GetTargetDevice(serial)
       if not target:
@@ -1954,23 +1983,31 @@
       if not self.auto_prov:
         # Auto provision mode exited.
         break
-      if not target.provision_state.bootloader_locked:
+      if i == len(self.PROVISION_STEPS):
+        break
+      operation = self.PROVISION_STEPS[i]
+      i += 1
+      if (not target.provision_state.bootloader_locked and
+          operation == 'FuseVbootKey'):
         self._FuseVbootKeyTarget(target)
         continue
-      elif not target.provision_state.avb_perm_attr_set:
+      elif (not target.provision_state.avb_perm_attr_set and
+            operation == 'FusePermAttr'):
         self._FusePermAttrTarget(target)
         continue
-      elif not target.provision_state.avb_locked:
+      elif (not target.provision_state.avb_locked and
+            operation == 'LockAvb'):
         self._LockAvbTarget(target)
         continue
-      elif not target.provision_state.provisioned:
+      elif (not target.provision_state.provisioned and
+            operation == 'Provision'):
         self._ProvisionTarget(target)
         if self.atft_manager.GetATFAKeysLeft() == 0:
           # No keys left. If it's auto provisioning mode, exit.
           self._SendAlertEvent(self.ALERT_NO_KEYS_LEFT_LEAVE_PROV)
           self.toolbar.ToggleTool(self.ID_TOOL_PROVISION, False)
           self.OnToggleAutoProv(None)
-      break
+
     self.auto_dev_serials.remove(serial)
     self.auto_prov_lock.release()
 
diff --git a/at-factory-tool/atft_unittest.py b/at-factory-tool/atft_unittest.py
index 92b57a6..122704d 100644
--- a/at-factory-tool/atft_unittest.py
+++ b/at-factory-tool/atft_unittest.py
@@ -49,6 +49,10 @@
     self.LANGUAGE = 'ENG'
     self.REBOOT_TIMEOUT = 1.0
     self.PRODUCT_ATTRIBUTE_FILE_EXTENSION = '*.atpa'
+    # Disable the test mode. (This mode is just for usage test, not unit test)
+    self.TEST_MODE = False
+    self.PROVISION_STEPS = ["FuseVbootKey", "FusePermAttr", "LockAvb",
+                            "Provision"]
 
     return {}
 
@@ -95,8 +99,6 @@
     self.test_text_window = ''
     self.atfa_keys = None
     self.device_map = {}
-    # Disable the test mode. (This mode is just for usage test, not unit test)
-    atft.TEST_MODE = False
 
   def AppendTargetDevice(self, device):
     self.test_target_devs.append(device)
@@ -357,6 +359,7 @@
     mock_atft = MockAtft()
     test_dev1 = TestDeviceInfo(self.TEST_SERIAL1, self.TEST_LOCATION1,
                                ProvisionStatus.PROVISION_SUCCESS)
+    test_dev1.provision_state.provisioned = True
     test_dev2 = TestDeviceInfo(self.TEST_SERIAL2, self.TEST_LOCATION1,
                                ProvisionStatus.IDLE)
     mock_atft.atft_manager.target_devs = []