// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package selinux

import (
	"fmt"
	"io"

	"github.com/google/blueprint"
	"github.com/google/blueprint/proptools"

	"android/soong/android"
	"android/soong/sysprop"
)

type selinuxContextsProperties struct {
	// Filenames under sepolicy directories, which will be used to generate contexts file.
	Srcs []string `android:"path"`

	// Output file name. Defaults to module name
	Stem *string

	Product_variables struct {
		Address_sanitize struct {
			Srcs []string `android:"path"`
		}
	}

	// Whether the comments in generated contexts file will be removed or not.
	Remove_comment *bool

	// Whether the result context file is sorted with fc_sort or not.
	Fc_sort *bool

	// Make this module available when building for recovery
	Recovery_available *bool

	// Board api level of policy files. Set "current" for RELEASE_BOARD_API_LEVEL, or a direct
	// version string (e.g. "202404"). Defaults to "current"
	Board_api_level *string
}

type seappProperties struct {
	// Files containing neverallow rules.
	Neverallow_files []string `android:"path"`

	// Precompiled sepolicy binary file which will be fed to checkseapp.
	Sepolicy *string `android:"path"`
}

type selinuxContextsModule struct {
	android.ModuleBase
	android.DefaultableModuleBase
	flaggableModuleBase

	properties      selinuxContextsProperties
	seappProperties seappProperties
	build           func(ctx android.ModuleContext, inputs android.Paths) android.Path
	deps            func(ctx android.BottomUpMutatorContext)
	outputPath      android.Path
	installPath     android.InstallPath
}

var _ flaggableModule = (*selinuxContextsModule)(nil)

var (
	reuseContextsDepTag  = dependencyTag{name: "reuseContexts"}
	syspropLibraryDepTag = dependencyTag{name: "sysprop_library"}
)

func init() {
	pctx.HostBinToolVariable("fc_sort", "fc_sort")

	android.RegisterModuleType("contexts_defaults", contextsDefaultsFactory)
	android.RegisterModuleType("file_contexts", fileFactory)
	android.RegisterModuleType("hwservice_contexts", hwServiceFactory)
	android.RegisterModuleType("property_contexts", propertyFactory)
	android.RegisterModuleType("service_contexts", serviceFactory)
	android.RegisterModuleType("keystore2_key_contexts", keystoreKeyFactory)
	android.RegisterModuleType("seapp_contexts", seappFactory)
	android.RegisterModuleType("vndservice_contexts", vndServiceFactory)
	android.RegisterModuleType("tee_service_contexts", teeServiceFactory)

	android.RegisterModuleType("file_contexts_test", fileContextsTestFactory)
	android.RegisterModuleType("property_contexts_test", propertyContextsTestFactory)
	android.RegisterModuleType("hwservice_contexts_test", hwserviceContextsTestFactory)
	android.RegisterModuleType("service_contexts_test", serviceContextsTestFactory)
	android.RegisterModuleType("vndservice_contexts_test", vndServiceContextsTestFactory)
}

func (m *selinuxContextsModule) InstallInRoot() bool {
	return m.InRecovery()
}

func (m *selinuxContextsModule) InstallInRecovery() bool {
	// ModuleBase.InRecovery() checks the image variant
	return m.InRecovery()
}

func (m *selinuxContextsModule) onlyInRecovery() bool {
	// ModuleBase.InstallInRecovery() checks commonProperties.Recovery property
	return m.ModuleBase.InstallInRecovery()
}

func (m *selinuxContextsModule) DepsMutator(ctx android.BottomUpMutatorContext) {
	m.flagDeps(ctx)

	if m.deps != nil {
		m.deps(ctx)
	}

	if m.InRecovery() && !m.onlyInRecovery() {
		ctx.AddFarVariationDependencies([]blueprint.Variation{
			{Mutator: "image", Variation: android.CoreVariation},
		}, reuseContextsDepTag, ctx.ModuleName())
	}
}

