Merge "Disable ubsan diagnostics under HWASan."
diff --git a/android/sdk.go b/android/sdk.go
index 36c576d..93beb6e 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -300,6 +300,22 @@
 	Name() string
 }
 
+// BpPrintable is a marker interface that must be implemented by any struct that is added as a
+// property value.
+type BpPrintable interface {
+	bpPrintable()
+}
+
+// BpPrintableBase must be embedded within any struct that is added as a
+// property value.
+type BpPrintableBase struct {
+}
+
+func (b BpPrintableBase) bpPrintable() {
+}
+
+var _ BpPrintable = BpPrintableBase{}
+
 // An individual member of the SDK, includes all of the variants that the SDK
 // requires.
 type SdkMember interface {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 88da21b..792f7f3 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -1695,6 +1695,36 @@
 	expectNoLink("libx", "shared_apex10000", "libz", "shared")
 }
 
+func TestApexMinSdkVersion_SupportsCodeNames_JavaLibs(t *testing.T) {
+	testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["libx"],
+			min_sdk_version: "S",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "libx",
+			srcs: ["a.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "S", // should be okay
+		}
+	`,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"S"}
+			variables.Platform_sdk_codename = proptools.StringPtr("S")
+		}),
+	)
+}
+
 func TestApexMinSdkVersion_DefaultsToLatest(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -3338,60 +3368,109 @@
 }
 
 func TestVndkApexCurrent(t *testing.T) {
-	ctx := testApex(t, `
-		apex_vndk {
-			name: "com.android.vndk.current",
-			key: "com.android.vndk.current.key",
-			updatable: false,
-		}
-
-		apex_key {
-			name: "com.android.vndk.current.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "libvndk",
-			srcs: ["mylib.cpp"],
-			vendor_available: true,
-			product_available: true,
-			vndk: {
-				enabled: true,
-			},
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "com.android.vndk.current" ],
-		}
-
-		cc_library {
-			name: "libvndksp",
-			srcs: ["mylib.cpp"],
-			vendor_available: true,
-			product_available: true,
-			vndk: {
-				enabled: true,
-				support_system_process: true,
-			},
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "com.android.vndk.current" ],
-		}
-	`+vndkLibrariesTxtFiles("current"))
-
-	ensureExactContents(t, ctx, "com.android.vndk.current", "android_common_image", []string{
-		"lib/libvndk.so",
-		"lib/libvndksp.so",
+	commonFiles := []string{
 		"lib/libc++.so",
-		"lib64/libvndk.so",
-		"lib64/libvndksp.so",
 		"lib64/libc++.so",
 		"etc/llndk.libraries.29.txt",
 		"etc/vndkcore.libraries.29.txt",
 		"etc/vndksp.libraries.29.txt",
 		"etc/vndkprivate.libraries.29.txt",
 		"etc/vndkproduct.libraries.29.txt",
-	})
+	}
+	testCases := []struct {
+		vndkVersion   string
+		expectedFiles []string
+	}{
+		{
+			vndkVersion: "current",
+			expectedFiles: append(commonFiles,
+				"lib/libvndk.so",
+				"lib/libvndksp.so",
+				"lib64/libvndk.so",
+				"lib64/libvndksp.so"),
+		},
+		{
+			vndkVersion: "",
+			expectedFiles: append(commonFiles,
+				// Legacy VNDK APEX contains only VNDK-SP files (of core variant)
+				"lib/libvndksp.so",
+				"lib64/libvndksp.so"),
+		},
+	}
+	for _, tc := range testCases {
+		t.Run("VNDK.current with DeviceVndkVersion="+tc.vndkVersion, func(t *testing.T) {
+			ctx := testApex(t, `
+			apex_vndk {
+				name: "com.android.vndk.current",
+				key: "com.android.vndk.current.key",
+				updatable: false,
+			}
+
+			apex_key {
+				name: "com.android.vndk.current.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			cc_library {
+				name: "libvndk",
+				srcs: ["mylib.cpp"],
+				vendor_available: true,
+				product_available: true,
+				vndk: {
+					enabled: true,
+				},
+				system_shared_libs: [],
+				stl: "none",
+				apex_available: [ "com.android.vndk.current" ],
+			}
+
+			cc_library {
+				name: "libvndksp",
+				srcs: ["mylib.cpp"],
+				vendor_available: true,
+				product_available: true,
+				vndk: {
+					enabled: true,
+					support_system_process: true,
+				},
+				system_shared_libs: [],
+				stl: "none",
+				apex_available: [ "com.android.vndk.current" ],
+			}
+
+			// VNDK-Ext should not cause any problems
+
+			cc_library {
+				name: "libvndk.ext",
+				srcs: ["mylib2.cpp"],
+				vendor: true,
+				vndk: {
+					enabled: true,
+					extends: "libvndk",
+				},
+				system_shared_libs: [],
+				stl: "none",
+			}
+
+			cc_library {
+				name: "libvndksp.ext",
+				srcs: ["mylib2.cpp"],
+				vendor: true,
+				vndk: {
+					enabled: true,
+					support_system_process: true,
+					extends: "libvndksp",
+				},
+				system_shared_libs: [],
+				stl: "none",
+			}
+		`+vndkLibrariesTxtFiles("current"), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.DeviceVndkVersion = proptools.StringPtr(tc.vndkVersion)
+			}))
+			ensureExactContents(t, ctx, "com.android.vndk.current", "android_common_image", tc.expectedFiles)
+		})
+	}
 }
 
 func TestVndkApexWithPrebuilt(t *testing.T) {
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index bd4a9d5..279cf54 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -481,3 +481,62 @@
 	pairs := java.ApexNamePairsFromModules(ctx, modules)
 	android.AssertDeepEquals(t, "module dependencies", expected, pairs)
 }
+
+// TestPlatformBootclasspath_IncludesRemainingApexJars verifies that any apex boot jar is present in
+// platform_bootclasspath's classpaths.proto config, if the apex does not generate its own config
+// by setting generate_classpaths_proto property to false.
+func TestPlatformBootclasspath_IncludesRemainingApexJars(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		prepareForTestWithMyapex,
+		java.FixtureConfigureUpdatableBootJars("myapex:foo"),
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				fragments: [
+					{
+						apex: "myapex",
+						module:"foo-fragment",
+					},
+				],
+			}
+
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				bootclasspath_fragments: ["foo-fragment"],
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			bootclasspath_fragment {
+				name: "foo-fragment",
+				generate_classpaths_proto: false,
+				contents: ["foo"],
+				apex_available: ["myapex"],
+			}
+
+			java_library {
+				name: "foo",
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+				apex_available: ["myapex"],
+				permitted_packages: ["foo"],
+			}
+		`),
+	).RunTest(t)
+
+	java.CheckClasspathFragmentProtoContentInfoProvider(t, result,
+		true,         // proto should be generated
+		"myapex:foo", // apex doesn't generate its own config, so must be in platform_bootclasspath
+		"bootclasspath.pb",
+		"out/soong/target/product/test_device/system/etc/classpaths",
+	)
+}
diff --git a/cc/vndk.go b/cc/vndk.go
index dd1c3e1..0b40076 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -371,7 +371,7 @@
 			if mctx.ModuleName() == "libz" {
 				return false
 			}
