Merge "Drive instrumentation with build flag" into main
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
index 62c9cbb..0569bfd 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
@@ -31,8 +31,9 @@
       // Signature format: 2.0
       package android {
         @FlaggedApi("android.flag.foo") public final class Clazz {
-          ctor public Clazz();
+          ctor @FlaggedApi("android.flag.foo") public Clazz();
           field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1
+          method @FlaggedApi("android.flag.foo") public int getErrorCode();
         }
         @FlaggedApi("android.flag.bar") public static class Clazz.Builder {
         }
@@ -47,6 +48,7 @@
         <class name="android/Clazz" since="1">
           <method name="&lt;init>()V"/>
           <field name="FOO"/>
+          <method name="getErrorCode()I"/>
         </class>
         <class name="android/Clazz${"$"}Builder" since="2">
         </class>
@@ -88,7 +90,9 @@
     val expected =
         setOf(
             Pair(Symbol("android.Clazz"), Flag("android.flag.foo")),
+            Pair(Symbol("android.Clazz.Clazz()"), Flag("android.flag.foo")),
             Pair(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
+            Pair(Symbol("android.Clazz.getErrorCode()"), Flag("android.flag.foo")),
             Pair(Symbol("android.Clazz.Builder"), Flag("android.flag.bar")),
         )
     val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream())
@@ -108,7 +112,9 @@
     val expected: Set<Symbol> =
         setOf(
             Symbol("android.Clazz"),
+            Symbol("android.Clazz.Clazz()"),
             Symbol("android.Clazz.FOO"),
+            Symbol("android.Clazz.getErrorCode()"),
             Symbol("android.Clazz.Builder"),
         )
     val actual = parseApiVersions(API_VERSIONS.byteInputStream())
@@ -131,8 +137,12 @@
     val expected =
         setOf<ApiError>(
             DisabledFlaggedApiIsPresentError(Symbol("android.Clazz"), Flag("android.flag.foo")),
+            DisabledFlaggedApiIsPresentError(
+                Symbol("android.Clazz.Clazz()"), Flag("android.flag.foo")),
             DisabledFlaggedApiIsPresentError(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
             DisabledFlaggedApiIsPresentError(
+                Symbol("android.Clazz.getErrorCode()"), Flag("android.flag.foo")),
+            DisabledFlaggedApiIsPresentError(
                 Symbol("android.Clazz.Builder"), Flag("android.flag.bar")),
         )
     val actual =
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
index 918a5d9..0c078a0 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
@@ -22,6 +22,7 @@
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.FieldItem
 import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.text.ApiFile
 import com.github.ajalt.clikt.core.CliktCommand
 import com.github.ajalt.clikt.core.ProgramResult
@@ -48,7 +49,7 @@
 @JvmInline
 internal value class Symbol(val name: String) {
   companion object {
-    private val FORBIDDEN_CHARS = listOf('/', '#', '$')
+    private val FORBIDDEN_CHARS = listOf('#', '$')
 
     /** Create a new Symbol from a String that may include delimiters other than dot. */
     fun create(name: String): Symbol {
@@ -187,6 +188,25 @@
           }
         }
 
+        override fun visitMethod(method: MethodItem) {
+          getFlagOrNull(method)?.let { flag ->
+            val name = buildString {
+              append(method.containingClass().qualifiedName())
+              append(".")
+              append(method.name())
+              append("(")
+              // TODO(334870672): replace this early return with proper parsing of the command line
+              // arguments, followed by translation to Lname/of/class; + III format
+              if (!method.parameters().isEmpty()) {
+                return
+              }
+              append(")")
+            }
+            val symbol = Symbol.create(name)
+            output.add(Pair(symbol, flag))
+          }
+        }
+
         private fun getFlagOrNull(item: Item): Flag? {
           return item.modifiers
               .findAnnotation("android.annotation.FlaggedApi")
@@ -223,7 +243,7 @@
         requireNotNull(cls.getAttribute("name")) {
           "Bad XML: <class> element without name attribute"
         }
-    output.add(Symbol.create(className))
+    output.add(Symbol.create(className.replace("/", ".")))
   }
 
   val fields = document.getElementsByTagName("field")
@@ -235,9 +255,31 @@
           "Bad XML: <field> element without name attribute"
         }
     val className =
-        requireNotNull(field.getParentNode()) { "Bad XML: top level <field> element" }
-            .getAttribute("name")
-    output.add(Symbol.create("$className.$fieldName"))
+        requireNotNull(field.getParentNode()?.getAttribute("name")) { "Bad XML: top level <field> element" }
+    output.add(Symbol.create("${className.replace("/", ".")}.$fieldName"))
+  }
+
+  val methods = document.getElementsByTagName("method")
+  // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead
+  for (i in 0.rangeUntil(methods.getLength())) {
+    val method = methods.item(i)
+    val methodSignature =
+        requireNotNull(method.getAttribute("name")) {
+          "Bad XML: <method> element without name attribute"
+        }
+    val methodSignatureParts = methodSignature.split(Regex("\\(|\\)"))
+    if (methodSignatureParts.size != 3) {
+      throw Exception("Bad XML: method signature '$methodSignature'")
+    }
+    var (methodName, methodArgs, methodReturnValue) = methodSignatureParts
+    val packageAndClassName =
+        requireNotNull(method.getParentNode()?.getAttribute("name")) {
+          "Bad XML: top level <method> element, or <class> element missing name attribute"
+        }
+    if (methodName == "<init>") {
+      methodName = packageAndClassName.split("/").last()
+    }
+    output.add(Symbol.create("${packageAndClassName.replace("/", ".")}.$methodName($methodArgs)"))
   }
 
   return output