func (m *selinuxContextsModule) propertyContextsDeps(ctx android.BottomUpMutatorContext) {
	for _, lib := range sysprop.SyspropLibraries(ctx.Config()) {
		ctx.AddFarVariationDependencies([]blueprint.Variation{}, syspropLibraryDepTag, lib)
	}
}

func (m *selinuxContextsModule) stem() string {
	return proptools.StringDefault(m.properties.Stem, m.Name())
}

func (m *selinuxContextsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	if m.InRecovery() {
		// Installing context files at the root of the recovery partition
		m.installPath = android.PathForModuleInstall(ctx)
	} else {
		m.installPath = android.PathForModuleInstall(ctx, "etc", "selinux")
	}

	if m.InRecovery() && !m.onlyInRecovery() {
		dep := ctx.GetDirectDepProxyWithTag(m.Name(), reuseContextsDepTag)
		m.outputPath = android.OutputFileForModule(ctx, dep, "")
		ctx.InstallFile(m.installPath, m.stem(), m.outputPath)
		return
	}

	builtContext := m.build(ctx, android.PathsForModuleSrc(ctx, m.properties.Srcs))

	outputPath := pathForModuleOut(ctx, m.stem())
	android.CopyFileRule(ctx, builtContext, outputPath)
	ctx.InstallFile(m.installPath, m.stem(), outputPath)
	m.outputPath = outputPath

	ctx.SetOutputFiles([]android.Path{m.outputPath}, "")
}

func newModule() *selinuxContextsModule {
	m := &selinuxContextsModule{}
	m.AddProperties(
		&m.properties,
		&m.seappProperties,
	)
	initFlaggableModule(m)
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	android.InitDefaultableModule(m)
	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
		m.selinuxContextsHook(ctx)
	})
	return m
}

type contextsDefaults struct {
	android.ModuleBase
	android.DefaultsModuleBase
}

// contexts_defaults provides a set of properties that can be inherited by other contexts modules.
// (file_contexts, property_contexts, seapp_contexts, etc.) A module can use the properties from a
// contexts_defaults using `defaults: ["<:default_module_name>"]`. Properties of both modules are
// erged (when possible) by prepending the default module's values to the depending module's values.
func contextsDefaultsFactory() android.Module {
	m := &contextsDefaults{}
	m.AddProperties(
		&selinuxContextsProperties{},
		&seappProperties{},
		&flaggableModuleProperties{},
	)
	android.InitDefaultsModule(m)
	return m
}

func (m *selinuxContextsModule) selinuxContextsHook(ctx android.LoadHookContext) {
	// TODO: clean this up to use build/soong/android/variable.go after b/79249983
	var srcs []string

	for _, sanitize := range ctx.Config().SanitizeDevice() {
		if sanitize == "address" {
			srcs = append(srcs, m.properties.Product_variables.Address_sanitize.Srcs...)
			break
		}
	}

	m.properties.Srcs = append(m.properties.Srcs, srcs...)
}

func (m *selinuxContextsModule) AndroidMk() android.AndroidMkData {
	nameSuffix := ""
	if m.InRecovery() && !m.onlyInRecovery() {
		nameSuffix = ".recovery"
	}
	return android.AndroidMkData{
		Class:      "ETC",
		OutputFile: android.OptionalPathForPath(m.outputPath),
		SubName:    nameSuffix,
		Extra: []android.AndroidMkExtraFunc{
			func(w io.Writer, outputFile android.Path) {
				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", m.installPath.String())
				fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", m.stem())
			},
		},
	}
}

func (m *selinuxContextsModule) ImageMutatorBegin(ctx android.ImageInterfaceContext) {
	if proptools.Bool(m.properties.Recovery_available) && m.ModuleBase.InstallInRecovery() {
		ctx.PropertyErrorf("recovery_available",
			"doesn't make sense at the same time as `recovery: true`")
	}
}

func (m *selinuxContextsModule) ImageMutatorSupported() bool { return true }

func (m *selinuxContextsModule) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *selinuxContextsModule) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *selinuxContextsModule) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return !m.ModuleBase.InstallInRecovery()
}

