check-flagged-apis: parse API signature files

Teach check-flagged-apis to extract flagged APIs from API signature files.

To keep things simple, only consider fields for now: support for classes
and methods will be added in a later CL.

Note: `m frameworks-base-api-current.txt` will generate an API signature
file that includes both the platform and mainline APIs.

Bug: 334870672
Test: atest --host check-flagged-apis-test
Test: check-flagged-apis --api-signature out/target/product/mainline_x86/obj/ETC/frameworks-base-api-current.txt_intermediates/frameworks-base-api-current.txt
Change-Id: Ic244b896672569f44af793796189b34c1f9d0c36
diff --git a/tools/check-flagged-apis/Android.bp b/tools/check-flagged-apis/Android.bp
index c17c9b2..7d0d33b 100644
--- a/tools/check-flagged-apis/Android.bp
+++ b/tools/check-flagged-apis/Android.bp
@@ -23,6 +23,7 @@
         "src/com/android/checkflaggedapis/Main.kt",
     ],
     static_libs: [
+        "metalava-signature-reader",
         "metalava-tools-common-m2-deps",
     ],
 }
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 badcdee..d7890d7 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
@@ -17,10 +17,28 @@
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
 
+private val API_SIGNATURE =
+    """
+      // Signature format: 2.0
+      package android {
+        public final class Clazz {
+          ctor public Clazz();
+          field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1
+        }
+      }
+"""
+        .trim()
+
 @RunWith(DeviceJUnit4ClassRunner::class)
 class CheckFlaggedApisTest : BaseHostJUnit4Test() {
-  @Test fun testPlaceholder() {}
+  @Test
+  fun testParseApiSignature() {
+    val expected = setOf(Pair(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")))
+    val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream())
+    assertEquals(expected, 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 af8be11..5fede7b 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
@@ -17,8 +17,16 @@
 
 package com.android.checkflaggedapis
 
+import com.android.tools.metalava.model.BaseItemVisitor
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.text.ApiFile
 import com.github.ajalt.clikt.core.CliktCommand
 import com.github.ajalt.clikt.core.ProgramResult
+import com.github.ajalt.clikt.parameters.options.help
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import com.github.ajalt.clikt.parameters.types.path
+import java.io.InputStream
 
 /**
  * Class representing the fully qualified name of a class, method or field.
@@ -70,11 +78,58 @@
   override fun toString(): String = name.toString()
 }
 
-class CheckCommand : CliktCommand() {
+class CheckCommand :
+    CliktCommand(
+        help =
+            """
+Check that all flagged APIs are used in the correct way.
+
+This tool reads the API signature file and checks that all flagged APIs are used in the correct way.
+
+The tool will exit with a non-zero exit code if any flagged APIs are found to be used in the incorrect way.
+""") {
+  private val apiSignaturePath by
+      option("--api-signature")
+          .help(
+              """
+              Path to API signature file.
+              Usually named *current.txt.
+              Tip: `m frameworks-base-api-current.txt` will generate a file that includes all platform and mainline APIs.
+              """)
+          .path(mustExist = true, canBeDir = false, mustBeReadable = true)
+          .required()
+
   override fun run() {
-    println("hello world")
+    @Suppress("UNUSED_VARIABLE")
+    val flaggedSymbols =
+        apiSignaturePath.toFile().inputStream().use {
+          parseApiSignature(apiSignaturePath.toString(), it)
+        }
     throw ProgramResult(0)
   }
 }
 
+internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> {
+  // TODO(334870672): add support for classes and metods
+  val output = mutableSetOf<Pair<Symbol, Flag>>()
+  val visitor =
+      object : BaseItemVisitor() {
+        override fun visitField(field: FieldItem) {
+          val flag =
+              field.modifiers
+                  .findAnnotation("android.annotation.FlaggedApi")
+                  ?.findAttribute("value")
+                  ?.value
+                  ?.value() as? String
+          if (flag != null) {
+            val symbol = Symbol.create(field.baselineElementId())
+            output.add(Pair(symbol, Flag(flag)))
+          }
+        }
+      }
+  val codebase = ApiFile.parseApi(path, input)
+  codebase.accept(visitor)
+  return output
+}
+
 fun main(args: Array<String>) = CheckCommand().main(args)