-			return m.ImageVariation().Variation == android.CoreVariation && lib.shared() && m.IsVndkSp()
+			return m.ImageVariation().Variation == android.CoreVariation && lib.shared() && m.IsVndkSp() && !m.IsVndkExt()
 		}
 
 		useCoreVariant := m.VndkVersion() == mctx.DeviceConfig().PlatformVndkVersion() &&
diff --git a/java/base.go b/java/base.go
index a7cc58e..a251c3f 100644
--- a/java/base.go
+++ b/java/base.go
@@ -260,8 +260,8 @@
 	EmbeddableSdkLibraryComponent
 }
 
-func (e *embeddableInModuleAndImport) initModuleAndImport(moduleBase *android.ModuleBase) {
-	e.initSdkLibraryComponent(moduleBase)
+func (e *embeddableInModuleAndImport) initModuleAndImport(module android.Module) {
+	e.initSdkLibraryComponent(module)
 }
 
 // Module/Import's DepIsInSameApex(...) delegates to this method.
@@ -1513,12 +1513,8 @@
 	if sdkSpec.Kind == android.SdkCore {
 		return nil
 	}
-	ver, err := sdkSpec.EffectiveVersion(ctx)
-	if err != nil {
-		return err
-	}
-	if ver.GreaterThan(sdkVersion) {
-		return fmt.Errorf("newer SDK(%v)", ver)
+	if sdkSpec.ApiLevel.GreaterThan(sdkVersion) {
+		return fmt.Errorf("newer SDK(%v)", sdkSpec.ApiLevel)
 	}
 	return nil
 }
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index eddcc83..ccb69a0 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -144,6 +144,8 @@
 
 // ApexVariantReference specifies a particular apex variant of a module.
 type ApexVariantReference struct {
+	android.BpPrintableBase
+
 	// The name of the module apex variant, i.e. the apex containing the module variant.
 	//
 	// If this is not specified then it defaults to "platform" which will cause a dependency to be
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 4d23820..c7249b0 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -496,13 +496,14 @@
 // generateClasspathProtoBuildActions generates all required build actions for classpath.proto config
 func (b *BootclasspathFragmentModule) generateClasspathProtoBuildActions(ctx android.ModuleContext) {
 	var classpathJars []classpathJar
+	configuredJars := b.configuredJars(ctx)
 	if "art" == proptools.String(b.properties.Image_name) {
 		// ART and platform boot jars must have a corresponding entry in DEX2OATBOOTCLASSPATH
-		classpathJars = configuredJarListToClasspathJars(ctx, b.configuredJars(ctx), BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
+		classpathJars = configuredJarListToClasspathJars(ctx, configuredJars, BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
 	} else {
-		classpathJars = configuredJarListToClasspathJars(ctx, b.configuredJars(ctx), b.classpathType)
+		classpathJars = configuredJarListToClasspathJars(ctx, configuredJars, b.classpathType)
 	}
-	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 }
 
 func (b *BootclasspathFragmentModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
@@ -748,6 +749,9 @@
 	Stub_libs               []string
 	Core_platform_stub_libs []string
 
+	// Fragment properties
+	Fragments []ApexVariantReference
+
 	// Flag files by *hiddenAPIFlagFileCategory
 	Flag_files_by_category FlagFilesByCategory
 
@@ -788,6 +792,9 @@
 	// Copy stub_libs properties.
 	b.Stub_libs = module.properties.Api.Stub_libs
 	b.Core_platform_stub_libs = module.properties.Core_platform_api.Stub_libs
+
+	// Copy fragment properties.
+	b.Fragments = module.properties.Fragments
 }
 
 func (b *bootclasspathFragmentSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
@@ -810,6 +817,9 @@
 		corePlatformApiPropertySet := propertySet.AddPropertySet("core_platform_api")
 		corePlatformApiPropertySet.AddPropertyWithTag("stub_libs", b.Core_platform_stub_libs, requiredMemberDependency)
 	}
+	if len(b.Fragments) > 0 {
+		propertySet.AddProperty("fragments", b.Fragments)
+	}
 
 	hiddenAPISet := propertySet.AddPropertySet("hidden_api")
 	hiddenAPIDir := "hiddenapi"
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index 3d72580..f7a200a 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -106,7 +106,7 @@
 	return jars
 }
 
-func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, jars []classpathJar) {
+func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, jars []classpathJar) {
 	generateProto := proptools.BoolDefault(c.properties.Generate_classpaths_proto, true)
 	if generateProto {
 		outputFilename := strings.ToLower(c.classpathType.String()) + ".pb"
@@ -129,6 +129,7 @@
 
 	classpathProtoInfo := ClasspathFragmentProtoContentInfo{
 		ClasspathFragmentProtoGenerated:  generateProto,
+		ClasspathFragmentProtoContents:   configuredJars,
 		ClasspathFragmentProtoInstallDir: c.installDirPath,
 		ClasspathFragmentProtoOutput:     c.outputFilepath,
 	}
@@ -177,6 +178,9 @@
 	// Whether the classpaths.proto config is generated for the fragment.
 	ClasspathFragmentProtoGenerated bool
 
+	// ClasspathFragmentProtoContents contains a list of jars that are part of this classpath fragment.
+	ClasspathFragmentProtoContents android.ConfiguredJarList
+
 	// ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module.
 	//
 	// The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir
diff --git a/java/java.go b/java/java.go
index ae8adf2..be1ad87 100644
--- a/java/java.go
+++ b/java/java.go
@@ -643,7 +643,7 @@
 
 	module.addHostAndDeviceProperties()
 
-	module.initModuleAndImport(&module.ModuleBase)
+	module.initModuleAndImport(module)
 
 	android.InitApexModule(module)
 	android.InitSdkAwareModule(module)
@@ -1416,12 +1416,8 @@
 	if sdkSpec.Kind == android.SdkCore {
 		return nil
 	}
-	ver, err := sdkSpec.EffectiveVersion(ctx)
-	if err != nil {
-		return err
-	}
-	if ver.GreaterThan(sdkVersion) {
-		return fmt.Errorf("newer SDK(%v)", ver)
+	if sdkSpec.ApiLevel.GreaterThan(sdkVersion) {
+		return fmt.Errorf("newer SDK(%v)", sdkSpec.ApiLevel)
 	}
 	return nil
 }
@@ -1496,7 +1492,7 @@
 		&module.dexer.dexProperties,
 	)
 
-	module.initModuleAndImport(&module.ModuleBase)
+	module.initModuleAndImport(module)
 
 	module.dexProperties.Optimize.EnabledByDefault = false
 
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 2a6a83e..a444de0 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -198,13 +198,29 @@
 
 // Generate classpaths.proto config
 func (b *platformBootclasspathModule) generateClasspathProtoBuildActions(ctx android.ModuleContext) {
+	configuredJars := b.configuredJars(ctx)
 	// ART and platform boot jars must have a corresponding entry in DEX2OATBOOTCLASSPATH
-	classpathJars := configuredJarListToClasspathJars(ctx, b.configuredJars(ctx), BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
-	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
+	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 }
 
 func (b *platformBootclasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
-	return b.getImageConfig(ctx).modules
+	// Include all non APEX jars
+	jars := b.getImageConfig(ctx).modules
+
+	// Include jars from APEXes that don't populate their classpath proto config.
+	remainingJars := dexpreopt.GetGlobalConfig(ctx).UpdatableBootJars
+	for _, fragment := range b.fragments {
+		info := ctx.OtherModuleProvider(fragment, ClasspathFragmentProtoContentInfoProvider).(ClasspathFragmentProtoContentInfo)
+		if info.ClasspathFragmentProtoGenerated {
+			remainingJars = remainingJars.RemoveList(info.ClasspathFragmentProtoContents)
+		}
+	}
+	for i := 0; i < remainingJars.Len(); i++ {
+		jars = jars.Append(remainingJars.Apex(i), remainingJars.Jar(i))
+	}
+
+	return jars
 }
 
 // checkNonUpdatableModules ensures that the non-updatable modules supplied are not part of an
diff --git a/java/sdk_library.go b/java/sdk_library.go
index dae31b9..101a658 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -631,9 +631,17 @@
 	Doctag_files []string `android:"path"`
 }
 
+// commonSdkLibraryAndImportModule defines the interface that must be provided by a module that
+// embeds the commonToSdkLibraryAndImport struct.
+type commonSdkLibraryAndImportModule interface {
+	android.Module
+
+	BaseModuleName() string
+}
+
 // Common code between sdk library and sdk library import
 type commonToSdkLibraryAndImport struct {
-	moduleBase *android.ModuleBase
+	module commonSdkLibraryAndImportModule
 
 	scopePaths map[*apiScope]*scopePaths
 
@@ -648,13 +656,13 @@
 	EmbeddableSdkLibraryComponent
 }
 
-func (c *commonToSdkLibraryAndImport) initCommon(moduleBase *android.ModuleBase) {
-	c.moduleBase = moduleBase
+func (c *commonToSdkLibraryAndImport) initCommon(module commonSdkLibraryAndImportModule) {
+	c.module = module
 
-	moduleBase.AddProperties(&c.commonSdkLibraryProperties)
+	module.AddProperties(&c.commonSdkLibraryProperties)
 
 	// Initialize this as an sdk library component.
-	c.initSdkLibraryComponent(moduleBase)
+	c.initSdkLibraryComponent(module)
 }
 
 func (c *commonToSdkLibraryAndImport) initCommonAfterDefaultsApplied(ctx android.DefaultableHookContext) bool {
@@ -670,7 +678,7 @@
 	// Only track this sdk library if this can be used as a shared library.
 	if c.sharedLibrary() {
 		// Use the name specified in the module definition as the owner.
-		c.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.moduleBase.BaseModuleName())
+		c.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.module.BaseModuleName())
 	}
 
 	return true
@@ -682,23 +690,23 @@
 
 // Module name of the runtime implementation library
 func (c *commonToSdkLibraryAndImport) implLibraryModuleName() string {
-	return c.moduleBase.BaseModuleName() + ".impl"
+	return c.module.BaseModuleName() + ".impl"
 }
 
 // Module name of the XML file for the lib
 func (c *commonToSdkLibraryAndImport) xmlPermissionsModuleName() string {
-	return c.moduleBase.BaseModuleName() + sdkXmlFileSuffix
+	return c.module.BaseModuleName() + sdkXmlFileSuffix
 }
 
 // Name of the java_library module that compiles the stubs source.
 func (c *commonToSdkLibraryAndImport) stubsLibraryModuleName(apiScope *apiScope) string {
-	return c.namingScheme.stubsLibraryModuleName(apiScope, c.moduleBase.BaseModuleName())
+	return c.namingScheme.stubsLibraryModuleName(apiScope, c.module.BaseModuleName())
 }
 
 // Name of the droidstubs module that generates the stubs source and may also
 // generate/check the API.
 func (c *commonToSdkLibraryAndImport) stubsSourceModuleName(apiScope *apiScope) string {
-	return c.namingScheme.stubsSourceModuleName(apiScope, c.moduleBase.BaseModuleName())
+	return c.namingScheme.stubsSourceModuleName(apiScope, c.module.BaseModuleName())
 }
 
 // The component names for different outputs of the java_sdk_library.
@@ -747,7 +755,7 @@
 		if scope, ok := scopeByName[scopeName]; ok {
 			paths := c.findScopePaths(scope)
 			if paths == nil {
-				return nil, fmt.Errorf("%q does not provide api scope %s", c.moduleBase.BaseModuleName(), scopeName)
+				return nil, fmt.Errorf("%q does not provide api scope %s", c.module.BaseModuleName(), scopeName)
 			}
 
 			switch component {
@@ -778,7 +786,7 @@
 			if c.doctagPaths != nil {
 				return c.doctagPaths, nil
 			} else {
-				return nil, fmt.Errorf("no doctag_files specified on %s", c.moduleBase.BaseModuleName())
+				return nil, fmt.Errorf("no doctag_files specified on %s", c.module.BaseModuleName())
 			}
 		}
 		return nil, nil
@@ -824,7 +832,7 @@
 
 	// If a specific numeric version has been requested then use prebuilt versions of the sdk.
 	if !sdkVersion.ApiLevel.IsPreview() {
-		return PrebuiltJars(ctx, c.moduleBase.BaseModuleName(), sdkVersion)
+		return PrebuiltJars(ctx, c.module.BaseModuleName(), sdkVersion)
 	}
 
 	paths := c.selectScopePaths(ctx, sdkVersion.Kind)
@@ -851,7 +859,7 @@
 				scopes = append(scopes, s.name)
 			}
 		}
-		ctx.ModuleErrorf("requires api scope %s from %s but it only has %q available", apiScope.name, c.moduleBase.BaseModuleName(), scopes)
+		ctx.ModuleErrorf("requires api scope %s from %s but it only has %q available", apiScope.name, c.module.BaseModuleName(), scopes)
 		return nil
 	}
 
@@ -907,7 +915,7 @@
 		// any app that includes code which depends (directly or indirectly) on the stubs
 		// library will have the appropriate <uses-library> invocation inserted into its
 		// manifest if necessary.
-		componentProps.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.moduleBase.BaseModuleName())
+		componentProps.SdkLibraryToImplicitlyTrack = proptools.StringPtr(c.module.BaseModuleName())
 	}
 
 	return componentProps