func (m *selinuxContextsModule) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *selinuxContextsModule) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *selinuxContextsModule) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *selinuxContextsModule) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return m.ModuleBase.InstallInRecovery() || proptools.Bool(m.properties.Recovery_available)
}

func (m *selinuxContextsModule) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
	return nil
}

func (m *selinuxContextsModule) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
}

var _ android.ImageInterface = (*selinuxContextsModule)(nil)

func (m *selinuxContextsModule) buildGeneralContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
	builtContext := pathForModuleOut(ctx, ctx.ModuleName()+"_m4out")

	rule := android.NewRuleBuilder(pctx, ctx)

	newlineFile := pathForModuleOut(ctx, "newline")

	rule.Command().Text("echo").FlagWithOutput("> ", newlineFile)
	rule.Temporary(newlineFile)

	var inputsWithNewline android.Paths
	for _, input := range inputs {
		inputsWithNewline = append(inputsWithNewline, input, newlineFile)
	}

	flags := m.getBuildFlags(ctx)
	rule.Command().
		Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
		Text("--fatal-warnings -s").
		FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
		Flag(boardApiLevelToM4Macro(ctx, m.properties.Board_api_level)).
		Flags(flagsToM4Macros(flags)).
		Inputs(inputsWithNewline).
		FlagWithOutput("> ", builtContext)

	if proptools.Bool(m.properties.Remove_comment) {
		rule.Temporary(builtContext)

		remove_comment_output := pathForModuleOut(ctx, ctx.ModuleName()+"_remove_comment")

		rule.Command().
			Text("sed -e 's/#.*$//' -e '/^$/d'").
			Input(builtContext).
			FlagWithOutput("> ", remove_comment_output)

		builtContext = remove_comment_output
	}

	if proptools.Bool(m.properties.Fc_sort) {
		rule.Temporary(builtContext)

		sorted_output := pathForModuleOut(ctx, ctx.ModuleName()+"_sorted")

		rule.Command().
			Tool(ctx.Config().HostToolPath(ctx, "fc_sort")).
			FlagWithInput("-i ", builtContext).
			FlagWithOutput("-o ", sorted_output)

		builtContext = sorted_output
	}

	rule.DeleteTemporaryFiles()
	rule.Build("selinux_contexts", "building contexts: "+m.Name())

	return builtContext
}

func (m *selinuxContextsModule) buildFileContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
	if m.properties.Remove_comment == nil {
		m.properties.Remove_comment = proptools.BoolPtr(true)
	}
	return m.buildGeneralContexts(ctx, inputs)
}

func fileFactory() android.Module {
	m := newModule()
	m.build = m.buildFileContexts
	return m
}

func (m *selinuxContextsModule) buildServiceContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
	if m.properties.Remove_comment == nil {
		m.properties.Remove_comment = proptools.BoolPtr(true)
	}

	return m.buildGeneralContexts(ctx, inputs)
}

