Support forcefully generating non-AB packages.

Force generate a non-A/B update package when requested.

Bug: 154344887
Test: ota_from_target_files.py --force_non_ab ...
Test: apply it as well

Change-Id: I5e81eb161722e07ef50081b6a16685cbc9963ae2
(cherry picked from commit 7169f754cc4b806c54a049b838355483575c1785)
Merged-In: I5e81eb161722e07ef50081b6a16685cbc9963ae2
diff --git a/core/Makefile b/core/Makefile
index 7feec37..1d9b99c 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -3365,10 +3365,10 @@
 # When building a standalone recovery image for non-A/B devices, recovery image must be self-signed
 # to be verified independently, and cannot be chained into vbmeta.img. See the link below for
 # details.
-ifneq ($(AB_OTA_UPDATER),true)
+ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
 ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 $(if $(BOARD_AVB_RECOVERY_KEY_PATH),,\
-    $(error BOARD_AVB_RECOVERY_KEY_PATH must be defined for non-A/B devices. \
+    $(error BOARD_AVB_RECOVERY_KEY_PATH must be defined for if non-A/B is supported. \
             See https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery))
 endif
 endif
@@ -3459,7 +3459,7 @@
 
 # The recovery partition in non-A/B devices should be verified separately. Skip adding the chain
 # partition descriptor for recovery partition into vbmeta.img.
-$(if $(or $(filter true,$(AB_OTA_UPDATER)),$(filter-out recovery,$(part))),\
+$(if $(or $(filter-out true,$(TARGET_OTA_ALLOW_NON_AB)),$(filter-out recovery,$(part))),\
     $(eval INTERNAL_AVB_MAKE_VBMETA_IMAGE_ARGS += \
         --chain_partition $(part):$($(_rollback_index_location)):$(AVB_CHAIN_KEY_DIR)/$(part).avbpubkey))
 
@@ -4254,6 +4254,9 @@
 	$(hide) echo "build_type=$(TARGET_BUILD_VARIANT)" >> $@
 	$(hide) echo "ab_update=true" >> $@
 endif
+ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
+	$(hide) echo "allow_non_ab=true" >> $@
+endif
 ifdef BOARD_PREBUILT_DTBOIMAGE
 	$(hide) echo "has_dtbo=true" >> $@
 ifeq ($(BOARD_AVB_ENABLE),true)
@@ -4331,10 +4334,13 @@
 
 ifeq ($(AB_OTA_UPDATER),true)
 updater_dep := system/update_engine/update_engine.conf
-else
-# Build OTA tools if not using the AB Updater.
+endif
+
+# Build OTA tools if non-A/B is allowed
+ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
 updater_dep := $(built_ota_tools)
 endif
+
 $(BUILT_TARGET_FILES_PACKAGE): $(updater_dep)
 
 # If we are using recovery as boot, output recovery files to BOOT/.
@@ -4610,7 +4616,7 @@
 	@# Extra contents of the OTA package
 	$(hide) mkdir -p $(zip_root)/OTA
 	$(hide) cp $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/
-ifneq ($(AB_OTA_UPDATER),true)
+ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
 ifneq ($(built_ota_tools),)
 	$(hide) mkdir -p $(zip_root)/OTA/bin
 	$(hide) cp $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/
@@ -4647,7 +4653,7 @@
 	$(hide) cp $(PRODUCT_ODM_BASE_FS_PATH) \
 	  $(zip_root)/META/$(notdir $(PRODUCT_ODM_BASE_FS_PATH))
 endif
-ifneq ($(AB_OTA_UPDATER),true)
+ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
 ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 	$(hide) PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH MKBOOTIMG=$(MKBOOTIMG) \
 	    $(MAKE_RECOVERY_PATCH) $(zip_root) $(zip_root)
diff --git a/core/board_config.mk b/core/board_config.mk
index 2279c3f..0ff28c5 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -550,13 +550,31 @@
 .KATI_READONLY := BUILDING_ODM_IMAGE
 
 ###########################################
-# Ensure that only TARGET_RECOVERY_UPDATER_LIBS *or* AB_OTA_UPDATER is set.
+# Ensure consistency among TARGET_RECOVERY_UPDATER_LIBS, AB_OTA_UPDATER, and PRODUCT_OTA_FORCE_NON_AB_PACKAGE.
 TARGET_RECOVERY_UPDATER_LIBS ?=
 AB_OTA_UPDATER ?=
 .KATI_READONLY := TARGET_RECOVERY_UPDATER_LIBS AB_OTA_UPDATER
-ifeq ($(AB_OTA_UPDATER),true)
+
+# Ensure that if PRODUCT_OTA_FORCE_NON_AB_PACKAGE == true, then AB_OTA_UPDATER must be true
+ifeq ($(PRODUCT_OTA_FORCE_NON_AB_PACKAGE),true)
+  ifneq ($(AB_OTA_UPDATER),true)
+    $(error AB_OTA_UPDATER must be set to true when PRODUCT_OTA_FORCE_NON_AB_PACKAGE is true)
+  endif
+endif
+
+# In some configurations, A/B and non-A/B may coexist. Check TARGET_OTA_ALLOW_NON_AB
+# to see if non-A/B is supported.
+TARGET_OTA_ALLOW_NON_AB := false
+ifneq ($(AB_OTA_UPDATER),true)
+  TARGET_OTA_ALLOW_NON_AB := true
+else ifeq ($(PRODUCT_OTA_FORCE_NON_AB_PACKAGE),true)
+  TARGET_OTA_ALLOW_NON_AB := true
+endif
+.KATI_READONLY := TARGET_OTA_ALLOW_NON_AB
+
+ifneq ($(TARGET_OTA_ALLOW_NON_AB),true)
   ifneq ($(strip $(TARGET_RECOVERY_UPDATER_LIBS)),)
-    $(error Do not use TARGET_RECOVERY_UPDATER_LIBS when using AB_OTA_UPDATER)
+    $(error Do not use TARGET_RECOVERY_UPDATER_LIBS when using TARGET_OTA_ALLOW_NON_AB)
   endif
 endif
 
diff --git a/core/product.mk b/core/product.mk
index ea9be72..0aa07ef 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -393,6 +393,13 @@
 # If set, device retrofits virtual A/B.
 _product_single_value_vars += PRODUCT_VIRTUAL_AB_OTA_RETROFIT
 
+# If set, forcefully generate a non-A/B update package.
+# Note: A device configuration should inherit from virtual_ab_ota_plus_non_ab.mk
+# instead of setting this variable directly.
+# Note: Use TARGET_OTA_ALLOW_NON_AB in the build system because
+# TARGET_OTA_ALLOW_NON_AB takes the value of AB_OTA_UPDATER into account.
+_product_single_value_vars += PRODUCT_OTA_FORCE_NON_AB_PACKAGE
+
 # If set, Java module in product partition cannot use hidden APIs.
 _product_single_value_vars += PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE
 
diff --git a/target/product/virtual_ab_ota_plus_non_ab.mk b/target/product/virtual_ab_ota_plus_non_ab.mk
new file mode 100644
index 0000000..325d75e
--- /dev/null
+++ b/target/product/virtual_ab_ota_plus_non_ab.mk
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2020 The Android Open-Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota.mk)
+
+PRODUCT_OTA_FORCE_NON_AB_PACKAGE := true
+
+PRODUCT_PROPERTY_OVERRIDES += ro.virtual_ab.allow_non_ab=true
diff --git a/tools/releasetools/blockimgdiff.py b/tools/releasetools/blockimgdiff.py
index 72f065d..8b6a690 100644
--- a/tools/releasetools/blockimgdiff.py
+++ b/tools/releasetools/blockimgdiff.py
@@ -1558,6 +1558,7 @@
     split_large_apks = []
     cache_size = common.OPTIONS.cache_size
     split_threshold = 0.125
+    assert cache_size is not None
     max_blocks_per_transfer = int(cache_size * split_threshold /
                                   self.tgt.blocksize)
     empty = RangeSet()
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 2b04334..9f48f78 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -848,12 +848,13 @@
 def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
                       system_root_image=False):
   class Partition(object):
