Merge "Allow RO build only for readonly mount points"
diff --git a/core/Makefile b/core/Makefile
index 77ded9c..a3b79a9 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -3933,7 +3933,7 @@
 INSTALLED_PVMFWIMAGE_TARGET := $(PRODUCT_OUT)/pvmfw.img
 INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET := $(PRODUCT_OUT)/pvmfw_embedded.avbpubkey
 INTERNAL_PVMFWIMAGE_FILES := $(call module-target-built-files,pvmfw_img)
-INTERNAL_PVMFW_EMBEDDED_AVBKEY := $(call module-target-built-files,pvmfw_sign_key)
+INTERNAL_PVMFW_EMBEDDED_AVBKEY := $(call module-target-built-files,pvmfw_embedded_key)
 
 $(call declare-1p-container,$(INSTALLED_PVMFWIMAGE_TARGET),)
 $(call declare-container-license-deps,$(INSTALLED_PVMFWIMAGE_TARGET),$(INTERNAL_PVMFWIMAGE_FILES),$(PRODUCT_OUT)/:/)
@@ -5045,6 +5045,10 @@
   $(sort $(shell find build/make/target/product/security -type f -name "*.x509.pem" -o \
       -name "*.pk8"))
 
+INTERNAL_OTATOOLS_PACKAGE_FILES += \
+  $(sort $(shell find packages/modules -type f -name "*.x509.pem" -o -name "*.pk8" -o -name \
+      "key.pem"))
+
 ifneq (,$(wildcard device))
 INTERNAL_OTATOOLS_PACKAGE_FILES += \
   $(sort $(shell find device $(wildcard vendor) -type f -name "*.pk8" -o -name "verifiedboot*" -o \
@@ -6824,12 +6828,26 @@
 	$(HOST_OUT_EXECUTABLES)/atree \
 	$(HOST_OUT_EXECUTABLES)/line_endings
 
+# The name of the subdir within the platforms dir of the sdk. One of:
+# - android-<SDK_INT> (stable base dessert SDKs)
+# - android-<CODENAME> (stable extension SDKs)
+# - android-<SDK_INT>-ext<EXT_INT> (codename SDKs)
+sdk_platform_dir_name := $(strip \
+  $(if $(filter REL,$(PLATFORM_VERSION_CODENAME)), \
+    $(if $(filter $(PLATFORM_SDK_EXTENSION_VERSION),$(PLATFORM_BASE_SDK_EXTENSION_VERSION)), \
+      android-$(PLATFORM_SDK_VERSION), \
+      android-$(PLATFORM_SDK_VERSION)-ext$(PLATFORM_SDK_EXTENSION_VERSION) \
+    ), \
+    android-$(PLATFORM_VERSION_CODENAME) \
+  ) \
+)
+
 INTERNAL_SDK_TARGET := $(sdk_dir)/$(sdk_name).zip
 $(INTERNAL_SDK_TARGET): PRIVATE_NAME := $(sdk_name)
 $(INTERNAL_SDK_TARGET): PRIVATE_DIR := $(sdk_dir)/$(sdk_name)
 $(INTERNAL_SDK_TARGET): PRIVATE_DEP_FILE := $(sdk_dep_file)
 $(INTERNAL_SDK_TARGET): PRIVATE_INPUT_FILES := $(sdk_atree_files)
-
+$(INTERNAL_SDK_TARGET): PRIVATE_PLATFORM_NAME := $(sdk_platform_dir_name)
 # Set SDK_GNU_ERROR to non-empty to fail when a GNU target is built.
 #
 #SDK_GNU_ERROR := true
@@ -6854,7 +6872,7 @@
 	        -I $(PRODUCT_OUT) \
 	        -I $(HOST_OUT) \
 	        -I $(TARGET_COMMON_OUT_ROOT) \
-	        -v "PLATFORM_NAME=android-$(PLATFORM_VERSION)" \
+	        -v "PLATFORM_NAME=$(PRIVATE_PLATFORM_NAME)" \
 	        -v "OUT_DIR=$(OUT_DIR)" \
 	        -v "HOST_OUT=$(HOST_OUT)" \
 	        -v "TARGET_ARCH=$(TARGET_ARCH)" \
diff --git a/core/config.mk b/core/config.mk
index 22224f6..631ba34 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -614,7 +614,7 @@
 JARJAR := $(HOST_OUT_JAVA_LIBRARIES)/jarjar.jar
 DATA_BINDING_COMPILER := $(HOST_OUT_JAVA_LIBRARIES)/databinding-compiler.jar
 FAT16COPY := build/make/tools/fat16copy.py
-CHECK_ELF_FILE := build/make/tools/check_elf_file.py
+CHECK_ELF_FILE := $(HOST_OUT_EXECUTABLES)/check_elf_file$(HOST_EXECUTABLE_SUFFIX)
 LPMAKE := $(HOST_OUT_EXECUTABLES)/lpmake$(HOST_EXECUTABLE_SUFFIX)
 ADD_IMG_TO_TARGET_FILES := $(HOST_OUT_EXECUTABLES)/add_img_to_target_files$(HOST_EXECUTABLE_SUFFIX)
 BUILD_IMAGE := $(HOST_OUT_EXECUTABLES)/build_image$(HOST_EXECUTABLE_SUFFIX)
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index dbd1e84..e83d408 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -41,3 +41,9 @@
 
 $(call dist-for-goals, general-tests, $(MODULE_INFO_JSON))
 $(call dist-for-goals, droidcore-unbundled, $(MODULE_INFO_JSON))
+
+# On every build, generate an all_modules.txt file to be used for autocompleting
+# the m command. After timing this using $(shell date +"%s.%3N"), it only adds
+# 0.01 seconds to the internal master build, and will only rerun on builds that
+# rerun kati.
+$(file >$(PRODUCT_OUT)/all_modules.txt,$(subst $(space),$(newline),$(ALL_MODULES)))
diff --git a/envsetup.sh b/envsetup.sh
index 3674a4a..4c1aeaa 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -1551,12 +1551,10 @@
     fi
 }
 
-# List all modules for the current device, as cached in module-info.json. If any build change is
-# made and it should be reflected in the output, you should run 'refreshmod' first.
+# List all modules for the current device, as cached in all_modules.txt. If any build change is
+# made and it should be reflected in the output, you should run `m nothing` first.
 function allmod() {
-    verifymodinfo || return 1
-
-    python3 -c "import json; print('\n'.join(sorted(json.load(open('$ANDROID_PRODUCT_OUT/module-info.json')).keys())))"
+    cat $ANDROID_PRODUCT_OUT/all_modules.txt 2>/dev/null
 }
 
 # Return the Bazel label of a Soong module if it is converted with bp2build.
@@ -1735,7 +1733,7 @@
 
 function _complete_android_module_names() {
     local word=${COMP_WORDS[COMP_CWORD]}
-    COMPREPLY=( $(QUIET_VERIFYMODINFO=true allmod | grep -E "^$word") )
+    COMPREPLY=( $(allmod | grep -E "^$word") )
 }
 
 # Print colored exit condition
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index 20493be..3705a50 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -23,6 +23,8 @@
 # - Released GSI contains more VNDK packages to support old version vendors
 # - etc.
 #
+# See device/generic/common/README.md for more details.
+#
 
 BUILDING_GSI := true
 
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index 1ebd4ab..a62cda7 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -165,3 +165,13 @@
     dalvik.vm.usap_pool_size_max?=3 \
     dalvik.vm.usap_pool_size_min?=1 \
     dalvik.vm.usap_pool_refill_delay_ms?=3000
+
+# Allow dexopt files that are side-effects of already allowlisted files.
+# This is only necessary when ART is prebuilt.
+ifeq (false,$(ART_MODULE_BUILD_FROM_SOURCE))
+  PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
+      system/framework/%.art \
+      system/framework/%.oat \
+      system/framework/%.odex \
+      system/framework/%.vdex
+endif
diff --git a/tests/b_tests.sh b/tests/b_tests.sh
index f4e043c..45cb4f7 100755
--- a/tests/b_tests.sh
+++ b/tests/b_tests.sh
@@ -18,6 +18,9 @@
 
 source $(dirname $0)/../envsetup.sh
 
+# lunch required to set up PATH to use b
+lunch aosp_arm64
+
 test_target=//build/bazel/scripts/difftool:difftool
 
 b build "$test_target"
diff --git a/tools/Android.bp b/tools/Android.bp
index f401058..1f0d406 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -59,3 +59,8 @@
   name: "check_radio_versions",
   srcs: ["check_radio_versions.py"],
 }
