diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt index 0d67214be912712c68f7c2ca5b94b80f1245d146..ed32741d960c1928d3e2716f067753abff4dbc80 100644 --- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt +++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt @@ -301,7 +301,12 @@ class ApiAnalyzer( } fun generateInheritedStubs(filterEmit: Predicate<Item>, filterReference: Predicate<Item>) { - packages.allClasses().forEach { + // When analyzing libraries we may discover some new classes during traversal; these aren't + // part of the API but may be super classes or interfaces; these will then be added into the + // package class lists, which could trigger a concurrent modification, so create a snapshot + // of the class list and iterate over it: + val allClasses = packages.allClasses().toList() + allClasses.forEach { if (filterEmit.test(it)) { generateInheritedStubs(it, filterEmit, filterReference) } diff --git a/src/main/java/com/android/tools/metalava/Constants.kt b/src/main/java/com/android/tools/metalava/Constants.kt index 59ffda5afd20f5534e256fea05f8b5f5d72e4903..dd071a2f1ec7d2415cb68827bf0515ace5dffa2f 100644 --- a/src/main/java/com/android/tools/metalava/Constants.kt +++ b/src/main/java/com/android/tools/metalava/Constants.kt @@ -37,6 +37,7 @@ const val ANDROID_NONNULL = "android.annotation.NonNull" const val ANDROIDX_VISIBLE_FOR_TESTING = "androidx.annotation.VisibleForTesting" const val ANDROID_SUPPORT_VISIBLE_FOR_TESTING = "android.support.annotation.VisibleForTesting" const val ATTR_OTHERWISE = "otherwise" +const val CARRIER_PRIVILEGES_MARKER = "carrier privileges" const val ENV_VAR_METALAVA_TESTS_RUNNING = "METALAVA_TESTS_RUNNING" const val ENV_VAR_METALAVA_DUMP_ARGV = "METALAVA_DUMP_ARGV" diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt index cd3f4d9d531de5de9bae8f45a0c7374c2a07102b..c2550929bc43457eb40cbbd2c278e24a37ee64de 100644 --- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt +++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt @@ -5,6 +5,7 @@ import com.android.sdklib.SdkVersionInfo import com.android.sdklib.repository.AndroidSdkHandler import com.android.tools.lint.LintCliClient import com.android.tools.lint.checks.ApiLookup +import com.android.tools.lint.detector.api.editDistance import com.android.tools.lint.helpers.DefaultJavaEvaluator import com.android.tools.metalava.doclava1.Errors import com.android.tools.metalava.model.AnnotationAttributeValue @@ -311,13 +312,26 @@ class DocAnalyzer( resolved else { val v: Any = value.value() ?: value.toSource() + if (v == CARRIER_PRIVILEGES_MARKER) { + // TODO: Warn if using allOf with carrier + sb.append("{@link android.telephony.TelephonyManager#hasCarrierPrivileges carrier privileges}") + continue + } findPermissionField(codebase, v) } if (field == null) { - reporter.report( - Errors.MISSING_PERMISSION, item, - "Cannot find permission field for $value required by $item (may be hidden or removed)" - ) + val v = value.value()?.toString() ?: value.toSource() + if (editDistance(CARRIER_PRIVILEGES_MARKER, v, 3) < 3) { + reporter.report( + Errors.MISSING_PERMISSION, item, + "Unrecognized permission `$v`; did you mean `$CARRIER_PRIVILEGES_MARKER`?" + ) + } else { + reporter.report( + Errors.MISSING_PERMISSION, item, + "Cannot find permission field for $value required by $item (may be hidden or removed)" + ) + } sb.append(value.toSource()) } else { if (filterReference.test(field)) { diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties index 2139cec290dc630c8a915e245203d749a0ce9fd9..579154c85e1672425b5da310e96dd26512e5c7a6 100644 --- a/src/main/resources/version.properties +++ b/src/main/resources/version.properties @@ -2,4 +2,4 @@ # Version definition # This file is read by gradle build scripts, but also packaged with metalava # as a resource for the Version classes to read. -metalavaVersion=1.2.8 +metalavaVersion=1.2.9 diff --git a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt index 13a86e4e3d20a4ea767dd66e93517da3c3668a90..5811331309cef36243f9023e081aa05181696efa 100644 --- a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt +++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt @@ -152,6 +152,7 @@ class DocAnalyzerTest : DriverTest() { @Test fun `Document Permissions`() { check( + docStubs = true, sourceFiles = *arrayOf( java( """ @@ -180,6 +181,15 @@ class DocAnalyzerTest : DriverTest() { @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) // b/73559440 public void test5() { } + + @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, "carrier privileges"}) + public void test6() { + } + + // Typo in marker + @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, "carier priviliges"}) + public void test6() { + } } """ ), @@ -201,6 +211,7 @@ class DocAnalyzerTest : DriverTest() { ), checkCompilation = false, // needs androidx.annotations in classpath checkDoclava1 = false, + warnings = "src/test/pkg/PermissionTest.java:31: lint: Unrecognized permission `carier priviliges`; did you mean `carrier privileges`? [MissingPermission]", stubs = arrayOf( """ package test.pkg; @@ -230,6 +241,16 @@ class DocAnalyzerTest : DriverTest() { public void test4() { throw new RuntimeException("Stub!"); } @androidx.annotation.RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true) public void test5() { throw new RuntimeException("Stub!"); } + /** + * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.telephony.TelephonyManager#hasCarrierPrivileges carrier privileges} + */ + @androidx.annotation.RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, "carrier privileges"}) + public void test6() { throw new RuntimeException("Stub!"); } + /** + * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or "carier priviliges" + */ + @androidx.annotation.RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, "carier priviliges"}) + public void test6() { throw new RuntimeException("Stub!"); } } """ )