Apicheck Support in Soong

didn't remove the props: api_filename, removed_api_filename yet since
these two currently are used by java_sdk_library.

Bug: b/78034256
Test: m clean && m checkapi

Change-Id: Iebd014ef227487717b5b3819c80d630c34559983
diff --git a/java/androidmk.go b/java/androidmk.go
index b168f2c..5a4a082 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -286,26 +286,52 @@
 				if ddoc.Javadoc.stubsSrcJar != nil {
 					fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", ddoc.Javadoc.stubsSrcJar.String())
 				}
+				if ddoc.checkCurrentApiTimestamp != nil {
+					fmt.Fprintln(w, ".PHONY:", ddoc.Name()+"-check-current-api")
+					fmt.Fprintln(w, ddoc.Name()+"-check-current-api:",
+						ddoc.checkCurrentApiTimestamp.String())
+
+					fmt.Fprintln(w, ".PHONY: checkapi")
+					fmt.Fprintln(w, "check-api:",
+						ddoc.checkCurrentApiTimestamp.String())
+
+					fmt.Fprintln(w, ".PHONY: droidcore")
+					fmt.Fprintln(w, "droidcore: checkapi")
+				}
+				if ddoc.updateCurrentApiTimestamp != nil {
+					fmt.Fprintln(w, ".PHONY:", ddoc.Name(), "-update-current-api")
+					fmt.Fprintln(w, ddoc.Name()+"-update-current-api:",
+						ddoc.updateCurrentApiTimestamp.String())
+
+					fmt.Fprintln(w, ".PHONY: update-api")
+					fmt.Fprintln(w, "update-api:",
+						ddoc.updateCurrentApiTimestamp.String())
+				}
+				if ddoc.checkLastReleasedApiTimestamp != nil {
+					fmt.Fprintln(w, ".PHONY:", ddoc.Name()+"-check-last-released-api")
+					fmt.Fprintln(w, ddoc.Name()+"-check-last-released-api:",
+						ddoc.checkLastReleasedApiTimestamp.String())
+				}
 				apiFilePrefix := "INTERNAL_PLATFORM_"
 				if String(ddoc.properties.Api_tag_name) != "" {
 					apiFilePrefix += String(ddoc.properties.Api_tag_name) + "_"
 				}
