Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a484783a authored by Yiming Pan's avatar Yiming Pan Committed by Gerrit Code Review
Browse files

Merge "APIs in a nested class can be flagged by outer class." into main

parents 114bff10 dd35c2f8
Loading
Loading
Loading
Loading
+23 −13
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
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
@@ -44,20 +45,9 @@ fun extractFlaggedApisFromClass(
    builder: FlagApiMap.Builder
) {
    if (methods.isEmpty()) return
    val classFlag =
        classItem.modifiers
            .findAnnotation("android.annotation.FlaggedApi")
            ?.findAttribute("value")
            ?.value
            ?.value() as? String
    val classFlag = getClassFlag(classItem)
    for (method in methods) {
        val methodFlag =
            method.modifiers
                .findAnnotation("android.annotation.FlaggedApi")
                ?.findAttribute("value")
                ?.value
                ?.value() as? String
                ?: classFlag
        val methodFlag = getFlagAnnotation(method) ?: classFlag
        val api =
            JavaMethod.newBuilder()
                .setPackageName(packageName)
@@ -81,3 +71,23 @@ fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: St
        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
}
+78 −0
Original line number Diff line number Diff line
@@ -141,6 +141,84 @@ class ExtractFlaggedApisTest {
        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 =