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!"); }
                 }
                 """
             )