func (m *selinuxContextsModule) checkVendorPropertyNamespace(ctx android.ModuleContext, input android.Path) android.Path {
	shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
	ApiLevelR := android.ApiLevelOrPanic(ctx, "R")

	rule := android.NewRuleBuilder(pctx, ctx)

	// This list is from vts_treble_sys_prop_test.
	allowedPropertyPrefixes := []string{
		"ctl.odm.",
		"ctl.vendor.",
		"ctl.start$odm.",
		"ctl.start$vendor.",
		"ctl.stop$odm.",
		"ctl.stop$vendor.",
		"init.svc.odm.",
		"init.svc.vendor.",
		"ro.boot.",
		"ro.hardware.",
		"ro.odm.",
		"ro.vendor.",
		"odm.",
		"persist.odm.",
		"persist.vendor.",
		"vendor.",
	}

	// persist.camera is also allowed for devices launching with R or eariler
	if shippingApiLevel.LessThanOrEqualTo(ApiLevelR) {
		allowedPropertyPrefixes = append(allowedPropertyPrefixes, "persist.camera.")
	}

	var allowedContextPrefixes []string

	if shippingApiLevel.GreaterThanOrEqualTo(ApiLevelR) {
		// This list is from vts_treble_sys_prop_test.
		allowedContextPrefixes = []string{
			"vendor_",
			"odm_",
		}
	}

	cmd := rule.Command().
		BuiltTool("check_prop_prefix").
		FlagWithInput("--property-contexts ", input).
		FlagForEachArg("--allowed-property-prefix ", proptools.ShellEscapeList(allowedPropertyPrefixes)). // contains shell special character '$'
		FlagForEachArg("--allowed-context-prefix ", allowedContextPrefixes)

	if !ctx.DeviceConfig().BuildBrokenVendorPropertyNamespace() {
		cmd.Flag("--strict")
	}

	out := pathForModuleOut(ctx, ctx.ModuleName()+"_namespace_checked")
	rule.Command().Text("cp -f").Input(input).Output(out)
	rule.Build("check_namespace", "checking namespace of "+ctx.ModuleName())
	return out
}

func (m *selinuxContextsModule) buildPropertyContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
	// vendor/odm properties are enforced for devices launching with Android Q or later. So, if
	// vendor/odm, make sure that only vendor/odm properties exist.
	builtCtxFile := m.buildGeneralContexts(ctx, inputs)

	shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
	ApiLevelQ := android.ApiLevelOrPanic(ctx, "Q")
	if (ctx.SocSpecific() || ctx.DeviceSpecific()) && shippingApiLevel.GreaterThanOrEqualTo(ApiLevelQ) {
		builtCtxFile = m.checkVendorPropertyNamespace(ctx, builtCtxFile)
	}

	var apiFiles android.Paths
	ctx.VisitDirectDepsProxyWithTag(syspropLibraryDepTag, func(c android.ModuleProxy) {
		info, ok := android.OtherModuleProvider(ctx, c, sysprop.SyspropLibraryInfoProvider)
		if !ok {
			panic(fmt.Errorf("unknown dependency %q for %q", ctx.OtherModuleName(c), ctx.ModuleName()))
		}
		if api := info.CurrentApiFile; api.Valid() {
			apiFiles = append(apiFiles, api.Path())
		}
	})

	// check compatibility with sysprop_library
	if len(apiFiles) > 0 {
		out := pathForModuleOut(ctx, ctx.ModuleName()+"_api_checked")
		rule := android.NewRuleBuilder(pctx, ctx)

		msg := `\n******************************\n` +
			`API of sysprop_library doesn't match with property_contexts\n` +
			`Please fix the breakage and rebuild.\n` +
			`******************************\n`

		rule.Command().
			Text("( ").
			BuiltTool("sysprop_type_checker").
			FlagForEachInput("--api ", apiFiles).
			FlagWithInput("--context ", builtCtxFile).
			Text(" || ( echo").Flag("-e").
			Flag(`"` + msg + `"`).
			Text("; exit 38) )")

		rule.Command().Text("cp -f").Input(builtCtxFile).Output(out)
		rule.Build("property_contexts_check_api", "checking API: "+m.Name())
		builtCtxFile = out
	}

	return builtCtxFile
}

func (m *selinuxContextsModule) shouldCheckCoredomain(ctx android.ModuleContext) bool {
	if !ctx.SocSpecific() && !ctx.DeviceSpecific() {
		return false
	}

	return ctx.DeviceConfig().CheckVendorSeappViolations()
}