-				if String(ddoc.properties.Api_filename) != "" {
+				if ddoc.apiFile != nil {
 					fmt.Fprintln(w, apiFilePrefix+"API_FILE := ", ddoc.apiFile.String())
 				}
-				if String(ddoc.properties.Private_api_filename) != "" {
+				if ddoc.privateApiFile != nil {
 					fmt.Fprintln(w, apiFilePrefix+"PRIVATE_API_FILE := ", ddoc.privateApiFile.String())
 				}
-				if String(ddoc.properties.Private_dex_api_filename) != "" {
+				if ddoc.privateDexApiFile != nil {
 					fmt.Fprintln(w, apiFilePrefix+"PRIVATE_DEX_API_FILE := ", ddoc.privateDexApiFile.String())
 				}
-				if String(ddoc.properties.Removed_api_filename) != "" {
+				if ddoc.removedApiFile != nil {
 					fmt.Fprintln(w, apiFilePrefix+"REMOVED_API_FILE := ", ddoc.removedApiFile.String())
 				}
-				if String(ddoc.properties.Removed_dex_api_filename) != "" {
+				if ddoc.removedDexApiFile != nil {
 					fmt.Fprintln(w, apiFilePrefix+"REMOVED_DEX_API_FILE := ", ddoc.removedDexApiFile.String())
 				}
-				if String(ddoc.properties.Exact_api_filename) != "" {
+				if ddoc.exactApiFile != nil {
 					fmt.Fprintln(w, apiFilePrefix+"EXACT_API_FILE := ", ddoc.exactApiFile.String())
 				}
 			},
diff --git a/java/config/config.go b/java/config/config.go
index 6633f79..d44315c 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -90,6 +90,7 @@
 	pctx.HostBinToolVariable("MergeZipsCmd", "merge_zips")
 	pctx.HostBinToolVariable("Zip2ZipCmd", "zip2zip")
 	pctx.HostBinToolVariable("ZipSyncCmd", "zipsync")
+	pctx.HostBinToolVariable("ApiCheckCmd", "apicheck")
 	pctx.VariableFunc("DxCmd", func(ctx android.PackageVarContext) string {
 		config := ctx.Config()
 		if config.IsEnvFalse("USE_D8") {
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 07042a1..202f22b 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -45,6 +45,24 @@
 		},
 		"outDir", "srcJarDir", "stubsDir", "srcJars", "opts",
 		"bootclasspathArgs", "classpathArgs", "sourcepath", "docZip")
+
+	apiCheck = pctx.AndroidStaticRule("apiCheck",
+		blueprint.RuleParams{
+			Command: `( ${config.ApiCheckCmd} -JXmx1024m -J"classpath $classpath" $opts ` +
+				`$apiFile $apiFileToCheck $removedApiFile $removedApiFileToCheck ` +
+				`&& touch $out ) || (echo $msg ; exit 38)`,
+			CommandDeps: []string{
+				"${config.ApiCheckCmd}",
+			},
+		},
+		"classpath", "opts", "apiFile", "apiFileToCheck", "removedApiFile", "removedApiFileToCheck", "msg")
+
+	updateApi = pctx.AndroidStaticRule("updateApi",
+		blueprint.RuleParams{
+			Command: `( ( cp -f $apiFileToCheck $apiFile && cp -f $removedApiFileToCheck $removedApiFile ) ` +
+				`&& touch $out ) || (echo failed to update public API ; exit 38)`,
+		},
+		"apiFile", "apiFileToCheck", "removedApiFile", "removedApiFileToCheck")
 )
 
 func init() {
@@ -94,6 +112,14 @@
 	Sdk_version *string `android:"arch_variant"`
 }
 
+type ApiToCheck struct {
+	Api_file *string
+
+	Removed_api_file *string
+
+	Args *string
+}
+
 type DroiddocProperties struct {
 	// directory relative to top of the source tree that contains doc templates files.
 	Custom_template *string
@@ -157,6 +183,12 @@
 
 	// if set to false, don't allow droiddoc to generate stubs source files. Defaults to true.
 	Create_stubs *bool
+
+	Check_api struct {
+		Last_released ApiToCheck
+
+		Current ApiToCheck
+	}
 }
 
 type Javadoc struct {
@@ -189,6 +221,10 @@
 	removedApiFile    android.WritablePath
 	removedDexApiFile android.WritablePath
 	exactApiFile      android.WritablePath
+
+	checkCurrentApiTimestamp      android.WritablePath
+	updateCurrentApiTimestamp     android.WritablePath
+	checkLastReleasedApiTimestamp android.WritablePath
 }
 
 func InitDroiddocModule(module android.DefaultableModule, hod android.HostOrDeviceSupported) {
@@ -420,6 +456,32 @@
 	})
 }
 
+func (d *Droiddoc) checkCurrentApi() bool {
+	if String(d.properties.Check_api.Current.Api_file) != "" &&
+		String(d.properties.Check_api.Current.Removed_api_file) != "" {
+		return true
+	} else if String(d.properties.Check_api.Current.Api_file) != "" {
+		panic("check_api.current.removed_api_file: has to be non empty!")
+	} else if String(d.properties.Check_api.Current.Removed_api_file) != "" {
+		panic("check_api.current.api_file: has to be non empty!")
+	}
+
+	return false
+}
+
+func (d *Droiddoc) checkLastReleasedApi() bool {
+	if String(d.properties.Check_api.Last_released.Api_file) != "" &&
+		String(d.properties.Check_api.Last_released.Removed_api_file) != "" {
+		return true
+	} else if String(d.properties.Check_api.Last_released.Api_file) != "" {
+		panic("check_api.last_released.removed_api_file: has to be non empty!")
+	} else if String(d.properties.Check_api.Last_released.Removed_api_file) != "" {
+		panic("check_api.last_released.api_file: has to be non empty!")
+	}
+
+	return false
+}
+
 func (d *Droiddoc) DepsMutator(ctx android.BottomUpMutatorContext) {
 	d.Javadoc.addDeps(ctx)
 
@@ -435,6 +497,16 @@
 
 	// knowntags may contain filegroup or genrule.
 	android.ExtractSourcesDeps(ctx, d.properties.Knowntags)
+
+	if d.checkCurrentApi() {
+		android.ExtractSourceDeps(ctx, d.properties.Check_api.Current.Api_file)
+		android.ExtractSourceDeps(ctx, d.properties.Check_api.Current.Removed_api_file)
+	}
+
+	if d.checkLastReleasedApi() {
+		android.ExtractSourceDeps(ctx, d.properties.Check_api.Last_released.Api_file)
+		android.ExtractSourceDeps(ctx, d.properties.Check_api.Last_released.Removed_api_file)
+	}
 }
 
 func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -547,12 +619,19 @@
 	}
 
 	var implicitOutputs android.WritablePaths
-	if String(d.properties.Api_filename) != "" {
-		d.apiFile = android.PathForModuleOut(ctx, String(d.properties.Api_filename))
+
+	if d.checkCurrentApi() || d.checkLastReleasedApi() || String(d.properties.Api_filename) != "" {
+		d.apiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.txt")
 		args = args + " -api " + d.apiFile.String()
 		implicitOutputs = append(implicitOutputs, d.apiFile)
 	}
 
+	if d.checkCurrentApi() || d.checkLastReleasedApi() || String(d.properties.Removed_api_filename) != "" {
+		d.removedApiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_removed.txt")
+		args = args + " -removedApi " + d.removedApiFile.String()
+		implicitOutputs = append(implicitOutputs, d.removedApiFile)
+	}
+
 	if String(d.properties.Private_api_filename) != "" {
 		d.privateApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_api_filename))
 		args = args + " -privateApi " + d.privateApiFile.String()
@@ -565,12 +644,6 @@
 		implicitOutputs = append(implicitOutputs, d.privateDexApiFile)
 	}
 
-	if String(d.properties.Removed_api_filename) != "" {
-		d.removedApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_api_filename))
-		args = args + " -removedApi " + d.removedApiFile.String()
-		implicitOutputs = append(implicitOutputs, d.removedApiFile)
-	}
-
 	if String(d.properties.Removed_dex_api_filename) != "" {
 		d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename))
 		args = args + " -removedDexApi " + d.removedDexApiFile.String()