-    def __init__(self, mount_point, fs_type, device, length, context):
+    def __init__(self, mount_point, fs_type, device, length, context, slotselect):
       self.mount_point = mount_point
       self.fs_type = fs_type
       self.device = device
       self.length = length
       self.context = context
+      self.slotselect = slotselect
 
   try:
     data = read_helper(recovery_fstab_path)
@@ -881,10 +882,13 @@
 
     # It's a good line, parse it.
     length = 0
+    slotselect = False
     options = options.split(",")
     for i in options:
       if i.startswith("length="):
         length = int(i[7:])
+      elif i == "slotselect":
+        slotselect = True
       else:
         # Ignore all unknown options in the unified fstab.
         continue
@@ -898,7 +902,8 @@
 
     mount_point = pieces[1]
     d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
-                               device=pieces[0], length=length, context=context)
+                               device=pieces[0], length=length, context=context,
+                               slotselect=slotselect)
 
   # / is used for the system mount point when the root directory is included in
   # system. Other areas assume system is always at "/system" so point /system
@@ -913,7 +918,8 @@
   """Finds the path to recovery fstab and loads its contents."""
   # recovery fstab is only meaningful when installing an update via recovery
   # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
-  if info_dict.get('ab_update') == 'true':
+  if info_dict.get('ab_update') == 'true' and \
+     info_dict.get("allow_non_ab") != "true":
     return None
 
   # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index ad001d1..16d7485 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -78,6 +78,13 @@
       Write a copy of the metadata to a separate file. Therefore, users can
       read the post build fingerprint without extracting the OTA package.
 