func (m *selinuxContextsModule) buildSeappContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
	neverallowFile := pathForModuleOut(ctx, "neverallow")
	ret := pathForModuleOut(ctx, "checkseapp", m.stem())

	// Step 1. Generate a M4 processed neverallow file
	flags := m.getBuildFlags(ctx)
	m4NeverallowFile := pathForModuleOut(ctx, "neverallow.m4out")
	rule := android.NewRuleBuilder(pctx, ctx)
	rule.Command().
		Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
		Flag("--fatal-warnings").
		FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
		Flags(flagsToM4Macros(flags)).
		Inputs(android.PathsForModuleSrc(ctx, m.seappProperties.Neverallow_files)).
		FlagWithOutput("> ", m4NeverallowFile)

	rule.Temporary(m4NeverallowFile)
	rule.Command().
		Text("( grep").
		Flag("-ihe").
		Text("'^neverallow'").
		Input(m4NeverallowFile).
		Text(">").
		Output(neverallowFile).
		Text("|| true )") // to make ninja happy even when result is empty

	// Step 2. Generate a M4 processed contexts file
	builtCtx := m.buildGeneralContexts(ctx, inputs)

	// Step 3. checkseapp
	rule.Temporary(neverallowFile)
	checkCmd := rule.Command().BuiltTool("checkseapp").
		FlagWithInput("-p ", android.PathForModuleSrc(ctx, proptools.String(m.seappProperties.Sepolicy))).
		FlagWithOutput("-o ", ret).
		Input(builtCtx).
		Input(neverallowFile)

	if m.shouldCheckCoredomain(ctx) {
		checkCmd.Flag("-c") // check coredomain for vendor contexts
	}

	rule.Build("seapp_contexts", "Building seapp_contexts: "+m.Name())
	return ret
}

func hwServiceFactory() android.Module {
	m := newModule()
	m.build = m.buildServiceContexts
	return m
}

func propertyFactory() android.Module {
	m := newModule()
	m.build = m.buildPropertyContexts
	m.deps = m.propertyContextsDeps
	return m
}

func serviceFactory() android.Module {
	m := newModule()
	m.build = m.buildServiceContexts
	return m
}

func keystoreKeyFactory() android.Module {
	m := newModule()
	m.build = m.buildGeneralContexts
	return m
}

func teeServiceFactory() android.Module {
	m := newModule()
	m.build = m.buildGeneralContexts
	return m
}

func seappFactory() android.Module {
	m := newModule()
	m.build = m.buildSeappContexts
	return m
}

func vndServiceFactory() android.Module {
	m := newModule()
	m.build = m.buildGeneralContexts
	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
		if !ctx.SocSpecific() {
			ctx.ModuleErrorf(m.Name(), "must set vendor: true")
			return
		}
	})
	return m
}

type contextsTestProperties struct {
	// Contexts files to be tested.
	Srcs []string `android:"path"`

	// Precompiled sepolicy binary to be tesed together.
	Sepolicy *string `android:"path"`
}

type fileContextsTestProperties struct {
	// Test data. File passed to `checkfc -t` to validate how contexts are resolved.
	Test_data *string `android:"path"`
}

type contextsTestModule struct {
	android.ModuleBase

	// The type of context.
	context contextType

	properties     contextsTestProperties
	fileProperties fileContextsTestProperties
	testTimestamp  android.OutputPath
}

type contextType int

const (
	FileContext contextType = iota
	PropertyContext
	ServiceContext
	HwServiceContext
	VndServiceContext
)

// checkfc parses a context file and checks for syntax errors.
// If -s is specified, the service backend is used to verify binder services.
// If -l is specified, the service backend is used to verify hwbinder services.
// Otherwise, context_file is assumed to be a file_contexts file
// If -e is specified, then the context_file is allowed to be empty.