@@ -624,6 +697,91 @@
 			"docZip":            d.Javadoc.docZip.String(),
 		},
 	})
+
+	java8Home := ctx.Config().Getenv("ANDROID_JAVA8_HOME")
+
+	checkApiClasspath := classpath{jsilver, doclava, android.PathForSource(ctx, java8Home, "lib/tools.jar")}
+
+	if d.checkCurrentApi() && !ctx.Config().IsPdkBuild() {
+		d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp")
+
+		apiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Api_file),
+			"check_api.current.api_file")
+		removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Removed_api_file),
+			"check_api.current_removed_api_file")
+
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        apiCheck,
+			Description: "Current API check",
+			Output:      d.checkCurrentApiTimestamp,
+			Inputs:      nil,
+			Implicits: append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile},
+				checkApiClasspath...),
+			Args: map[string]string{
+				"classpath":             checkApiClasspath.FormJavaClassPath(""),
+				"opts":                  String(d.properties.Check_api.Current.Args),
+				"apiFile":               apiFile.String(),
+				"apiFileToCheck":        d.apiFile.String(),
+				"removedApiFile":        removedApiFile.String(),
+				"removedApiFileToCheck": d.removedApiFile.String(),
+				"msg": fmt.Sprintf(`\n******************************\n`+
+					`You have tried to change the API from what has been previously approved.\n\n`+
+					`To make these errors go away, you have two choices:\n`+
+					`   1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+
+					`      errors above.\n\n`+
+					`   2. You can update current.txt by executing the following command:`+
+					`         make %s-update-current-api\n\n`+
+					`      To submit the revised current.txt to the main Android repository,`+
+					`      you will need approval.\n`+
+					`******************************\n`, ctx.ModuleName()),
+			},
+		})
+
+		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp")
+
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        updateApi,
+			Description: "update current API",
+			Output:      d.updateCurrentApiTimestamp,
+			Implicits:   append(android.Paths{}, apiFile, removedApiFile, d.apiFile, d.removedApiFile),
+			Args: map[string]string{
+				"apiFile":               apiFile.String(),
+				"apiFileToCheck":        d.apiFile.String(),
+				"removedApiFile":        removedApiFile.String(),
+				"removedApiFileToCheck": d.removedApiFile.String(),
+			},
+		})
+	}
+
+	if d.checkLastReleasedApi() && !ctx.Config().IsPdkBuild() {
+		d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp")
+
+		apiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file),
+			"check_api.last_released.api_file")
+		removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Removed_api_file),
+			"check_api.last_released.removed_api_file")
+
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        apiCheck,
+			Description: "Last Released API check",
+			Output:      d.checkLastReleasedApiTimestamp,
+			Inputs:      nil,
+			Implicits: append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile},
+				checkApiClasspath...),
+			Args: map[string]string{
+				"classpath":             checkApiClasspath.FormJavaClassPath(""),
+				"opts":                  String(d.properties.Check_api.Last_released.Args),
+				"apiFile":               apiFile.String(),
+				"apiFileToCheck":        d.apiFile.String(),
+				"removedApiFile":        removedApiFile.String(),
+				"removedApiFileToCheck": d.removedApiFile.String(),
+				"msg": `\n******************************\n` +
+					`You have tried to change the API from what has been previously released in\n` +
+					`an SDK.  Please fix the errors listed above.\n` +
+					`******************************\n`,
+			},
+		})
+	}
 }
 
 var droiddocTemplateTag = dependencyTag{name: "droiddoc-template"}
diff --git a/java/java_test.go b/java/java_test.go
index ea52496..4a0229e 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -175,6 +175,7 @@
 
 		"jdk8/jre/lib/jce.jar": nil,
 		"jdk8/jre/lib/rt.jar":  nil,
+		"jdk8/lib/tools.jar":   nil,
 
 		"bar-doc/a.java":                 nil,
 		"bar-doc/b.java":                 nil,