1// Copyright 2020 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package java
16
17// This file contains the module implementations for android_app_import and android_test_import.
18
19import (
20	"reflect"
21
22	"github.com/google/blueprint/proptools"
23
24	"android/soong/android"
25)
26
27func init() {
28	RegisterAppImportBuildComponents(android.InitRegistrationContext)
29
30	initAndroidAppImportVariantGroupTypes()
31}
32
33func RegisterAppImportBuildComponents(ctx android.RegistrationContext) {
34	ctx.RegisterModuleType("android_app_import", AndroidAppImportFactory)
35	ctx.RegisterModuleType("android_test_import", AndroidTestImportFactory)
36}
37
38type AndroidAppImport struct {
39	android.ModuleBase
40	android.DefaultableModuleBase
41	android.ApexModuleBase
42	prebuilt android.Prebuilt
43
44	properties   AndroidAppImportProperties
45	dpiVariants  interface{}
46	archVariants interface{}
47
48	outputFile  android.Path
49	certificate Certificate
50
51	dexpreopter
52
53	usesLibrary usesLibrary
54
55	preprocessed bool
56
57	installPath android.InstallPath
58
59	hideApexVariantFromMake bool
60}
61
62type AndroidAppImportProperties struct {
63	// A prebuilt apk to import
64	Apk *string
65
66	// The name of a certificate in the default certificate directory or an android_app_certificate
67	// module name in the form ":module". Should be empty if presigned or default_dev_cert is set.
68	Certificate *string
69
70	// Names of extra android_app_certificate modules to sign the apk with in the form ":module".
71	Additional_certificates []string
72
73	// Set this flag to true if the prebuilt apk is already signed. The certificate property must not
74	// be set for presigned modules.
75	Presigned *bool
76
77	// Name of the signing certificate lineage file or filegroup module.
78	Lineage *string `android:"path"`
79
80	// Sign with the default system dev certificate. Must be used judiciously. Most imported apps
81	// need to either specify a specific certificate or be presigned.
82	Default_dev_cert *bool
83
84	// Specifies that this app should be installed to the priv-app directory,
85	// where the system will grant it additional privileges not available to
86	// normal apps.
87	Privileged *bool
88
89	// Names of modules to be overridden. Listed modules can only be other binaries
90	// (in Make or Soong).
91	// This does not completely prevent installation of the overridden binaries, but if both
92	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
93	// from PRODUCT_PACKAGES.
94	Overrides []string
95
96	// Optional name for the installed app. If unspecified, it is derived from the module name.
97	Filename *string
98
99	// If set, create package-export.apk, which other packages can
100	// use to get PRODUCT-agnostic resource data like IDs and type definitions.
101	Export_package_resources *bool
102}
103
104func (a *AndroidAppImport) IsInstallable() bool {
105	return true
106}
107
108// Updates properties with variant-specific values.
109func (a *AndroidAppImport) processVariants(ctx android.LoadHookContext) {
110	config := ctx.Config()
111
112	dpiProps := reflect.ValueOf(a.dpiVariants).Elem().FieldByName("Dpi_variants")
113	// Try DPI variant matches in the reverse-priority order so that the highest priority match
114	// overwrites everything else.
115	// TODO(jungjw): Can we optimize this by making it priority order?
116	for i := len(config.ProductAAPTPrebuiltDPI()) - 1; i >= 0; i-- {
117		MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPrebuiltDPI()[i])
118	}
119	if config.ProductAAPTPreferredConfig() != "" {
120		MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPreferredConfig())
121	}
122
123	archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName("Arch")
124	archType := ctx.Config().AndroidFirstDeviceTarget.Arch.ArchType
125	MergePropertiesFromVariant(ctx, &a.properties, archProps, archType.Name)
126
127	if String(a.properties.Apk) == "" {
128		// Disable this module since the apk property is still empty after processing all matching
129		// variants. This likely means there is no matching variant, and the default variant doesn't
130		// have an apk property value either.
131		a.Disable()
132	}
133}
134
135func MergePropertiesFromVariant(ctx android.EarlyModuleContext,
136	dst interface{}, variantGroup reflect.Value, variant string) {
137	src := variantGroup.FieldByName(proptools.FieldNameForProperty(variant))
138	if !src.IsValid() {
139		return
140	}
141
142	err := proptools.ExtendMatchingProperties([]interface{}{dst}, src.Interface(), nil, proptools.OrderAppend)
143	if err != nil {
144		if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
145			ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
146		} else {
147			panic(err)
148		}
149	}
150}
151
152func (a *AndroidAppImport) isPrebuiltFrameworkRes() bool {
153	return a.Name() == "prebuilt_framework-res"
154}
155
156func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) {
157	cert := android.SrcIsModule(String(a.properties.Certificate))
158	if cert != "" {
159		ctx.AddDependency(ctx.Module(), certificateTag, cert)
160	}
161
162	for _, cert := range a.properties.Additional_certificates {
163		cert = android.SrcIsModule(cert)
164		if cert != "" {
165			ctx.AddDependency(ctx.Module(), certificateTag, cert)
166		} else {
167			ctx.PropertyErrorf("additional_certificates",
168				`must be names of android_app_certificate modules in the form ":module"`)
169		}
170	}
171
172	a.usesLibrary.deps(ctx, !a.isPrebuiltFrameworkRes())
173}
174
175func (a *AndroidAppImport) uncompressEmbeddedJniLibs(
176	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
177	// Test apps don't need their JNI libraries stored uncompressed. As a matter of fact, messing
178	// with them may invalidate pre-existing signature data.
179	if ctx.InstallInTestcases() && (Bool(a.properties.Presigned) || a.preprocessed) {
180		ctx.Build(pctx, android.BuildParams{
181			Rule:   android.Cp,
182			Output: outputPath,
183			Input:  inputPath,
184		})
185		return
186	}
187	rule := android.NewRuleBuilder(pctx, ctx)
188	rule.Command().
189		Textf(`if (zipinfo %s 'lib/*.so' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
190		BuiltTool("zip2zip").
191		FlagWithInput("-i ", inputPath).
192		FlagWithOutput("-o ", outputPath).
193		FlagWithArg("-0 ", "'lib/**/*.so'").
194		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
195	rule.Build("uncompress-embedded-jni-libs", "Uncompress embedded JIN libs")
196}
197
198// Returns whether this module should have the dex file stored uncompressed in the APK.
199func (a *AndroidAppImport) shouldUncompressDex(ctx android.ModuleContext) bool {
200	if ctx.Config().UnbundledBuild() || a.preprocessed {
201		return false
202	}
203
204	// Uncompress dex in APKs of privileged apps
205	if ctx.Config().UncompressPrivAppDex() && a.Privileged() {
206		return true
207	}
208
209	return shouldUncompressDex(ctx, &a.dexpreopter)
210}
211
212func (a *AndroidAppImport) uncompressDex(
213	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
214	rule := android.NewRuleBuilder(pctx, ctx)
215	rule.Command().
216		Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
217		BuiltTool("zip2zip").
218		FlagWithInput("-i ", inputPath).
219		FlagWithOutput("-o ", outputPath).
220		FlagWithArg("-0 ", "'classes*.dex'").
221		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
222	rule.Build("uncompress-dex", "Uncompress dex files")
223}
224
225func (a *AndroidAppImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
226	a.generateAndroidBuildActions(ctx)
227}
228
229func (a *AndroidAppImport) InstallApkName() string {
230	return a.BaseModuleName()
231}
232
233func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext) {
234	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
235	if !apexInfo.IsForPlatform() {
236		a.hideApexVariantFromMake = true
237	}
238
239	numCertPropsSet := 0
240	if String(a.properties.Certificate) != "" {
241		numCertPropsSet++
242	}
243	if Bool(a.properties.Presigned) {
244		numCertPropsSet++
245	}
246	if Bool(a.properties.Default_dev_cert) {
247		numCertPropsSet++
248	}
249	if numCertPropsSet != 1 {
250		ctx.ModuleErrorf("One and only one of certficate, presigned, and default_dev_cert properties must be set")
251	}
252
253	_, certificates := collectAppDeps(ctx, a, false, false)
254
255	// TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK
256	// TODO: LOCAL_PACKAGE_SPLITS
257
258	srcApk := a.prebuilt.SingleSourcePath(ctx)
259
260	// TODO: Install or embed JNI libraries
261
262	// Uncompress JNI libraries in the apk
263	jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk")
264	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath)
265
266	var installDir android.InstallPath
267
268	if a.isPrebuiltFrameworkRes() {
269		// framework-res.apk is installed as system/framework/framework-res.apk
270		installDir = android.PathForModuleInstall(ctx, "framework")
271		a.preprocessed = true
272	} else if Bool(a.properties.Privileged) {
273		installDir = android.PathForModuleInstall(ctx, "priv-app", a.BaseModuleName())
274	} else if ctx.InstallInTestcases() {
275		installDir = android.PathForModuleInstall(ctx, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch())
276	} else {
277		installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName())
278	}
279
280	a.dexpreopter.isApp = true
281	a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk")
282	a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned)
283	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
284
285	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
286	a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
287
288	if a.usesLibrary.enforceUsesLibraries() {
289		srcApk = a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk)
290	}
291
292	a.dexpreopter.dexpreopt(ctx, jnisUncompressed)
293	if a.dexpreopter.uncompressedDex {
294		dexUncompressed := android.PathForModuleOut(ctx, "dex-uncompressed", ctx.ModuleName()+".apk")
295		a.uncompressDex(ctx, jnisUncompressed, dexUncompressed.OutputPath)
296		jnisUncompressed = dexUncompressed
297	}
298
299	apkFilename := proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+".apk")
300
301	// TODO: Handle EXTERNAL
302
303	// Sign or align the package if package has not been preprocessed
304
305	if a.isPrebuiltFrameworkRes() {
306		a.outputFile = srcApk
307		certificates = processMainCert(a.ModuleBase, String(a.properties.Certificate), certificates, ctx)
308		if len(certificates) != 1 {
309			ctx.ModuleErrorf("Unexpected number of certificates were extracted: %q", certificates)
310		}
311		a.certificate = certificates[0]
312	} else if a.preprocessed {
313		a.outputFile = srcApk
314		a.certificate = PresignedCertificate
315	} else if !Bool(a.properties.Presigned) {
316		// If the certificate property is empty at this point, default_dev_cert must be set to true.
317		// Which makes processMainCert's behavior for the empty cert string WAI.
318		certificates = processMainCert(a.ModuleBase, String(a.properties.Certificate), certificates, ctx)
319		a.certificate = certificates[0]
320		signed := android.PathForModuleOut(ctx, "signed", apkFilename)
321		var lineageFile android.Path
322		if lineage := String(a.properties.Lineage); lineage != "" {
323			lineageFile = android.PathForModuleSrc(ctx, lineage)
324		}
325		SignAppPackage(ctx, signed, jnisUncompressed, certificates, nil, lineageFile)
326		a.outputFile = signed
327	} else {
328		alignedApk := android.PathForModuleOut(ctx, "zip-aligned", apkFilename)
329		TransformZipAlign(ctx, alignedApk, jnisUncompressed)
330		a.outputFile = alignedApk
331		a.certificate = PresignedCertificate
332	}
333
334	// TODO: Optionally compress the output apk.
335
336	if apexInfo.IsForPlatform() {
337		a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile)
338	}
339
340	// TODO: androidmk converter jni libs
341}
342
343func (a *AndroidAppImport) Prebuilt() *android.Prebuilt {
344	return &a.prebuilt
345}
346
347func (a *AndroidAppImport) Name() string {
348	return a.prebuilt.Name(a.ModuleBase.Name())
349}
350
351func (a *AndroidAppImport) OutputFile() android.Path {
352	return a.outputFile
353}
354
355func (a *AndroidAppImport) JacocoReportClassesFile() android.Path {
356	return nil
357}
358
359func (a *AndroidAppImport) Certificate() Certificate {
360	return a.certificate
361}
362
363var dpiVariantGroupType reflect.Type
364var archVariantGroupType reflect.Type
365var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}
366
367func initAndroidAppImportVariantGroupTypes() {
368	dpiVariantGroupType = createVariantGroupType(supportedDpis, "Dpi_variants")
369
370	archNames := make([]string, len(android.ArchTypeList()))
371	for i, archType := range android.ArchTypeList() {
372		archNames[i] = archType.Name
373	}
374	archVariantGroupType = createVariantGroupType(archNames, "Arch")
375}
376
377// Populates all variant struct properties at creation time.
378func (a *AndroidAppImport) populateAllVariantStructs() {
379	a.dpiVariants = reflect.New(dpiVariantGroupType).Interface()
380	a.AddProperties(a.dpiVariants)
381
382	a.archVariants = reflect.New(archVariantGroupType).Interface()
383	a.AddProperties(a.archVariants)
384}
385
386func (a *AndroidAppImport) Privileged() bool {
387	return Bool(a.properties.Privileged)
388}
389
390func (a *AndroidAppImport) DepIsInSameApex(_ android.BaseModuleContext, _ android.Module) bool {
391	// android_app_import might have extra dependencies via uses_libs property.
392	// Don't track the dependency as we don't automatically add those libraries
393	// to the classpath. It should be explicitly added to java_libs property of APEX
394	return false
395}
396
397func (a *AndroidAppImport) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
398	return android.SdkSpecPrivate
399}
400
401func (a *AndroidAppImport) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
402	return android.SdkSpecPrivate
403}
404
405func (a *AndroidAppImport) LintDepSets() LintDepSets {
406	return LintDepSets{}
407}
408
409var _ android.ApexModule = (*AndroidAppImport)(nil)
410
411// Implements android.ApexModule
412func (j *AndroidAppImport) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
413	sdkVersion android.ApiLevel) error {
414	// Do not check for prebuilts against the min_sdk_version of enclosing APEX
415	return nil
416}
417
418func createVariantGroupType(variants []string, variantGroupName string) reflect.Type {
419	props := reflect.TypeOf((*AndroidAppImportProperties)(nil))
420
421	variantFields := make([]reflect.StructField, len(variants))
422	for i, variant := range variants {
423		variantFields[i] = reflect.StructField{
424			Name: proptools.FieldNameForProperty(variant),
425			Type: props,
426		}
427	}
428
429	variantGroupStruct := reflect.StructOf(variantFields)
430	return reflect.StructOf([]reflect.StructField{
431		{
432			Name: variantGroupName,
433			Type: variantGroupStruct,
434		},
435	})
436}
437
438// android_app_import imports a prebuilt apk with additional processing specified in the module.
439// DPI-specific apk source files can be specified using dpi_variants. Example:
440//
441//     android_app_import {
442//         name: "example_import",
443//         apk: "prebuilts/example.apk",
444//         dpi_variants: {
445//             mdpi: {
446//                 apk: "prebuilts/example_mdpi.apk",
447//             },
448//             xhdpi: {
449//                 apk: "prebuilts/example_xhdpi.apk",
450//             },
451//         },
452//         certificate: "PRESIGNED",
453//     }
454func AndroidAppImportFactory() android.Module {
455	module := &AndroidAppImport{}
456	module.AddProperties(&module.properties)
457	module.AddProperties(&module.dexpreoptProperties)
458	module.AddProperties(&module.usesLibrary.usesLibraryProperties)
459	module.populateAllVariantStructs()
460	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
461		module.processVariants(ctx)
462	})
463
464	android.InitApexModule(module)
465	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
466	android.InitDefaultableModule(module)
467	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
468
469	module.usesLibrary.enforce = true
470
471	return module
472}
473
474type androidTestImportProperties struct {
475	// Whether the prebuilt apk can be installed without additional processing. Default is false.
476	Preprocessed *bool
477}
478
479type AndroidTestImport struct {
480	AndroidAppImport
481
482	testProperties testProperties
483
484	testImportProperties androidTestImportProperties
485
486	data android.Paths
487}
488
489func (a *AndroidTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
490	a.preprocessed = Bool(a.testImportProperties.Preprocessed)
491
492	a.generateAndroidBuildActions(ctx)
493
494	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
495}
496
497func (a *AndroidTestImport) InstallInTestcases() bool {
498	return true
499}
500
501// android_test_import imports a prebuilt test apk with additional processing specified in the
502// module. DPI or arch variant configurations can be made as with android_app_import.
503func AndroidTestImportFactory() android.Module {
504	module := &AndroidTestImport{}
505	module.AddProperties(&module.properties)
506	module.AddProperties(&module.dexpreoptProperties)
507	module.AddProperties(&module.testProperties)
508	module.AddProperties(&module.testImportProperties)
509	module.populateAllVariantStructs()
510	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
511		module.processVariants(ctx)
512	})
513
514	module.dexpreopter.isTest = true
515
516	android.InitApexModule(module)
517	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
518	android.InitDefaultableModule(module)
519	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
520
521	return module
522}
523