@@ -939,8 +947,8 @@
 	sdkLibraryComponentProperties SdkLibraryComponentProperties
 }
 
-func (e *EmbeddableSdkLibraryComponent) initSdkLibraryComponent(moduleBase *android.ModuleBase) {
-	moduleBase.AddProperties(&e.sdkLibraryComponentProperties)
+func (e *EmbeddableSdkLibraryComponent) initSdkLibraryComponent(module android.Module) {
+	module.AddProperties(&e.sdkLibraryComponentProperties)
 }
 
 // to satisfy SdkLibraryComponentDependency
@@ -1707,7 +1715,7 @@
 	module.addHostAndDeviceProperties()
 	module.AddProperties(&module.sdkLibraryProperties)
 
-	module.initSdkLibraryComponent(&module.ModuleBase)
+	module.initSdkLibraryComponent(module)
 
 	module.properties.Installable = proptools.BoolPtr(true)
 	module.deviceProperties.IsSDKLibrary = true
@@ -1772,7 +1780,7 @@
 	module := &SdkLibrary{}
 
 	// Initialize information common between source and prebuilt.
-	module.initCommon(&module.ModuleBase)
+	module.initCommon(module)
 
 	module.InitSdkLibraryProperties()
 	android.InitApexModule(module)
@@ -1920,7 +1928,7 @@
 	module.AddProperties(&module.properties, allScopeProperties)
 
 	// Initialize information common between source and prebuilt.
-	module.initCommon(&module.ModuleBase)
+	module.initCommon(module)
 
 	android.InitPrebuiltModule(module, &[]string{""})
 	android.InitApexModule(module)
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index a0decb7..252c615 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -48,13 +48,14 @@
 }
 
 func (p *platformSystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	classpathJars := configuredJarListToClasspathJars(ctx, p.configuredJars(ctx), p.classpathType)
-	p.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	configuredJars := p.configuredJars(ctx)
+	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, p.classpathType)
+	p.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 }
 
 func (p *platformSystemServerClasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
-	global := dexpreopt.GetGlobalConfig(ctx)
-	return global.SystemServerJars
+	// TODO(satayev): include any apex jars that don't populate their classpath proto config.
+	return dexpreopt.GetGlobalConfig(ctx).SystemServerJars
 }
 
 type SystemServerClasspathModule struct {
@@ -94,8 +95,9 @@
 		ctx.PropertyErrorf("contents", "empty contents are not allowed")
 	}
 