+
+python_binary_host {
+  name: "check_elf_file",
+  srcs: ["check_elf_file.py"],
+}
diff --git a/tools/check_elf_file.py b/tools/check_elf_file.py
index 0b80226..eaa1854 100755
--- a/tools/check_elf_file.py
+++ b/tools/check_elf_file.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2019 The Android Open Source Project
 #
@@ -196,11 +196,7 @@
   def _read_llvm_readobj(cls, elf_file_path, header, llvm_readobj):
     """Run llvm-readobj and parse the output."""
     cmd = [llvm_readobj, '--dynamic-table', '--dyn-symbols', elf_file_path]
-    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    out, _ = proc.communicate()
-    rc = proc.returncode
-    if rc != 0:
-      raise subprocess.CalledProcessError(rc, cmd, out)
+    out = subprocess.check_output(cmd, text=True)
     lines = out.splitlines()
     return cls._parse_llvm_readobj(elf_file_path, header, lines)
 
@@ -467,7 +463,7 @@
     """Check whether all undefined symbols are resolved to a definition."""
     all_elf_files = [self._file_under_test] + self._shared_libs
     missing_symbols = []
-    for sym, imported_vers in self._file_under_test.imported.iteritems():
+    for sym, imported_vers in self._file_under_test.imported.items():
       for imported_ver in imported_vers:
         lib = self._find_symbol_from_libs(all_elf_files, sym, imported_ver)
         if not lib:
diff --git a/tools/compliance/README.md b/tools/compliance/README.md
new file mode 100644
index 0000000..995d9ca
--- /dev/null
+++ b/tools/compliance/README.md
@@ -0,0 +1,101 @@
+# Compliance
+
+<!-- Much of this content appears too in doc.go
+When changing this file consider whether the change also applies to doc.go -->
+
+Package compliance provides an approved means for reading, consuming, and
+analyzing license metadata graphs.
+
+Assuming the license metadata and dependencies are fully and accurately
+recorded in the build system, any discrepancy between the official policy for
+open source license compliance and this code is **a bug in this code.**
+
+## Naming
+
+All of the code that directly reflects a policy decision belongs in a file with
+a name begninning `policy_`. Changes to these files need to be authored or
+reviewed by someone in OSPO or whichever successor group governs policy.
+
+The files with names not beginning `policy_` describe data types, and general,
+reusable algorithms.
+
+The source code for binary tools and utilities appears under the `cmd/`
+subdirectory. Other subdirectories contain reusable components that are not
+`compliance` per se.
+
+## Data Types
+
+A few principal types to understand are LicenseGraph, LicenseCondition, and
+ResolutionSet.
+
+### LicenseGraph
+
+A LicenseGraph is an immutable graph of the targets and dependencies reachable
+from a specific set of root targets. In general, the root targets will be the
+artifacts in a release or distribution. While conceptually immutable, parts of
+the graph may be loaded or evaluated lazily.
+
+Conceptually, the graph itself will always be a directed acyclic graph. One
+representation is a set of directed edges. Another is a set of nodes with
+directed edges to their dependencies.
+
+The edges have annotations, which can distinguish between build tools, runtime
+dependencies, and dependencies like 'contains' that make a derivative work.
+
+### LicenseCondition
+
+A LicenseCondition is an immutable tuple pairing a condition name with an
+originating target. e.g. Per current policy, a static library licensed under an
+MIT license would pair a "notice" condition with the static library target, and
+a dynamic license licensed under GPL would pair a "restricted" condition with
+the dynamic library target.
+
+### ResolutionSet
+
+A ResolutionSet is an immutable set of `AttachesTo`, `ActsOn`, `Resolves`
+tuples describing how license conditions apply to targets.
+
+`AttachesTo` is the trigger for acting. Distribution of the target invokes
+the policy.
+
+`ActsOn` is the target to share, give notice for, hide etc.
+
+`Resolves` is the set of conditions that the action resolves.
+
+For most condition types, `ActsOn` will be the target where the condition
+originated. For example, a notice condition policy means attribution or notice
+must be given for the target where the condition originates. Likewise, a
+proprietary condition policy means the privacy of the target where the
+condition originates must be respected. i.e. The thing acted on is the origin.
+
+Restricted conditions are different. The infectious nature of restricted often
+means sharing code that is not the target where the restricted condition
+originates. Linking an MIT library to a GPL library implies a policy to share
+the MIT library despite the MIT license having no source sharing requirement.
+
+In this case, one or more resolution tuples will have the MIT license module in
+`ActsOn` and the restricted condition originating at the GPL library module in
+`Resolves`. These tuples will `AttachTo` every target that depends on the GPL
+library because shipping any of those targets trigger the policy to share the
+code.
+
+## Processes
+
+### ReadLicenseGraph
+
+The principal means to ingest license metadata. Given the distribution targets,
+ReadLicenseGraph populates the LicenseGraph for those root targets.
+
+### NoticeIndex.IndexLicenseTexts
+
+IndexLicenseTexts reads, deduplicates and caches license texts for notice
+files. Also reads and caches project metadata for deriving library names.
+
+The algorithm for deriving library names has not been dictated by OSPO policy,
+but reflects a pragmatic attempt to comply with Android policy regarding
+unreleased product names, proprietary partner names etc.
+
+### projectmetadata.Index.MetadataForProjects
+
+MetadataForProjects reads, deduplicates and caches project METADATA files used
+for notice library names, and various properties appearing in SBOMs.
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
index 7ef14e9..a7c3d01 100644
--- a/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
@@ -2,7 +2,7 @@
 module_classes: "EXECUTABLES"
 projects:  "standalone/binary"
 license_kinds:  "SPDX-license-identifier-LGPL-2.0"
