summaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/Android.bp50
-rw-r--r--api/StubLibraries.bp382
-rw-r--r--api/api.go21
-rw-r--r--api/coverage/tools/Android.bp21
-rw-r--r--api/coverage/tools/ExtractFlaggedApis.kt101
-rw-r--r--api/coverage/tools/ExtractFlaggedApisTest.kt238
6 files changed, 658 insertions, 155 deletions
diff --git a/api/Android.bp b/api/Android.bp
index 8e063667826c..3fa9c600741e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -298,6 +298,28 @@ packages_to_document = [
"org.xmlpull",
]
+// These are libs from framework-internal-utils that are required (i.e. being referenced)
+// from framework-non-updatable-sources. Add more here when there's a need.
+// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+// dependencies gets bigger.
+android_non_updatable_stubs_libs = [
+ "android.hardware.cas-V1.2-java",
+ "android.hardware.health-V1.0-java-constants",
+ "android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V2.0-java",
+ "android.hardware.tv.input-V1.0-java-constants",
+ "android.hardware.usb-V1.0-java-constants",
+ "android.hardware.usb-V1.1-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
+ "android.hardware.vibrator-V1.3-java",
+ "framework-protos",
+]
+
+java_defaults {
+ name: "android-non-updatable-stubs-libs-defaults",
+ libs: android_non_updatable_stubs_libs,
+}
+
// Defaults for all stubs that include the non-updatable framework. These defaults do not include
// module symbols, so will not compile correctly on their own. Users must add module APIs to the
// classpath (or sources) somehow.
@@ -320,8 +342,11 @@ stubs_defaults {
"packages/modules/Media/apex/aidl/stable",
"hardware/interfaces/biometrics/common/aidl",
"hardware/interfaces/biometrics/fingerprint/aidl",
+ "hardware/interfaces/common/aidl",
+ "hardware/interfaces/common/fmq/aidl",
"hardware/interfaces/graphics/common/aidl",
"hardware/interfaces/keymaster/aidl",
+ "hardware/interfaces/power/aidl",
"system/hardware/interfaces/media/aidl",
],
},
@@ -329,18 +354,7 @@ stubs_defaults {
// from framework-non-updatable-sources. Add more here when there's a need.
// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
// dependencies gets bigger.
- libs: [
- "android.hardware.cas-V1.2-java",
- "android.hardware.health-V1.0-java-constants",
- "android.hardware.thermal-V1.0-java-constants",
- "android.hardware.thermal-V2.0-java",
- "android.hardware.tv.input-V1.0-java-constants",
- "android.hardware.usb-V1.0-java-constants",
- "android.hardware.usb-V1.1-java-constants",
- "android.hardware.usb.gadget-V1.0-java",
- "android.hardware.vibrator-V1.3-java",
- "framework-protos",
- ],
+ libs: android_non_updatable_stubs_libs,
flags: [
"--error NoSettingsProvider",
"--error UnhiddenSystemApi",
@@ -368,6 +382,18 @@ stubs_defaults {
],
}
+soong_config_module_type {
+ name: "non_updatable_exportable_droidstubs",
+ module_type: "droidstubs",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_hidden_api_exportable_stubs",
+ ],
+ properties: [
+ "dists",
+ ],
+}
+
// We resolve dependencies on APIs in modules by depending on a prebuilt of the whole
// platform (sdk_system_current_android). That prebuilt does not include module-lib APIs,
// so use the prebuilt module-lib stubs for modules that export module-lib stubs that the
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index c1add03fa31a..13d6ae5be3e8 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -27,7 +27,12 @@
// These modules provide source files for the stub libraries
/////////////////////////////////////////////////////////////////////
-droidstubs {
+soong_config_module_type_import {
+ from: "frameworks/base/api/Android.bp",
+ module_types: ["non_updatable_exportable_droidstubs"],
+}
+
+non_updatable_exportable_droidstubs {
name: "api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -45,24 +50,44 @@ droidstubs {
},
api_lint: {
enabled: true,
- new_since: ":android.api.public.latest",
+ new_since: ":android.api.combined.public.latest",
baseline_file: ":non-updatable-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "android-non-updatable.txt",
- tag: ".api.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable.txt",
+ tag: ".exportable.api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable.txt",
+ tag: ".api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
},
- ],
+ },
api_surface: "public",
}
@@ -86,7 +111,7 @@ module_libs = [
"\\)",
]
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "system-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -105,28 +130,48 @@ droidstubs {
},
api_lint: {
enabled: true,
- new_since: ":android.api.system.latest",
+ new_since: ":android.api.combined.system.latest",
baseline_file: ":non-updatable-system-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "android-non-updatable.txt",
- tag: ".api.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable.txt",
+ tag: ".exportable.api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable.txt",
+ tag: ".api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
},
- ],
+ },
api_surface: "system",
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "test-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -140,40 +185,72 @@ droidstubs {
},
api_lint: {
enabled: true,
- new_since: ":android.api.test.latest",
+ new_since: ":android.api.combined.test.latest",
baseline_file: ":non-updatable-test-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android.txt",
- tag: ".api.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "removed.txt",
- tag: ".removed-api.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android-non-updatable.txt",
- tag: ".api.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android.txt",
+ tag: ".exportable.api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "removed.txt",
+ tag: ".exportable.removed-api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable.txt",
+ tag: ".exportable.api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android.txt",
+ tag: ".api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "removed.txt",
+ tag: ".removed-api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable.txt",
+ tag: ".api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
},
- ],
+ },
api_surface: "test",
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "module-lib-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -192,24 +269,44 @@ droidstubs {
},
api_lint: {
enabled: true,
- new_since: ":android.api.module-lib.latest",
+ new_since: ":android.api.combined.module-lib.latest",
baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android-non-updatable.txt",
- tag: ".api.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable.txt",
+ tag: ".exportable.api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable.txt",
+ tag: ".api.txt",
+ },
+ {
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable-removed.txt",
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
},
- ],
+ },
api_surface: "module-lib",
}
@@ -1047,31 +1144,47 @@ java_library {
],
}
-droidstubs {
- name: "api_versions_public",
- srcs: [":android_stubs_current_with_test_libs{.jar}"],
+// Defaults for `droidstubs` modules that generate `api-versions.xml` files for
+// the various API surfaces.
+stubs_defaults {
+ name: "api_versions_base_defaults",
+ defaults_visibility: ["//visibility:private"],
generate_stubs: false,
api_levels_annotations_enabled: true,
api_levels_annotations_dirs: [
"sdk-dir",
"api-versions-jars-dir",
],
- api_levels_sdk_type: "public",
+}
+
+// Defaults for `droidstubs` modules that generate complete `api-versions.xml`
+// files, i.e. include SDK extensions.
+stubs_defaults {
+ name: "api_versions_complete_defaults",
+ defaults_visibility: ["//visibility:private"],
+ defaults: ["api_versions_base_defaults"],
extensions_info_file: ":sdk-extensions-info",
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the public APIs, both updatable and non-updatable and historic
+// information about all previous dessert and SDK extension releases.
+droidstubs {
+ name: "api_versions_public",
+ defaults: ["api_versions_complete_defaults"],
+ srcs: [":android_stubs_current_with_test_libs{.jar}"],
+ api_levels_sdk_type: "public",
visibility: ["//frameworks/base"],
}
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the system APIs, both updatable and non-updatable and historic
+// information about all previous dessert and SDK extension releases.
droidstubs {
name: "api_versions_system",
+ defaults: ["api_versions_complete_defaults"],
srcs: [":android_system_stubs_current_with_test_libs{.jar}"],
- generate_stubs: false,
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
api_levels_sdk_type: "system",
- extensions_info_file: ":sdk-extensions-info",
dists: [
// Make the api-versions.xml file for the system API available in the
// sdk build target.
@@ -1083,42 +1196,99 @@ droidstubs {
],
}
-// This module can be built with:
-// m out/soong/.intermediates/frameworks/base/api/api_versions_module_lib/android_common/metalava/api-versions.xml
-droidstubs {
- name: "api_versions_module_lib",
- srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
- generate_stubs: false,
- api_levels_annotations_enabled: true,
+// Defaults for `droidstubs` modules that generate `api-versions.xml` files that
+// only include non-updatable code, i.e. for platform API only, not SDK
+// extensions.
+stubs_defaults {
+ name: "api_versions_non_updatable_defaults",
+ defaults_visibility: ["//visibility:private"],
+ defaults: ["api_versions_base_defaults"],
// this only has the non-updatable portions of the module lib sdk,
// which can reference classes from updatable apexes, so remove references to them
// from this api_versions file.
flags: ["--remove-missing-class-references-in-api-levels"],
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
- api_levels_sdk_type: "module-lib",
// extensions_info_file is purposefully omitted, because this module should just be
// the non-updatable portions of the sdk, and extension sdks are updatable.
}
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about only the non-updatable module-lib APIs and historic information about
+// all previous dessert and SDK extension releases. That historic information
+// may include information about APIs that were previously not-updatable which
+// have since become updatable.
+droidstubs {
+ name: "api_versions_module_lib",
+ defaults: ["api_versions_non_updatable_defaults"],
+ srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
+ api_levels_sdk_type: "module-lib",
+}
+
+// Create a single jar containing the whole module-lib API surface.
+// This is needed because Metalava only consumes the first jar file it is given
+// and ignores the rest.
+java_library {
+ name: "android_module_stubs_complete_current_with_test_libs",
+ static_libs: [
+ "android_module_stubs_current_with_test_libs",
+ "framework-updatable-stubs-module_libs_api-exportable",
+ ],
+ defaults: ["android.jar_defaults"],
+ visibility: [
+ "//visibility:override",
+ "//visibility:private",
+ ],
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the module-lib APIs, both updatable and non-updatable and historic
+// information about all previous dessert and SDK extension releases.
+droidstubs {
+ name: "api_versions_module_lib_complete",
+ defaults: ["api_versions_complete_defaults"],
+ srcs: [":android_module_stubs_complete_current_with_test_libs{.jar}"],
+ api_levels_sdk_type: "module-lib",
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about only the non-updatable system-server APIs and historic information
+// about all previous dessert and SDK extension releases. That historic
+// information may include information about APIs that were previously
+// not-updatable which have since become updatable.
droidstubs {
name: "api_versions_system_server",
+ defaults: ["api_versions_non_updatable_defaults"],
srcs: [":android_system_server_stubs_current_with_test_libs{.jar}"],
- generate_stubs: false,
- api_levels_annotations_enabled: true,
- // this only has the non-updatable portions of the system server sdk,
- // which can reference classes from updatable apexes, so remove references to them
- // from this api_versions file.
- flags: ["--remove-missing-class-references-in-api-levels"],
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
+ api_levels_sdk_type: "system-server",
+}
+
+// Create a single jar containing the whole system-server API surface.
+// This is needed because Metalava only consumes the first jar file it is given
+// and ignores the rest.
+java_library {
+ name: "android_system_server_stubs_complete_current_with_test_libs",
+ static_libs: [
+ "android_system_server_stubs_current_with_test_libs",
+ // system-server extends module-lib but libraries which only service-*
+ // libraries provided system-server APIs, so include module-lib APIs for
+ // the others, e.g. framework-* libraries.
+ "framework-updatable-stubs-module_libs_api-exportable",
+ "framework-updatable-stubs-system_server_api-exportable",
],
+ defaults: ["android.jar_defaults"],
+ visibility: [
+ "//visibility:override",
+ "//visibility:private",
+ ],
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the system-server APIs, both updatable and non-updatable and
+// historic information about all previous dessert and SDK extension releases.
+droidstubs {
+ name: "api_versions_system_server_complete",
+ defaults: ["api_versions_complete_defaults"],
+ srcs: [":android_system_server_stubs_complete_current_with_test_libs{.jar}"],
api_levels_sdk_type: "system-server",
- // extensions_info_file is purposefully omitted, because this module should just be
- // the non-updatable portions of the sdk, and extension sdks are updatable.
}
/////////////////////////////////////////////////////////////////////
diff --git a/api/api.go b/api/api.go
index b31a26c90789..449fac63f90c 100644
--- a/api/api.go
+++ b/api/api.go
@@ -386,6 +386,26 @@ func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []
ctx.CreateModule(java.LibraryFactory, &props)
}
+func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
+ // The user of this module compiles against the "core" SDK and against non-updatable bootclasspathModules,
+ // so remove to avoid dupes.
+ bootclasspathModules := removeAll(bootclasspath, core_libraries_modules)
+ bootclasspathModules = removeAll(bootclasspath, non_updatable_modules)
+ modules := append(
+ // Include all the module-lib APIs from the bootclasspath libraries.
+ transformArray(bootclasspathModules, "", ".stubs.exportable.module_lib"),
+ // Then add all the system-server APIs from the service-* libraries.
+ transformArray(system_server_classpath, "", ".stubs.exportable.system_server")...,
+ )
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("framework-updatable-stubs-system_server_api-exportable")
+ props.Static_libs = modules
+ props.Sdk_version = proptools.StringPtr("system_server_current")
+ props.Visibility = []string{"//frameworks/base"}
+ props.Is_stubs_module = proptools.BoolPtr(true)
+ ctx.CreateModule(java.LibraryFactory, &props)
+}
+
func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []string) {
props := fgProps{}
props.Name = proptools.StringPtr("all-modules-public-stubs-source")
@@ -531,6 +551,7 @@ func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
createMergedSystemExportableStubs(ctx, bootclasspath)
createMergedTestExportableStubsForNonUpdatableModules(ctx)
createMergedFrameworkModuleLibExportableStubs(ctx, bootclasspath)
+ createMergedFrameworkSystemServerExportableStubs(ctx, bootclasspath, system_server_classpath)
createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
diff --git a/api/coverage/tools/Android.bp b/api/coverage/tools/Android.bp
index 3e169120dc48..caaca99bdc45 100644
--- a/api/coverage/tools/Android.bp
+++ b/api/coverage/tools/Android.bp
@@ -30,3 +30,24 @@ java_library_host {
type: "full",
},
}
+
+java_test_host {
+ name: "extract-flagged-apis-test",
+ srcs: ["ExtractFlaggedApisTest.kt"],
+ libs: [
+ "extract_flagged_apis_proto",
+ "junit",
+ "libprotobuf-java-full",
+ ],
+ static_libs: [
+ "truth",
+ "truth-liteproto-extension",
+ "truth-proto-extension",
+ ],
+ data: [
+ ":extract-flagged-apis",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+}
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index caa1929abd54..5efda98a1518 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -16,51 +16,78 @@
package android.platform.coverage
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.text.ApiFile
import java.io.File
import java.io.FileWriter
/** Usage: extract-flagged-apis <api text file> <output .pb file> */
fun main(args: Array<String>) {
- var cb = ApiFile.parseApi(listOf(File(args[0])))
- var builder = FlagApiMap.newBuilder()
+ val cb = ApiFile.parseApi(listOf(File(args[0])))
+ val builder = FlagApiMap.newBuilder()
for (pkg in cb.getPackages().packages) {
- var packageName = pkg.qualifiedName()
- pkg.allClasses()
- .filter { it.methods().size > 0 }
- .forEach {
- for (method in it.methods()) {
- val flagValue =
- method.modifiers
- .findAnnotation("android.annotation.FlaggedApi")
- ?.findAttribute("value")
- ?.value
- ?.value()
- if (flagValue != null && flagValue is String) {
- var api =
- JavaMethod.newBuilder()
- .setPackageName(packageName)
- .setClassName(it.fullName())
- .setMethodName(method.name())
- for (param in method.parameters()) {
- api.addParameters(param.type().toTypeString())
- }
- if (builder.containsFlagToApi(flagValue)) {
- var updatedApis =
- builder
- .getFlagToApiOrThrow(flagValue)
- .toBuilder()
- .addJavaMethods(api)
- .build()
- builder.putFlagToApi(flagValue, updatedApis)
- } else {
- var apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
- builder.putFlagToApi(flagValue, apis)
- }
- }
- }
- }
+ val packageName = pkg.qualifiedName()
+ pkg.allClasses().forEach {
+ extractFlaggedApisFromClass(it, it.methods(), packageName, builder)
+ extractFlaggedApisFromClass(it, it.constructors(), packageName, builder)
+ }
}
val flagApiMap = builder.build()
FileWriter(args[1]).use { it.write(flagApiMap.toString()) }
}
+
+fun extractFlaggedApisFromClass(
+ classItem: ClassItem,
+ methods: List<MethodItem>,
+ packageName: String,
+ builder: FlagApiMap.Builder
+) {
+ if (methods.isEmpty()) return
+ val classFlag = getClassFlag(classItem)
+ for (method in methods) {
+ val methodFlag = getFlagAnnotation(method) ?: classFlag
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName(packageName)
+ .setClassName(classItem.fullName())
+ .setMethodName(method.name())
+ for (param in method.parameters()) {
+ api.addParameters(param.type().toTypeString())
+ }
+ if (methodFlag != null) {
+ addFlaggedApi(builder, api, methodFlag)
+ }
+ }
+}
+
+fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: String) {
+ if (builder.containsFlagToApi(flag)) {
+ val updatedApis = builder.getFlagToApiOrThrow(flag).toBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, updatedApis)
+ } else {
+ val apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, apis)
+ }
+}
+
+fun getClassFlag(classItem: ClassItem): String? {
+ var classFlag = getFlagAnnotation(classItem)
+ var cur = classItem
+ // If a class is not an inner class, use its @FlaggedApi annotation value.
+ // Otherwise, use the flag value of the closest outer class that is annotated by @FlaggedApi.
+ while (cur.isInnerClass() && classFlag == null) {
+ cur = cur.parent() as ClassItem
+ classFlag = getFlagAnnotation(cur)
+ }
+ return classFlag
+}
+
+fun getFlagAnnotation(item: Item): String? {
+ return item.modifiers
+ .findAnnotation("android.annotation.FlaggedApi")
+ ?.findAttribute("value")
+ ?.value
+ ?.value() as? String
+}
diff --git a/api/coverage/tools/ExtractFlaggedApisTest.kt b/api/coverage/tools/ExtractFlaggedApisTest.kt
new file mode 100644
index 000000000000..427be36254d3
--- /dev/null
+++ b/api/coverage/tools/ExtractFlaggedApisTest.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2024 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 android.platform.coverage
+
+import com.google.common.truth.extensions.proto.ProtoTruth.assertThat
+import com.google.protobuf.TextFormat
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ExtractFlaggedApisTest {
+
+ companion object {
+ const val COMMAND = "java -jar extract-flagged-apis.jar %s %s"
+ }
+
+ private var apiTextFile: Path = Files.createTempFile("current", ".txt")
+ private var flagToApiMap: Path = Files.createTempFile("flag_api_map", ".textproto")
+
+ @Before
+ fun setup() {
+ apiTextFile = Files.createTempFile("current", ".txt")
+ flagToApiMap = Files.createTempFile("flag_api_map", ".textproto")
+ }
+
+ @After
+ fun cleanup() {
+ Files.deleteIfExists(apiTextFile)
+ Files.deleteIfExists(flagToApiMap)
+ }
+
+ @Test
+ fun extractFlaggedApis_onlyMethodFlag_useMethodFlag() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.net.ipsec.ike {
+ public final class IkeSession implements java.lang.AutoCloseable {
+ method @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public void dump(@NonNull java.io.PrintWriter);
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName("android.net.ipsec.ike")
+ .setClassName("IkeSession")
+ .setMethodName("dump")
+ api.addParameters("java.io.PrintWriter")
+ addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api")
+ assertThat(result).isEqualTo(expected.build())
+ }
+
+ @Test
+ fun extractFlaggedApis_onlyClassFlag_useClassFlag() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.net.ipsec.ike {
+ @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public final class IkeSession implements java.lang.AutoCloseable {
+ method public void dump(@NonNull java.io.PrintWriter);
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName("android.net.ipsec.ike")
+ .setClassName("IkeSession")
+ .setMethodName("dump")
+ api.addParameters("java.io.PrintWriter")
+ addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api")
+ assertThat(result).isEqualTo(expected.build())
+ }
+
+ @Test
+ fun extractFlaggedApis_flaggedConstructorsAreFlaggedApis() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.app.pinner {
+ @FlaggedApi("android.app.pinner_service_client_api") public class PinnerServiceClient {
+ ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnerServiceClient();
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName("android.app.pinner")
+ .setClassName("PinnerServiceClient")
+ .setMethodName("PinnerServiceClient")
+ addFlaggedApi(expected, api, "android.app.pinner_service_client_api")
+ assertThat(result).isEqualTo(expected.build())
+ }
+
+ @Test
+ fun extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlag() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.location.provider {
+ @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ForwardGeocodeRequest implements android.os.Parcelable {
+ method public int describeContents();
+ }
+ public static final class ForwardGeocodeRequest.Builder {
+ method @NonNull public android.location.provider.ForwardGeocodeRequest build();
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api1 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.location.provider")
+ .setClassName("ForwardGeocodeRequest")
+ .setMethodName("describeContents")
+ addFlaggedApi(expected, api1, "Flags.FLAG_NEW_GEOCODER")
+ val api2 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.location.provider")
+ .setClassName("ForwardGeocodeRequest.Builder")
+ .setMethodName("build")
+ addFlaggedApi(expected, api2, "Flags.FLAG_NEW_GEOCODER")
+ assertThat(result).ignoringRepeatedFieldOrder().isEqualTo(expected.build())
+ }
+
+ @Test
+ fun extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlag_deeplyNested() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.package.xyz {
+ @FlaggedApi(outer_class_flag) public final class OuterClass {
+ method public int apiInOuterClass();
+ }
+ public final class OuterClass.Deeply.NestedClass {
+ method public void apiInNestedClass();
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api1 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.package.xyz")
+ .setClassName("OuterClass")
+ .setMethodName("apiInOuterClass")
+ addFlaggedApi(expected, api1, "outer_class_flag")
+ val api2 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.package.xyz")
+ .setClassName("OuterClass.Deeply.NestedClass")
+ .setMethodName("apiInNestedClass")
+ addFlaggedApi(expected, api2, "outer_class_flag")
+ assertThat(result).ignoringRepeatedFieldOrder().isEqualTo(expected.build())
+ }
+
+ private fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: String) {
+ if (builder.containsFlagToApi(flag)) {
+ val updatedApis =
+ builder.getFlagToApiOrThrow(flag).toBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, updatedApis)
+ } else {
+ val apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, apis)
+ }
+ }
+
+ private fun createCommand(): Array<String> {
+ val command =
+ String.format(COMMAND, apiTextFile.toAbsolutePath(), flagToApiMap.toAbsolutePath())
+ return command.split(" ").toTypedArray()
+ }
+}