-	classpathJars := configuredJarListToClasspathJars(ctx, s.configuredJars(ctx), s.classpathType)
-	s.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	configuredJars := s.configuredJars(ctx)
+	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, s.classpathType)
+	s.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
 	s.modulePaths = append(s.modulePaths, ctx.ModuleDir())
diff --git a/java/testing.go b/java/testing.go
index 7b452f7..c3803c8 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -363,6 +363,17 @@
 	android.AssertDeepEquals(t, fmt.Sprintf("%s modules", "platform-bootclasspath"), expected, pairs)
 }
 
+func CheckClasspathFragmentProtoContentInfoProvider(t *testing.T, result *android.TestResult, generated bool, contents, outputFilename, installDir string) {
+	t.Helper()
+	p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
+	info := result.ModuleProvider(p, ClasspathFragmentProtoContentInfoProvider).(ClasspathFragmentProtoContentInfo)
+
+	android.AssertBoolEquals(t, "classpath proto generated", generated, info.ClasspathFragmentProtoGenerated)
+	android.AssertStringEquals(t, "classpath proto contents", contents, info.ClasspathFragmentProtoContents.String())
+	android.AssertStringEquals(t, "output filepath", outputFilename, info.ClasspathFragmentProtoOutput.Base())
+	android.AssertPathRelativeToTopEquals(t, "install filepath", installDir, info.ClasspathFragmentProtoInstallDir)
+}
+
 // ApexNamePairsFromModules returns the apex:module pair for the supplied modules.
 func ApexNamePairsFromModules(ctx *android.TestContext, modules []android.Module) []string {
 	pairs := []string{}
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index 378f7cf..d47eb1f 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -511,6 +511,127 @@
 	)
 }
 