+  --force_non_ab
+      This flag can only be set on an A/B device that also supports non-A/B
+      updates. Implies --two_step.
+      If set, generate that non-A/B update package.
+      If not set, generates A/B package for A/B device and non-A/B package for
+      non-A/B device.
+
 Non-A/B OTA specific options
 
   -b  (--binary) <file>
@@ -251,6 +258,7 @@
 OPTIONS.output_metadata_path = None
 OPTIONS.disable_fec_computation = False
 OPTIONS.boot_variable_values = None
+OPTIONS.force_non_ab = False
 
 
 METADATA_NAME = 'META-INF/com/android/metadata'
@@ -933,7 +941,7 @@
           'ro.build.version.security_patch'),
   }
 
-  if target_info.is_ab:
+  if target_info.is_ab and not OPTIONS.force_non_ab:
     metadata['ota-type'] = 'AB'
     metadata['ota-required-cache'] = '0'
   else:
@@ -2067,6 +2075,8 @@
       OPTIONS.output_metadata_path = a
     elif o == "--disable_fec_computation":
       OPTIONS.disable_fec_computation = True
+    elif o == "--force_non_ab":
+      OPTIONS.force_non_ab = True
     else:
       return False
     return True
@@ -2103,6 +2113,7 @@
                                  "skip_compatibility_check",
                                  "output_metadata_path=",
                                  "disable_fec_computation",
+                                 "force_non_ab",
                              ], extra_option_handler=option_handler)
 
   if len(args) != 2:
@@ -2164,11 +2175,17 @@
     OPTIONS.skip_postinstall = True
 
   ab_update = OPTIONS.info_dict.get("ab_update") == "true"
+  allow_non_ab = OPTIONS.info_dict.get("allow_non_ab") == "true"
+  if OPTIONS.force_non_ab:
+    assert allow_non_ab, "--force_non_ab only allowed on devices that supports non-A/B"
+    assert ab_update, "--force_non_ab only allowed on A/B devices"
+
+  generate_ab = not OPTIONS.force_non_ab and ab_update
 
   # Use the default key to sign the package if not specified with package_key.
   # package_keys are needed on ab_updates, so always define them if an
-  # ab_update is getting created.
-  if not OPTIONS.no_signing or ab_update:
+  # A/B update is getting created.
+  if not OPTIONS.no_signing or generate_ab:
     if OPTIONS.package_key is None:
       OPTIONS.package_key = OPTIONS.info_dict.get(
           "default_system_dev_certificate",
@@ -2176,7 +2193,7 @@
     # Get signing keys
     OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
 
-  if ab_update:
+  if generate_ab:
     GenerateAbOtaPackage(
         target_file=args[0],
         output_file=args[1],