-license_conditions:  "restricted"
+license_conditions:  "restricted_allows_dynamic_linking"
 license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
 is_container:  false
 built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
index 7ef14e9..a7c3d01 100644
--- a/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
@@ -2,7 +2,7 @@
 module_classes: "EXECUTABLES"
 projects:  "standalone/binary"
 license_kinds:  "SPDX-license-identifier-LGPL-2.0"
-license_conditions:  "restricted"
+license_conditions:  "restricted_allows_dynamic_linking"
 license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
 is_container:  false
 built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
index a505d4a..101ca19 100644
--- a/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
+++ b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
@@ -1,7 +1,7 @@
 package_name:  "Device"
 projects:  "device/library"
 license_kinds:  "SPDX-license-identifier-LGPL-2.0"
-license_conditions:  "restricted"
+license_conditions:  "restricted_allows_dynamic_linking"
 license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
 is_container:  false
 built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
diff --git a/tools/compliance/doc.go b/tools/compliance/doc.go
index a47c1cf..5ced9ee 100644
--- a/tools/compliance/doc.go
+++ b/tools/compliance/doc.go
@@ -11,6 +11,10 @@
 // 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.
+
+// Much of this content appears too in README.md
+// When changing this file consider whether the change also applies to README.md
+
 /*
 
 Package compliance provides an approved means for reading, consuming, and
@@ -31,6 +35,13 @@
 artifacts in a release or distribution. While conceptually immutable, parts of
 the graph may be loaded or evaluated lazily.
 
+Conceptually, the graph itself will always be a directed acyclic graph. One
+representation is a set of directed edges. Another is a set of nodes with
+directed edges to their dependencies.
+
+The edges have annotations, which can distinguish between build tools, runtime
+dependencies, and dependencies like 'contains' that make a derivative work.
+
 LicenseCondition
 ----------------
 
@@ -51,17 +62,13 @@
 
 `ActsOn` is the target to share, give notice for, hide etc.
 
-`Resolves` is the license condition that the action resolves.
+`Resolves` is the set of condition types that the action resolves.
 
-Remember: Each license condition pairs a condition name with an originating
-target so each resolution in a ResolutionSet has two targets it applies to and
-one target from which it originates, all of which may be the same target.
-
-For most condition types, `ActsOn` and `Resolves.Origin` will be the same
-target. For example, a notice condition policy means attribution or notice must
-be given for the target where the condition originates. Likewise, a proprietary
-condition policy means the privacy of the target where the condition originates
-must be respected. i.e. The thing acted on is the origin.
+For most condition types, `ActsOn` will be the target where the condition
+originated. For example, a notice condition policy means attribution or notice
+must be given for the target where the condition originates. Likewise, a
+proprietary condition policy means the privacy of the target where the
+condition originates must be respected. i.e. The thing acted on is the origin.
 
 Restricted conditions are different. The infectious nature of restricted often
 means sharing code that is not the target where the restricted condition
diff --git a/tools/compliance/graph.go b/tools/compliance/graph.go
index e73ab46..cd5fd59 100644
--- a/tools/compliance/graph.go
+++ b/tools/compliance/graph.go
@@ -58,13 +58,11 @@
 	/// (guarded by mu)
 	targets map[string]*TargetNode
 
-	// wgBU becomes non-nil when the bottom-up resolve begins and reaches 0
-	// (i.e. Wait() proceeds) when the bottom-up resolve completes. (guarded by mu)
-	wgBU *sync.WaitGroup
+	// onceBottomUp makes sure the bottom-up resolve walk only happens one time.
+	onceBottomUp sync.Once
 
-	// wgTD becomes non-nil when the top-down resolve begins and reaches 0 (i.e. Wait()
-	// proceeds) when the top-down resolve completes. (guarded by mu)
-	wgTD *sync.WaitGroup
+	// onceTopDown makes sure the top-down resolve walk only happens one time.
+	onceTopDown sync.Once
 
 	// shippedNodes caches the results of a full walk of nodes identifying targets
 	// distributed either directly or as derivative works. (creation guarded by mu)
@@ -300,23 +298,6 @@
 	return tn.proto.GetPackageName()
 }
 
-// ModuleTypes returns the list of module types implementing the target.
-// (unordered)
-//
-// In an ideal world, only 1 module type would implement each target, but the
-// interactions between Soong and Make for host versus product and for a
-// variety of architectures sometimes causes multiple module types per target
-// (often a regular build target and a prebuilt.)
-func (tn *TargetNode) ModuleTypes() []string {
-	return append([]string{}, tn.proto.ModuleTypes...)
-}
-
-// ModuleClasses returns the list of module classes implementing the target.
-// (unordered)
-func (tn *TargetNode) ModuleClasses() []string {
-	return append([]string{}, tn.proto.ModuleClasses...)
-}
-
 // Projects returns the projects defining the target node. (unordered)
 //
 // In an ideal world, only 1 project defines a target, but the interaction
@@ -326,14 +307,6 @@
 	return append([]string{}, tn.proto.Projects...)
 }
 
-// LicenseKinds returns the list of license kind names for the module or
-// target. (unordered)
-//
-// e.g. SPDX-license-identifier-MIT or legacy_proprietary
-func (tn *TargetNode) LicenseKinds() []string {
-	return append([]string{}, tn.proto.LicenseKinds...)
-}
-
 // LicenseConditions returns a copy of the set of license conditions
 // originating at the target. The values that appear and how each is resolved
 // is a matter of policy. (unordered)
diff --git a/tools/compliance/policy_policy.go b/tools/compliance/policy_policy.go
index 02d1d96..23e25c6 100644
--- a/tools/compliance/policy_policy.go
+++ b/tools/compliance/policy_policy.go
@@ -117,32 +117,6 @@
 func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet {
 	cs := NewLicenseConditionSet()
 	for _, name := range names {
-		if name == "restricted" {
-			if 0 == len(tn.LicenseKinds()) {
-				cs = cs.Plus(RestrictedCondition)
-				continue
-			}
-			hasLgpl := false
-			hasGeneric := false
-			for _, kind := range tn.LicenseKinds() {
-				if anyLgpl.MatchString(kind) {
-					cs = cs.Plus(WeaklyRestrictedCondition)
-					hasLgpl = true
-				} else if versionedGpl.MatchString(kind) {
-					cs = cs.Plus(RestrictedCondition)
-				} else if genericGpl.MatchString(kind) {
-					hasGeneric = true
-				} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
-					cs = cs.Plus(RestrictedCondition)
-				} else {
-					cs = cs.Plus(RestrictedCondition)
-				}
-			}
-			if hasGeneric && !hasLgpl {
-				cs = cs.Plus(RestrictedCondition)
-			}
-			continue
-		}
 		if lc, ok := RecognizedConditionNames[name]; ok {
 			cs |= LicenseConditionSet(lc)
 		}
diff --git a/tools/compliance/policy_resolve.go b/tools/compliance/policy_resolve.go
index 93335a9..fc8ed4c 100644
--- a/tools/compliance/policy_resolve.go
+++ b/tools/compliance/policy_resolve.go
@@ -49,84 +49,71 @@
 func TraceBottomUpConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
 
 	// short-cut if already walked and cached
-	lg.mu.Lock()
-	wg := lg.wgBU
+	lg.onceBottomUp.Do(func() {
+		// amap identifes targets previously walked. (guarded by mu)
+		amap := make(map[*TargetNode]struct{})
 
-	if wg != nil {
-		lg.mu.Unlock()
-		wg.Wait()
-		return
-	}
-	wg = &sync.WaitGroup{}
-	wg.Add(1)
-	lg.wgBU = wg
-	lg.mu.Unlock()
+		// mu guards concurrent access to amap
+		var mu sync.Mutex
 
-	// amap identifes targets previously walked. (guarded by mu)
-	amap := make(map[*TargetNode]struct{})
+		var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
 
-	// mu guards concurrent access to amap
-	var mu sync.Mutex
+		walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
+			priorWalkResults := func() (LicenseConditionSet, bool) {
+				mu.Lock()
+				defer mu.Unlock()
 
-	var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
-
-	walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
-		priorWalkResults := func() (LicenseConditionSet, bool) {
-			mu.Lock()
-			defer mu.Unlock()
-
-			if _, alreadyWalked := amap[target]; alreadyWalked {
-				if treatAsAggregate {
-					return target.resolution, true
+				if _, alreadyWalked := amap[target]; alreadyWalked {
+					if treatAsAggregate {
+						return target.resolution, true
+					}
+					if !target.pure {
+						return target.resolution, true
+					}
+					// previously walked in a pure aggregate context,
+					// needs to walk again in non-aggregate context
+				} else {
+					target.resolution |= conditionsFn(target)
+					amap[target] = struct{}{}
 				}
-				if !target.pure {
-					return target.resolution, true
-				}
-				// previously walked in a pure aggregate context,
-				// needs to walk again in non-aggregate context
-			} else {
-				target.resolution |= conditionsFn(target)
-				amap[target] = struct{}{}
+				target.pure = treatAsAggregate
+				return target.resolution, false
 			}
-			target.pure = treatAsAggregate
-			return target.resolution, false
-		}
-		cs, alreadyWalked := priorWalkResults()
-		if alreadyWalked {
+			cs, alreadyWalked := priorWalkResults()
+			if alreadyWalked {
+				return cs
+			}
+
+			c := make(chan LicenseConditionSet, len(target.edges))
+			// add all the conditions from all the dependencies
+			for _, edge := range target.edges {
+				go func(edge *TargetEdge) {
+					// walk dependency to get its conditions
+					cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
+
+					// turn those into the conditions that apply to the target
+					cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
+
+					c <- cs
+				}(edge)
+			}
+			for i := 0; i < len(target.edges); i++ {
+				cs |= <-c
+			}
+			mu.Lock()
+			target.resolution |= cs
+			mu.Unlock()
+
+			// return conditions up the tree
 			return cs
 		}
 
-		c := make(chan LicenseConditionSet, len(target.edges))
-		// add all the conditions from all the dependencies
-		for _, edge := range target.edges {
-			go func(edge *TargetEdge) {
-				// walk dependency to get its conditions
-				cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
-
-				// turn those into the conditions that apply to the target
-				cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
-
-				c <- cs
-			}(edge)
+		// walk each of the roots
+		for _, rname := range lg.rootFiles {
+			rnode := lg.targets[rname]
+			_ = walk(rnode, rnode.IsContainer())
 		}
-		for i := 0; i < len(target.edges); i++ {
-			cs |= <-c
-		}
-		mu.Lock()
-		target.resolution |= cs
-		mu.Unlock()
-
-		// return conditions up the tree
-		return cs
-	}
-
-	// walk each of the roots
-	for _, rname := range lg.rootFiles {
-		rnode := lg.targets[rname]
-		_ = walk(rnode, rnode.IsContainer())
-	}
-
-	wg.Done()
+	})
 }
 
 // ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
@@ -145,78 +132,76 @@
 func TraceTopDownConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
 
 	// short-cut if already walked and cached
-	lg.mu.Lock()
-	wg := lg.wgTD
+	lg.onceTopDown.Do(func() {
+		wg := &sync.WaitGroup{}
+		wg.Add(1)
 
-	if wg != nil {
-		lg.mu.Unlock()
-		wg.Wait()
-		return
-	}
-	wg = &sync.WaitGroup{}
-	wg.Add(1)
-	lg.wgTD = wg
-	lg.mu.Unlock()
+		// start with the conditions propagated up the graph
+		TraceBottomUpConditions(lg, conditionsFn)
 
-	// start with the conditions propagated up the graph
-	TraceBottomUpConditions(lg, conditionsFn)
+		// amap contains the set of targets already walked. (guarded by mu)
+		amap := make(map[*TargetNode]struct{})
 
-	// amap contains the set of targets already walked. (guarded by mu)
-	amap := make(map[*TargetNode]struct{})
+		// mu guards concurrent access to amap
+		var mu sync.Mutex
 
-	// mu guards concurrent access to amap
-	var mu sync.Mutex
+		var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
 
-	var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
-
-	walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
-		defer wg.Done()
-		mu.Lock()
-		fnode.resolution |= conditionsFn(fnode)
-		fnode.resolution |= cs
-		fnode.pure = treatAsAggregate
-		amap[fnode] = struct{}{}
-		cs = fnode.resolution
-		mu.Unlock()
-		// for each dependency
-		for _, edge := range fnode.edges {
-			func(edge *TargetEdge) {
-				// dcs holds the dpendency conditions inherited from the target
-				dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
-				dnode := edge.dependency
+		walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
+			defer wg.Done()
+			continueWalk := func() bool {
 				mu.Lock()
 				defer mu.Unlock()
-				depcs := dnode.resolution
-				_, alreadyWalked := amap[dnode]
-				if !dcs.IsEmpty() && alreadyWalked {
-					if dcs.Difference(depcs).IsEmpty() {
+
+				depcs := fnode.resolution
+				_, alreadyWalked := amap[fnode]
+				if alreadyWalked {
+					if cs.IsEmpty() {
+						return false
+					}
+					if cs.Difference(depcs).IsEmpty() {
 						// no new conditions
 
 						// pure aggregates never need walking a 2nd time with same conditions
 						if treatAsAggregate {
-							return
+							return false
 						}
 						// non-aggregates don't need walking as non-aggregate a 2nd time
-						if !dnode.pure {
-							return
+						if !fnode.pure {
+							return false
 						}
 						// previously walked as pure aggregate; need to re-walk as non-aggregate
 					}
 				}
+				fnode.resolution |= conditionsFn(fnode)
+				fnode.resolution |= cs
+				fnode.pure = treatAsAggregate
+				amap[fnode] = struct{}{}
+				cs = fnode.resolution
+				return true
+			}()
+			if !continueWalk {
+				return
+			}
+			// for each dependency
+			for _, edge := range fnode.edges {
+				// dcs holds the dpendency conditions inherited from the target
+				dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
+				dnode := edge.dependency
 				// add the conditions to the dependency
 				wg.Add(1)
 				go walk(dnode, dcs, treatAsAggregate && dnode.IsContainer())
-			}(edge)
+			}
 		}
-	}
 
-	// walk each of the roots
-	for _, rname := range lg.rootFiles {
-		rnode := lg.targets[rname]
-		wg.Add(1)
-		// add the conditions to the root and its transitive closure
-		go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
-	}
-	wg.Done()
-	wg.Wait()
+		// walk each of the roots
+		for _, rname := range lg.rootFiles {
+			rnode := lg.targets[rname]
+			wg.Add(1)
+			// add the conditions to the root and its transitive closure
+			go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
+		}
+		wg.Done()
+		wg.Wait()
+	})
 }
diff --git a/tools/compliance/policy_resolvenotices_test.go b/tools/compliance/policy_resolvenotices_test.go
index ee5e3b8..92b0ce3 100644
--- a/tools/compliance/policy_resolvenotices_test.go
+++ b/tools/compliance/policy_resolvenotices_test.go
@@ -217,10 +217,10 @@
 			},
 			expectedResolutions: []res{
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -245,7 +245,7 @@
 			expectedResolutions: []res{
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -258,17 +258,17 @@
 			},
 			expectedResolutions: []res{
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -280,11 +280,11 @@
 			},
 			expectedResolutions: []res{
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -309,7 +309,7 @@
 			expectedResolutions: []res{
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -336,7 +336,7 @@
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -363,7 +363,7 @@
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
diff --git a/tools/compliance/policy_resolveshare_test.go b/tools/compliance/policy_resolveshare_test.go
index d49dfa8..cf88058 100644
--- a/tools/compliance/policy_resolveshare_test.go
+++ b/tools/compliance/policy_resolveshare_test.go
@@ -73,8 +73,8 @@
 				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
 			},
 			expectedResolutions: []res{
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
-				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -84,7 +84,7 @@
 				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
 			expectedResolutions: []res{
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -94,7 +94,7 @@
 				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
 			expectedResolutions: []res{
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
diff --git a/tools/compliance/projectmetadata/projectmetadata.go b/tools/compliance/projectmetadata/projectmetadata.go
index 1861b47..f9ddbae 100644
--- a/tools/compliance/projectmetadata/projectmetadata.go
+++ b/tools/compliance/projectmetadata/projectmetadata.go
@@ -118,6 +118,7 @@
 // a `ProjectMetadata`, pm (can be nil even without error), or a non-nil `err`.
 type projectIndex struct {
 	project string
+	path    string
 	pm      *ProjectMetadata
 	err     error
 	done    chan struct{}
@@ -230,6 +231,19 @@
 	return result, nil
 }
 
+// AllMetadataFiles returns the sorted list of all METADATA files read thus far.
+func (ix *Index) AllMetadataFiles() []string {
+	files := []string(nil)
+	ix.projects.Range(func(key, value any) bool {
+		pi := value.(*projectIndex)
+		if pi.path != "" {
+			files = append(files, pi.path)
+		}
+		return true
+	})
+	return files
+}
+
 // readMetadataFile tries to read and parse a METADATA file at `path` for `project`.
 func (ix *Index) readMetadataFile(pi *projectIndex, path string) {
 	f, err := ix.rootFS.Open(path)
@@ -250,9 +264,24 @@
 	pm := &ProjectMetadata{project: pi.project}
 	err = uo.Unmarshal(data, &pm.proto)
 	if err != nil {
-		pi.err = fmt.Errorf("error in project %q metadata %q: %w", pi.project, path, err)
+		pi.err = fmt.Errorf(`error in project %q METADATA %q: %v
+
+METADATA and METADATA.android files must parse as text protobufs
+defined by
+   build/soong/compliance/project_metadata_proto/project_metadata.proto
+
+* unknown fields don't matter
+* check invalid ENUM names
+* check quoting
+* check unescaped nested quotes
+* check the comment marker for protobuf is '#' not '//'
+
+if importing a library that uses a different sort of METADATA file, add
+a METADATA.android file beside it to parse instead
+`, pi.project, path, err)
 		return
 	}
 
+	pi.path = path
 	pi.pm = pm
 }
diff --git a/tools/compliance/test_util.go b/tools/compliance/test_util.go
index 6c50d3e..db711a7 100644
--- a/tools/compliance/test_util.go
+++ b/tools/compliance/test_util.go
@@ -57,7 +57,7 @@
 	LGPL = `` +
 		`package_name: "Free Library"
 license_kinds: "SPDX-license-identifier-LGPL-2.0"
-license_conditions: "restricted"
+license_conditions: "restricted_allows_dynamic_linking"
 `
 
 	// MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing.
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index 465e1a8..d308a55 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -842,14 +842,13 @@
   SYSTEM/ after rebuilding recovery.
   """
   common.ZipDelete(zip_filename, files_list)
-  output_zip = zipfile.ZipFile(zip_filename, "a",
+  with zipfile.ZipFile(zip_filename, "a",
                                compression=zipfile.ZIP_DEFLATED,
-                               allowZip64=True)
-  for item in files_list:
-    file_path = os.path.join(OPTIONS.input_tmp, item)
-    assert os.path.exists(file_path)
-    common.ZipWrite(output_zip, file_path, arcname=item)
-  common.ZipClose(output_zip)
+                               allowZip64=True) as output_zip:
+    for item in files_list:
+      file_path = os.path.join(OPTIONS.input_tmp, item)
+      assert os.path.exists(file_path)
+      common.ZipWrite(output_zip, file_path, arcname=item)
 
 
 def HasPartition(partition_name):
@@ -1176,7 +1175,7 @@
   AddVbmetaDigest(output_zip)
 
   if output_zip:
-    common.ZipClose(output_zip)
+    output_zip.close()
     if OPTIONS.replace_updated_files_list:
       ReplaceUpdatedFiles(output_zip.filename,
                           OPTIONS.replace_updated_files_list)
diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py
index d7b0ba2..22992e8 100644
--- a/tools/releasetools/apex_utils.py
+++ b/tools/releasetools/apex_utils.py
@@ -415,7 +415,7 @@
   apex_zip = zipfile.ZipFile(apex_file, 'a', allowZip64=True)
   common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE)
   common.ZipWrite(apex_zip, payload_public_key, arcname=APEX_PUBKEY)
-  common.ZipClose(apex_zip)
+  apex_zip.close()
 
   # 3. Sign the APEX container with container_key.
   signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
diff --git a/tools/releasetools/check_ota_package_signature.py b/tools/releasetools/check_ota_package_signature.py
index b395c19..97957be 100755
--- a/tools/releasetools/check_ota_package_signature.py
+++ b/tools/releasetools/check_ota_package_signature.py
@@ -142,7 +142,7 @@
   """Verifies the payload and metadata signatures in an A/B OTA payload."""
   package_zip = zipfile.ZipFile(package, 'r', allowZip64=True)
   if 'payload.bin' not in package_zip.namelist():
-    common.ZipClose(package_zip)
+    package_zip.close()
     return
 
   print('Verifying A/B OTA payload signatures...')
@@ -160,7 +160,7 @@
          '--in_file=' + payload_file,
          '--public_key=' + pubkey]
   common.RunAndCheckOutput(cmd)
-  common.ZipClose(package_zip)
+  package_zip.close()
 
   # Verified successfully upon reaching here.
   print('\nPayload signatures VERIFIED\n\n')
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index c369a59..8f61eac 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -280,8 +280,8 @@
       inp_partition = os.path.join(inp, target_files_rel_path,"apex")
       if os.path.exists(inp_partition):
         apex_dir = root_dir + os.path.join(device_path + "/apex");
-        os.makedirs(apex_dir)
-        os.rename(inp_partition, apex_dir)
+        os.makedirs(root_dir + device_path)
+        shutil.copytree(inp_partition, apex_dir, symlinks=True)
         ExtractApexes(apex_dir, extracted_root)
         create_info_file = True
 
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 715802f..2f05d44 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -2797,18 +2797,6 @@
 def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
              compress_type=None):
 
-  # http://b/18015246
-  # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
-  # for files larger than 2GiB. We can work around this by adjusting their
-  # limit. Note that `zipfile.writestr()` will not work for strings larger than
-  # 2GiB. The Python interpreter sometimes rejects strings that large (though
-  # it isn't clear to me exactly what circumstances cause this).
-  # `zipfile.write()` must be used directly to work around this.
-  #
-  # This mess can be avoided if we port to python3.
-  saved_zip64_limit = zipfile.ZIP64_LIMIT
-  zipfile.ZIP64_LIMIT = (1 << 32) - 1
-
   if compress_type is None:
     compress_type = zip_file.compression
   if arcname is None:
@@ -2834,14 +2822,13 @@
   finally:
     os.chmod(filename, saved_stat.st_mode)
     os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
-    zipfile.ZIP64_LIMIT = saved_zip64_limit
 
 
 def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
                 compress_type=None):
   """Wrap zipfile.writestr() function to work around the zip64 limit.
 
-  Even with the ZIP64_LIMIT workaround, it won't allow writing a string
+  Python's zip implementation won't allow writing a string
   longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
   when calling crc32(bytes).
 
@@ -2850,9 +2837,6 @@
   when we know the string won't be too long.
   """
 
-  saved_zip64_limit = zipfile.ZIP64_LIMIT
-  zipfile.ZIP64_LIMIT = (1 << 32) - 1
-
   if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
     zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
     zinfo.compress_type = zip_file.compression
@@ -2885,7 +2869,6 @@
   zinfo.date_time = (2009, 1, 1, 0, 0, 0)
 
   zip_file.writestr(zinfo, data)
-  zipfile.ZIP64_LIMIT = saved_zip64_limit
 
 
 def ZipDelete(zip_filename, entries, force=False):
@@ -2919,18 +2902,6 @@
   os.replace(new_zipfile, zip_filename)
 
 
-def ZipClose(zip_file):
-  # http://b/18015246
-  # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
-  # central directory.
-  saved_zip64_limit = zipfile.ZIP64_LIMIT
-  zipfile.ZIP64_LIMIT = (1 << 32) - 1
-
-  zip_file.close()
-
-  zipfile.ZIP64_LIMIT = saved_zip64_limit
-
-
 class DeviceSpecificParams(object):
   module = None
 
diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py
index 76da89c..f8bdd81 100755
--- a/tools/releasetools/img_from_target_files.py
+++ b/tools/releasetools/img_from_target_files.py
@@ -173,7 +173,7 @@
   logger.info('Writing super.img to archive...')
   with zipfile.ZipFile(
       output_file, 'a', compression=zipfile.ZIP_DEFLATED,
-      allowZip64=not OPTIONS.sparse_userimages) as output_zip:
+      allowZip64=True) as output_zip:
     common.ZipWrite(output_zip, super_file, 'super.img')
 
 
diff --git a/tools/releasetools/non_ab_ota.py b/tools/releasetools/non_ab_ota.py
index 6c927ec..ac85aa4 100644
--- a/tools/releasetools/non_ab_ota.py
+++ b/tools/releasetools/non_ab_ota.py
@@ -277,7 +277,7 @@
 
   # We haven't written the metadata entry, which will be done in
   # FinalizeMetadata.
-  common.ZipClose(output_zip)
+  output_zip.close()
 
   needed_property_files = (
       NonAbOtaPropertyFiles(),
@@ -531,7 +531,7 @@
 
   # We haven't written the metadata entry yet, which will be handled in
   # FinalizeMetadata().
-  common.ZipClose(output_zip)
+  output_zip.close()
 
   # Sign the generated zip package unless no_signing is specified.
   needed_property_files = (
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 9d5c67d..60e95ad 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -487,7 +487,7 @@
       else:
         common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
 
-  common.ZipClose(target_zip)
+  target_zip.close()
 
   return target_file
 
@@ -624,7 +624,7 @@
 
     # TODO(xunchang) handle META/postinstall_config.txt'
 
-  common.ZipClose(partial_target_zip)
+  partial_target_zip.close()
 
   return partial_target_file
 
@@ -709,7 +709,7 @@
   # Write new ab_partitions.txt file
   common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
 
-  common.ZipClose(target_zip)
+  target_zip.close()
 
   return target_file
 
@@ -1017,11 +1017,11 @@
     common.ZipWriteStr(output_zip, "apex_info.pb", ota_apex_info,
                        compress_type=zipfile.ZIP_STORED)
 
-  common.ZipClose(target_zip)
+  target_zip.close()
 
   # We haven't written the metadata entry yet, which will be handled in
   # FinalizeMetadata().
-  common.ZipClose(output_zip)
+  output_zip.close()
 
   FinalizeMetadata(metadata, staging_file, output_file,
                    package_key=OPTIONS.package_key)
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 9f41874..e36a2be 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -22,7 +22,7 @@
 
 import ota_metadata_pb2
 import common
-from common import (ZipDelete, ZipClose, OPTIONS, MakeTempFile,
+from common import (ZipDelete, OPTIONS, MakeTempFile,
                     ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
                     SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
                     GetRamdiskFormat)
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index d3fbdad..4e7274f 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -901,7 +901,7 @@
   certs_zip = zipfile.ZipFile(temp_file, "w", allowZip64=True)
   for k in keys:
     common.ZipWrite(certs_zip, k)
-  common.ZipClose(certs_zip)
+  certs_zip.close()
   common.ZipWriteStr(output_zip, filename, temp_file.getvalue())
 
 
@@ -1529,8 +1529,8 @@
                      platform_api_level, codename_to_api_level_map,
                      compressed_extension)
 
-  common.ZipClose(input_zip)
-  common.ZipClose(output_zip)
+  input_zip.close()
+  output_zip.close()
 
   if OPTIONS.vendor_partitions and OPTIONS.vendor_otatools:
     BuildVendorPartitions(args[1])
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 2a0e592..8c9655ad0 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -222,17 +222,17 @@
     info_dict = copy.deepcopy(self.TEST_INFO_FINGERPRINT_DICT)
     build_info = common.BuildInfo(info_dict)
     self.assertEqual(
-      'product-brand/product-name/product-device:version-release/build-id/'
-      'version-incremental:build-type/build-tags', build_info.fingerprint)
+        'product-brand/product-name/product-device:version-release/build-id/'
+        'version-incremental:build-type/build-tags', build_info.fingerprint)
 
     build_props = info_dict['build.prop'].build_props
     del build_props['ro.build.id']
     build_props['ro.build.legacy.id'] = 'legacy-build-id'
     build_info = common.BuildInfo(info_dict, use_legacy_id=True)
     self.assertEqual(
-      'product-brand/product-name/product-device:version-release/'
-      'legacy-build-id/version-incremental:build-type/build-tags',
-      build_info.fingerprint)
+        'product-brand/product-name/product-device:version-release/'
+        'legacy-build-id/version-incremental:build-type/build-tags',
+        build_info.fingerprint)
 
     self.assertRaises(common.ExternalError, common.BuildInfo, info_dict, None,
                       False)
@@ -241,9 +241,9 @@
     info_dict['vbmeta_digest'] = 'abcde12345'
     build_info = common.BuildInfo(info_dict, use_legacy_id=False)
     self.assertEqual(
-      'product-brand/product-name/product-device:version-release/'
-      'legacy-build-id.abcde123/version-incremental:build-type/build-tags',
-      build_info.fingerprint)
+        'product-brand/product-name/product-device:version-release/'
+        'legacy-build-id.abcde123/version-incremental:build-type/build-tags',
+        build_info.fingerprint)
 
   def test___getitem__(self):
     target_info = common.BuildInfo(self.TEST_INFO_DICT, None)
@@ -376,7 +376,7 @@
     info_dict['build.prop'].build_props[
         'ro.product.property_source_order'] = 'bad-source'
     with self.assertRaisesRegexp(common.ExternalError,
-        'Invalid ro.product.property_source_order'):
+                                 'Invalid ro.product.property_source_order'):
       info = common.BuildInfo(info_dict, None)
       info.GetBuildProp('ro.product.device')
 
@@ -459,7 +459,7 @@
       time.sleep(5)  # Make sure the atime/mtime will change measurably.
 
       common.ZipWrite(zip_file, test_file_name, **extra_zipwrite_args)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       self._verify(zip_file, zip_file_name, arcname, sha1_hash.hexdigest(),
                    test_file_name, expected_stat, expected_mode,
@@ -494,7 +494,7 @@
         expected_mode = extra_args.get("perms", zinfo_perms)
 
       common.ZipWriteStr(zip_file, zinfo_or_arcname, contents, **extra_args)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       self._verify(zip_file, zip_file_name, arcname, sha1(contents).hexdigest(),
                    expected_mode=expected_mode,
@@ -536,7 +536,7 @@
 
       common.ZipWrite(zip_file, test_file_name, **extra_args)
       common.ZipWriteStr(zip_file, arcname_small, small, **extra_args)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       # Verify the contents written by ZipWrite().
       self._verify(zip_file, zip_file_name, arcname_large,
@@ -551,12 +551,6 @@
       os.remove(zip_file_name)
       os.remove(test_file_name)
 
-  def _test_reset_ZIP64_LIMIT(self, func, *args):
-    default_limit = (1 << 31) - 1
-    self.assertEqual(default_limit, zipfile.ZIP64_LIMIT)
-    func(*args)
-    self.assertEqual(default_limit, zipfile.ZIP64_LIMIT)
-
   def test_ZipWrite(self):
     file_contents = os.urandom(1024)
     self._test_ZipWrite(file_contents)
@@ -581,7 +575,7 @@
     })
 
   def test_ZipWrite_resets_ZIP64_LIMIT(self):
-    self._test_reset_ZIP64_LIMIT(self._test_ZipWrite, "")
+    self._test_ZipWrite("")
 
   def test_ZipWriteStr(self):
     random_string = os.urandom(1024)
@@ -632,9 +626,9 @@
     })
 
   def test_ZipWriteStr_resets_ZIP64_LIMIT(self):
-    self._test_reset_ZIP64_LIMIT(self._test_ZipWriteStr, 'foo', b'')
+    self._test_ZipWriteStr('foo', b'')
     zinfo = zipfile.ZipInfo(filename="foo")
-    self._test_reset_ZIP64_LIMIT(self._test_ZipWriteStr, zinfo, b'')
+    self._test_ZipWriteStr(zinfo, b'')
 
   def test_bug21309935(self):
     zip_file = tempfile.NamedTemporaryFile(delete=False)
@@ -656,7 +650,7 @@
       zinfo = zipfile.ZipInfo(filename="qux")
       zinfo.external_attr = 0o700 << 16
       common.ZipWriteStr(zip_file, zinfo, random_string, perms=0o400)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       self._verify(zip_file, zip_file_name, "foo",
                    sha1(random_string).hexdigest(),
@@ -683,7 +677,7 @@
       common.ZipWrite(output_zip, entry_file.name, arcname='Test1')
       common.ZipWrite(output_zip, entry_file.name, arcname='Test2')
       common.ZipWrite(output_zip, entry_file.name, arcname='Test3')
-      common.ZipClose(output_zip)
+      output_zip.close()
     zip_file.close()
 
     try:
@@ -731,8 +725,8 @@
       common.ZipWrite(output_zip, entry_file.name, arcname='Foo3')
       common.ZipWrite(output_zip, entry_file.name, arcname='Bar4')
       common.ZipWrite(output_zip, entry_file.name, arcname='Dir5/Baz5')
-      common.ZipClose(output_zip)
-    common.ZipClose(output_zip)
+      output_zip.close()
+    output_zip.close()
     return zip_file
 
   @test_utils.SkipIfExternalToolsUnavailable()
@@ -819,9 +813,9 @@
   )
 
   APKCERTS_CERTMAP1 = {
-      'RecoveryLocalizer.apk' : 'certs/devkey',
-      'Settings.apk' : 'build/make/target/product/security/platform',
-      'TV.apk' : 'PRESIGNED',
+      'RecoveryLocalizer.apk': 'certs/devkey',
+      'Settings.apk': 'build/make/target/product/security/platform',
+      'TV.apk': 'PRESIGNED',
   }
 
   APKCERTS_TXT2 = (
@@ -836,10 +830,10 @@
   )
 
   APKCERTS_CERTMAP2 = {
-      'Compressed1.apk' : 'certs/compressed1',
-      'Compressed2a.apk' : 'certs/compressed2',
-      'Compressed2b.apk' : 'certs/compressed2',
-      'Compressed3.apk' : 'certs/compressed3',
+      'Compressed1.apk': 'certs/compressed1',
+      'Compressed2a.apk': 'certs/compressed2',
+      'Compressed2b.apk': 'certs/compressed2',
+      'Compressed3.apk': 'certs/compressed3',
   }
 
   APKCERTS_TXT3 = (
@@ -848,7 +842,7 @@
   )
 
   APKCERTS_CERTMAP3 = {
-      'Compressed4.apk' : 'certs/compressed4',
+      'Compressed4.apk': 'certs/compressed4',
   }
 
   # Test parsing with no optional fields, both optional fields, and only the
@@ -865,9 +859,9 @@
   )
 
   APKCERTS_CERTMAP4 = {
-      'RecoveryLocalizer.apk' : 'certs/devkey',
-      'Settings.apk' : 'build/make/target/product/security/platform',
-      'TV.apk' : 'PRESIGNED',
+      'RecoveryLocalizer.apk': 'certs/devkey',
+      'Settings.apk': 'build/make/target/product/security/platform',
+      'TV.apk': 'PRESIGNED',
   }
 
   def setUp(self):
@@ -971,7 +965,7 @@
     extracted_from_privkey = common.ExtractAvbPublicKey('avbtool', privkey)
     extracted_from_pubkey = common.ExtractAvbPublicKey('avbtool', pubkey)
     with open(extracted_from_privkey, 'rb') as privkey_fp, \
-        open(extracted_from_pubkey, 'rb') as pubkey_fp:
+            open(extracted_from_pubkey, 'rb') as pubkey_fp:
       self.assertEqual(privkey_fp.read(), pubkey_fp.read())
 
   def test_ParseCertificate(self):
@@ -1235,7 +1229,8 @@
     self.assertEqual(
         '1-5 9-10',
         sparse_image.file_map['//system/file1'].extra['text_str'])
-    self.assertTrue(sparse_image.file_map['//system/file2'].extra['incomplete'])
+    self.assertTrue(
+        sparse_image.file_map['//system/file2'].extra['incomplete'])
     self.assertTrue(
         sparse_image.file_map['/system/app/file3'].extra['incomplete'])
 
@@ -1343,7 +1338,7 @@
       'recovery_api_version': 3,
       'fstab_version': 2,
       'system_root_image': 'true',
-      'no_recovery' : 'true',
+      'no_recovery': 'true',
       'recovery_as_boot': 'true',
   }
 
@@ -1664,6 +1659,7 @@
     self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
                       test_file.name, 'generic_kernel')
 
+
 class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
   """Checks the format of install-recovery.sh.
 
@@ -1673,7 +1669,7 @@
   def setUp(self):
     self._tempdir = common.MakeTempDir()
     # Create a fake dict that contains the fstab info for boot&recovery.
-    self._info = {"fstab" : {}}
+    self._info = {"fstab": {}}
     fake_fstab = [
         "/dev/soc.0/by-name/boot /boot emmc defaults defaults",
         "/dev/soc.0/by-name/recovery /recovery emmc defaults defaults"]
@@ -2020,11 +2016,11 @@
           input_zip, 'odm', placeholder_values)
 
     self.assertEqual({
-      'ro.odm.build.date.utc': '1578430045',
-      'ro.odm.build.fingerprint':
-      'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
-      'ro.product.odm.device': 'coral',
-      'ro.product.odm.name': 'product1',
+        'ro.odm.build.date.utc': '1578430045',
+        'ro.odm.build.fingerprint':
+        'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
+        'ro.product.odm.device': 'coral',
+        'ro.product.odm.name': 'product1',
     }, partition_props.build_props)
 
     with zipfile.ZipFile(input_file, 'r', allowZip64=True) as input_zip:
@@ -2207,8 +2203,8 @@
 
     copied_props = copy.deepcopy(partition_props)
     self.assertEqual({
-      'ro.odm.build.date.utc': '1578430045',
-      'ro.odm.build.fingerprint':
-      'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
-      'ro.product.odm.device': 'coral',
+        'ro.odm.build.date.utc': '1578430045',
+        'ro.odm.build.fingerprint':
+        'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
+        'ro.product.odm.device': 'coral',
     }, copied_props.build_props)