+// TestSnapshotWithBootClasspathFragment_Fragments makes sure that the fragments property of a
+// bootclasspath_fragment is correctly output to the sdk snapshot.
+func TestSnapshotWithBootClasspathFragment_Fragments(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		java.PrepareForTestWithJavaDefaultModules,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mysdklibrary", "myothersdklibrary"),
+		prepareForSdkTestWithApex,
+
+		// Some additional files needed for the myotherapex.
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/myotherapex-file_contexts": nil,
+			"myotherapex/apex_manifest.json":                 nil,
+			"myotherapex/Test.java":                          nil,
+		}),
+
+		android.FixtureAddTextFile("myotherapex/Android.bp", `
+			apex {
+				name: "myotherapex",
+				key: "myapex.key",
+				min_sdk_version: "2",
+				bootclasspath_fragments: ["myotherbootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "myotherbootclasspathfragment",
+				apex_available: ["myotherapex"],
+				contents: [
+					"myotherlib",
+				],
+			}
+
+			java_library {
+				name: "myotherlib",
+				apex_available: ["myotherapex"],
+				srcs: ["Test.java"],
+				min_sdk_version: "2",
+				permitted_packages: ["myothersdklibrary"],
+				compile_dex: true,
+			}
+		`),
+
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				contents: [
+					"mysdklibrary",
+				],
+				fragments: [
+					{
+						apex: "myotherapex",
+						module: "myotherbootclasspathfragment"
+					},
+				],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				srcs: ["Test.java"],
+				shared_library: false,
+				public: {enabled: true},
+				min_sdk_version: "2",
+			}
+		`),
+	).RunTest(t)
+
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("myapex", "mybootclasspathfragment")
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    contents: ["mysdklibrary"],
+    fragments: [
+        {
+            apex: "myotherapex",
+            module: "myotherbootclasspathfragment",
+        },
+    ],
+    hidden_api: {
+        stub_flags: "hiddenapi/stub-flags.csv",
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        all_flags: "hiddenapi/all-flags.csv",
+    },
+}
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+		`),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
+	)
+}
+
 // Test that bootclasspath_fragment works with sdk.
 func TestBasicSdkWithBootclasspathFragment(t *testing.T) {
 	android.GroupFixturePreparers(
diff --git a/sdk/update.go b/sdk/update.go
index 36b564f..e2e5997 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -114,8 +114,16 @@
 	gc.indentLevel--
 }
 
-func (gc *generatedContents) Printfln(format string, args ...interface{}) {
-	fmt.Fprintf(&(gc.content), strings.Repeat("    ", gc.indentLevel)+format+"\n", args...)
+// IndentedPrintf will add spaces to indent the line to the appropriate level before printing the
+// arguments.
+func (gc *generatedContents) IndentedPrintf(format string, args ...interface{}) {
+	fmt.Fprintf(&(gc.content), strings.Repeat("    ", gc.indentLevel)+format, args...)
+}
+
+// UnindentedPrintf does not add spaces to indent the line to the appropriate level before printing
+// the arguments.
+func (gc *generatedContents) UnindentedPrintf(format string, args ...interface{}) {
+	fmt.Fprintf(&(gc.content), format, args...)
 }
 
 func (gf *generatedFile) build(pctx android.PackageContext, ctx android.BuilderContext, implicits android.Paths) {
@@ -742,13 +750,13 @@
 }
 
 func generateFilteredBpContents(contents *generatedContents, bpFile *bpFile, moduleFilter func(module *bpModule) bool) {
-	contents.Printfln("// This is auto-generated. DO NOT EDIT.")
+	contents.IndentedPrintf("// This is auto-generated. DO NOT EDIT.\n")
 	for _, bpModule := range bpFile.order {
 		if moduleFilter(bpModule) {
-			contents.Printfln("")
-			contents.Printfln("%s {", bpModule.moduleType)
+			contents.IndentedPrintf("\n")
+			contents.IndentedPrintf("%s {\n", bpModule.moduleType)
 			outputPropertySet(contents, bpModule.bpPropertySet)
-			contents.Printfln("}")
+			contents.IndentedPrintf("}\n")
 		}
 	}
 }
@@ -759,7 +767,7 @@
 	addComment := func(name string) {
 		if text, ok := set.comments[name]; ok {
 			for _, line := range strings.Split(text, "\n") {
-				contents.Printfln("// %s", line)
+				contents.IndentedPrintf("// %s\n", line)
 			}
 		}
 	}
@@ -776,29 +784,8 @@
 		}
 
 		addComment(name)
-		switch v := value.(type) {
-		case []string:
-			length := len(v)
-			if length > 1 {
-				contents.Printfln("%s: [", name)
-				contents.Indent()
-				for i := 0; i < length; i = i + 1 {
-					contents.Printfln("%q,", v[i])
-				}
-				contents.Dedent()
-				contents.Printfln("],")
-			} else if length == 0 {
-				contents.Printfln("%s: [],", name)
-			} else {
-				contents.Printfln("%s: [%q],", name, v[0])
-			}
-
-		case bool:
-			contents.Printfln("%s: %t,", name, v)
-
-		default:
-			contents.Printfln("%s: %q,", name, value)
-		}
+		reflectValue := reflect.ValueOf(value)
+		outputNamedValue(contents, name, reflectValue)
 	}
 
 	for _, name := range set.order {
@@ -808,15 +795,94 @@
 		switch v := value.(type) {
 		case *bpPropertySet:
 			addComment(name)
-			contents.Printfln("%s: {", name)
+			contents.IndentedPrintf("%s: {\n", name)
 			outputPropertySet(contents, v)
-			contents.Printfln("},")
+			contents.IndentedPrintf("},\n")
 		}
 	}
 
 	contents.Dedent()
 }
 
