Merge changes I397b32ae,Ic244b896,I5ccf2a64 into main

* changes:
  check-flagged-apis: parse flag names and values
  check-flagged-apis: parse API signature files
  check-flagged-apis: add unit test infrastructure
diff --git a/tools/check-flagged-apis/Android.bp b/tools/check-flagged-apis/Android.bp
index 209c89b..ebd79c1 100644
--- a/tools/check-flagged-apis/Android.bp
+++ b/tools/check-flagged-apis/Android.bp
@@ -13,16 +13,39 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_updatable_sdk_apis",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_defaults {
+    name: "check-flagged-apis-defaults",
+    srcs: [
+        "src/com/android/checkflaggedapis/Main.kt",
+    ],
+    static_libs: [
+        "libaconfig_java_proto_lite",
+        "metalava-signature-reader",
+        "metalava-tools-common-m2-deps",
+    ],
+}
+
 java_binary_host {
     name: "check-flagged-apis",
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "metalava-tools-common-m2-deps",
+    defaults: [
+        "check-flagged-apis-defaults",
     ],
     main_class: "com.android.checkflaggedapis.Main",
 }
+
+java_test_host {
+    name: "check-flagged-apis-test",
+    defaults: [
+        "check-flagged-apis-defaults",
+    ],
+    srcs: [
+        "src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt",
+    ],
+    static_libs: [
+        "tradefed",
+    ],
+}
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
new file mode 100644
index 0000000..5fb67be
--- /dev/null
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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 com.android.checkflaggedapis
+
+import android.aconfig.Aconfig
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+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()
+
+private val PARSED_FLAGS =
+    {
+      val parsed_flag =
+          Aconfig.parsed_flag
+              .newBuilder()
+              .setPackage("android.flag")
+              .setName("foo")
+              .setState(Aconfig.flag_state.ENABLED)
+              .setPermission(Aconfig.flag_permission.READ_ONLY)
+              .build()
+      val parsed_flags = Aconfig.parsed_flags.newBuilder().addParsedFlag(parsed_flag).build()
+      val binaryProto = ByteArrayOutputStream()
+      parsed_flags.writeTo(binaryProto)
+      ByteArrayInputStream(binaryProto.toByteArray())
+    }()
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class CheckFlaggedApisTest : BaseHostJUnit4Test() {
+  @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)
+  }
+
+  @Test
+  fun testParseFlagValues() {
+    val expected: Map<Flag, Boolean> = mapOf(Flag("android.flag.foo") to true)
+    val actual = parseFlagValues(PARSED_FLAGS)
+    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..005f6c0 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,17 @@
 
 package com.android.checkflaggedapis
 
+import android.aconfig.Aconfig
+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 +79,76 @@
   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()
+  private val flagValuesPath by
+      option("--flag-values")
+          .help(
+              """
+            Path to aconfig parsed_flags binary proto file.
+            Tip: `m all_aconfig_declarations` will generate a file that includes all information about all flags.
+            """)
+          .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)
+        }
+    @Suppress("UNUSED_VARIABLE")
+    val flags = flagValuesPath.toFile().inputStream().use { parseFlagValues(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
+}
+
+internal fun parseFlagValues(input: InputStream): Map<Flag, Boolean> {
+  val parsedFlags = Aconfig.parsed_flags.parseFrom(input).getParsedFlagList()
+  return parsedFlags.associateBy(
+      { Flag("${it.getPackage()}.${it.getName()}") },
+      { it.getState() == Aconfig.flag_state.ENABLED })
+}
+
 fun main(args: Array<String>) = CheckCommand().main(args)