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 cd859448b0..111ea91f85 100644 --- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt +++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt @@ -269,6 +269,42 @@ class CheckFlaggedApisTest { assertEquals(expected, actual) } + @Test + fun testNestedFlagsOuterFlagWins() { + val apiSignature = + """ + // Signature format: 2.0 + package android { + @FlaggedApi("android.flag.foo") public final class A { + method @FlaggedApi("android.flag.bar") public boolean method(); + } + @FlaggedApi("android.flag.bar") public final class B { + method @FlaggedApi("android.flag.foo") public boolean method(); + } + } + """ + .trim() + + val apiVersions = + """ + + + + + + + """ + .trim() + + val expected = setOf() + val actual = + findErrors( + parseApiSignature("in-memory", apiSignature.byteInputStream()), + parseFlagValues(generateFlagsProto(DISABLED, ENABLED)), + parseApiVersions(apiVersions.byteInputStream())) + assertEquals(expected, actual) + } + @Test fun testFindErrorsDisabledFlaggedApiIsPresent() { val expected = 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 b514048bd6..a277ce815f 100644 --- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt +++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt @@ -385,10 +385,48 @@ internal fun findErrors( return false } + + /** + * Returns whether the given flag is enabled for the given symbol. + * + * A flagged member inside a flagged class is ignored (and the flag value considered disabled) if + * the class' flag is disabled. + * + * @param symbol the symbol to check + * @param flag the flag to check + * @return whether the flag is enabled for the given symbol + */ + fun isFlagEnabledForSymbol(symbol: Symbol, flag: Flag): Boolean { + when (symbol) { + is ClassSymbol -> return flags.getValue(flag) + is MemberSymbol -> { + val memberFlagValue = flags.getValue(flag) + if (!memberFlagValue) { + return false + } + // Special case: if the MemberSymbol's flag is enabled, but the outer + // ClassSymbol's flag (if the class is flagged) is disabled, consider + // the MemberSymbol's flag as disabled: + // + // @FlaggedApi(this-flag-is-disabled) Clazz { + // @FlaggedApi(this-flag-is-enabled) method(); // The Clazz' flag "wins" + // } + // + // Note: the current implementation does not handle nested classes. + val classFlagValue = + flaggedSymbolsInSource + .find { it.first.toPrettyString() == symbol.clazz } + ?.let { flags.getValue(it.second) } + ?: true + return classFlagValue + } + } + } + val errors = mutableSetOf() for ((symbol, flag) in flaggedSymbolsInSource) { try { - if (flags.getValue(flag)) { + if (isFlagEnabledForSymbol(symbol, flag)) { if (!symbolsInOutput.containsSymbol(symbol)) { errors.add(EnabledFlaggedApiNotPresentError(symbol, flag)) }