+// outputNamedValue outputs a value that has an associated name. The name will be indented, followed
+// by the value and then followed by a , and a newline.
+func outputNamedValue(contents *generatedContents, name string, value reflect.Value) {
+	contents.IndentedPrintf("%s: ", name)
+	outputUnnamedValue(contents, value)
+	contents.UnindentedPrintf(",\n")
+}
+
+// outputUnnamedValue outputs a single value. The value is not indented and is not followed by
+// either a , or a newline. With multi-line values, e.g. slices, all but the first line will be
+// indented and all but the last line will end with a newline.
+func outputUnnamedValue(contents *generatedContents, value reflect.Value) {
+	valueType := value.Type()
+	switch valueType.Kind() {
+	case reflect.Bool:
+		contents.UnindentedPrintf("%t", value.Bool())
+
+	case reflect.String:
+		contents.UnindentedPrintf("%q", value)
+
+	case reflect.Ptr:
+		outputUnnamedValue(contents, value.Elem())
+
+	case reflect.Slice:
+		length := value.Len()
+		if length == 0 {
+			contents.UnindentedPrintf("[]")
+		} else {
+			firstValue := value.Index(0)
+			if length == 1 && !multiLineValue(firstValue) {
+				contents.UnindentedPrintf("[")
+				outputUnnamedValue(contents, firstValue)
+				contents.UnindentedPrintf("]")
+			} else {
+				contents.UnindentedPrintf("[\n")
+				contents.Indent()
+				for i := 0; i < length; i++ {
+					itemValue := value.Index(i)
+					contents.IndentedPrintf("")
+					outputUnnamedValue(contents, itemValue)
+					contents.UnindentedPrintf(",\n")
+				}
+				contents.Dedent()
+				contents.IndentedPrintf("]")
+			}
+		}
+
+	case reflect.Struct:
+		// Avoid unlimited recursion by requiring every structure to implement android.BpPrintable.
+		v := value.Interface()
+		if _, ok := v.(android.BpPrintable); !ok {
+			panic(fmt.Errorf("property value %#v of type %T does not implement android.BpPrintable", v, v))
+		}
+		contents.UnindentedPrintf("{\n")
+		contents.Indent()
+		for f := 0; f < valueType.NumField(); f++ {
+			fieldType := valueType.Field(f)
+			if fieldType.Anonymous {
+				continue
+			}
+			fieldValue := value.Field(f)
+			fieldName := fieldType.Name
+			propertyName := proptools.PropertyNameForField(fieldName)
+			outputNamedValue(contents, propertyName, fieldValue)
+		}
+		contents.Dedent()
+		contents.IndentedPrintf("}")
+
+	default:
+		panic(fmt.Errorf("Unknown type: %T of value %#v", value, value))
+	}
+}
+
+// multiLineValue returns true if the supplied value may require multiple lines in the output.
+func multiLineValue(value reflect.Value) bool {
+	kind := value.Kind()
+	return kind == reflect.Slice || kind == reflect.Struct
+}
+
 func (s *sdk) GetAndroidBpContentsForTests() string {
 	contents := &generatedContents{}
 	generateBpContents(contents, s.builderForTests.bpFile)