// file_contexts_test tests given file_contexts files with checkfc.
func fileContextsTestFactory() android.Module {
	m := &contextsTestModule{context: FileContext}
	m.AddProperties(&m.properties)
	m.AddProperties(&m.fileProperties)
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

// property_contexts_test tests given property_contexts files with property_info_checker.
func propertyContextsTestFactory() android.Module {
	m := &contextsTestModule{context: PropertyContext}
	m.AddProperties(&m.properties)
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

// hwservice_contexts_test tests given hwservice_contexts files with checkfc.
func hwserviceContextsTestFactory() android.Module {
	m := &contextsTestModule{context: HwServiceContext}
	m.AddProperties(&m.properties)
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

// service_contexts_test tests given service_contexts files with checkfc.
func serviceContextsTestFactory() android.Module {
	// checkfc -s: service_contexts test
	m := &contextsTestModule{context: ServiceContext}
	m.AddProperties(&m.properties)
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

// vndservice_contexts_test tests given vndservice_contexts files with checkfc.
func vndServiceContextsTestFactory() android.Module {
	m := &contextsTestModule{context: VndServiceContext}
	m.AddProperties(&m.properties)
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

func (m *contextsTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	tool := "checkfc"
	if m.context == PropertyContext {
		tool = "property_info_checker"
	}

	if len(m.properties.Srcs) == 0 {
		ctx.PropertyErrorf("srcs", "can't be empty")
		return
	}

	validateWithPolicy := true
	if proptools.String(m.properties.Sepolicy) == "" {
		if m.context == FileContext {
			if proptools.String(m.fileProperties.Test_data) == "" {
				ctx.PropertyErrorf("test_data", "Either test_data or sepolicy should be provided")
				return
			}
			validateWithPolicy = false
		} else {
			ctx.PropertyErrorf("sepolicy", "can't be empty")
			return
		}
	}

	flags := []string(nil)
	switch m.context {
	case FileContext:
		if !validateWithPolicy {
			flags = []string{"-t"}
		}
	case ServiceContext:
		flags = []string{"-s" /* binder services */}
	case HwServiceContext:
		flags = []string{"-e" /* allow empty */, "-l" /* hwbinder services */}
	case VndServiceContext:
		flags = []string{"-e" /* allow empty */, "-v" /* vnd service */}
	}

	srcs := android.PathsForModuleSrc(ctx, m.properties.Srcs)
	rule := android.NewRuleBuilder(pctx, ctx)

	if validateWithPolicy {
		sepolicy := android.PathForModuleSrc(ctx, proptools.String(m.properties.Sepolicy))
		rule.Command().BuiltTool(tool).
			Flags(flags).
			Input(sepolicy).
			Inputs(srcs)
	} else {
		test_data := android.PathForModuleSrc(ctx, proptools.String(m.fileProperties.Test_data))
		rule.Command().BuiltTool(tool).
			Flags(flags).
			Inputs(srcs).
			Input(test_data)
	}

	m.testTimestamp = pathForModuleOut(ctx, "timestamp")
	rule.Command().Text("touch").Output(m.testTimestamp)
	rule.Build("contexts_test", "running contexts test: "+ctx.ModuleName())

	ctx.CheckbuildFile(m.testTimestamp)

	moduleInfoJSON := ctx.ModuleInfoJSON()
	moduleInfoJSON.Class = []string{"FAKE"}
	moduleInfoJSON.SystemSharedLibs = []string{"none"}
}

func (m *contextsTestModule) AndroidMkEntries() []android.AndroidMkEntries {
	return []android.AndroidMkEntries{android.AndroidMkEntries{
		Class: "FAKE",
		// OutputFile is needed, even though BUILD_PHONY_PACKAGE doesn't use it.
		// Without OutputFile this module won't be exported to Makefile.
		OutputFile: android.OptionalPathForPath(m.testTimestamp),
		Include:    "$(BUILD_PHONY_PACKAGE)",
		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
				entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", m.testTimestamp.String())
			},
		},
	}}
}

// contextsTestModule implements ImageInterface to be able to include recovery_available contexts
// modules as its sources.
func (m *contextsTestModule) ImageMutatorBegin(ctx android.ImageInterfaceContext) {
}

func (m *contextsTestModule) ImageMutatorSupported() bool { return true }

func (m *contextsTestModule) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *contextsTestModule) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *contextsTestModule) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return true
}

func (m *contextsTestModule) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *contextsTestModule) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *contextsTestModule) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *contextsTestModule) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
	return false
}

func (m *contextsTestModule) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
	return nil
}

func (m *contextsTestModule) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
}

var _ android.ImageInterface = (*contextsTestModule)(nil)
