[automerger skipped] Merge branch 'upstream-google' into rng_import18 am: a99b56cbdb am: 95ace1ade5 -s ours

am skip reason: Merged-In Ife59e70205fa3b8dd3aa4cdef4689a8fd276073c with SHA-1 6660b73edd is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/external/robolectric/+/23691011

Change-Id: I231285f45ab06c9772f2ca386dbf9365938843f4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.github/workflows/gradle_tasks_validation.yml b/.github/workflows/gradle_tasks_validation.yml
index 96b4b33..9762b9e 100644
--- a/.github/workflows/gradle_tasks_validation.yml
+++ b/.github/workflows/gradle_tasks_validation.yml
@@ -15,6 +15,23 @@
   contents: read
 
 jobs:
+  run_checkForApiChanges:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Set up JDK
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: 11
+
+      - uses: gradle/gradle-build-action@v2
+
+      - name: Run checkForApiChanges
+        run: ./gradlew checkForApiChanges
+
   run_aggregateDocs:
     runs-on: ubuntu-20.04
 
@@ -30,7 +47,7 @@
       - uses: gradle/gradle-build-action@v2
 
       - name: Run aggregateDocs
-        run: SKIP_NATIVERUNTIME_BUILD=true ./gradlew clean aggregateDocs # building the native runtime is not required for checking javadoc
+        run: ./gradlew clean aggregateDocs
 
   run_instrumentAll:
     runs-on: ubuntu-20.04
@@ -50,7 +67,7 @@
       - uses: gradle/gradle-build-action@v2
 
       - name: Run :preinstrumented:instrumentAll
-        run: SKIP_NATIVERUNTIME_BUILD=true ./gradlew :preinstrumented:instrumentAll
+        run: ./gradlew :preinstrumented:instrumentAll
 
       - name: Run :preinstrumented:instrumentAll with SDK 33
-        run: SKIP_NATIVERUNTIME_BUILD=true PREINSTRUMENTED_SDK_VERSIONS=33 ./gradlew :preinstrumented:instrumentAll
+        run: PREINSTRUMENTED_SDK_VERSIONS=33 ./gradlew :preinstrumented:instrumentAll
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index d83e804..b47f375 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -101,7 +101,7 @@
         run: |
           TARGET="google_apis"
           echo "TARGET=$TARGET" >> $GITHUB_OUTPUT
-          
+
       - name: AVD cache
         uses: actions/cache@v3
         id: avd-cache
@@ -147,3 +147,25 @@
           path: |
             **/build/reports/*
             **/build/outputs/*/connected/*
+
+  publish-to-snapshots:
+    runs-on: ubuntu-20.04
+    env:
+      SONATYPE_LOGIN: ${{ secrets.SONATYPE_LOGIN }}
+      SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
+    needs: unit-tests
+    if: github.ref == 'refs/heads/master'
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Set up JDK 11
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: 11
+
+      - uses: gradle/gradle-build-action@v2
+
+      - name: Publish
+        run: |
+          ./gradlew publish --stacktrace --no-watch-fs
diff --git a/.gitmodules b/.gitmodules
index bbbdc09..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,10 +0,0 @@
-[submodule "nativeruntime/external/sqlite"]
-	path = nativeruntime/external/sqlite
-	url = https://android.googlesource.com/platform/external/sqlite
-	branch = android11-release
-
-[submodule "nativeruntime/external/icu"]
-	path = nativeruntime/external/icu
-	url = https://github.com/unicode-org/icu
-	branch = release-69-1
-	shallow = false
diff --git a/Android.bp b/Android.bp
index 319b5e3..135e7c1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13,7 +13,10 @@
 // limitations under the License.
 
 package {
-    default_visibility: [":__subpackages__"],
+    default_visibility: [
+        "//external/robolectric:__subpackages__",
+        "//test/robolectric-extensions:__subpackages__",
+    ],
     default_applicable_licenses: ["external_robolectric_license"],
 }
 
@@ -33,7 +36,10 @@
 // See: http://go/android-license-faq
 license {
     name: "external_robolectric_license",
-    visibility: [":__subpackages__"],
+    visibility: [
+      ":__subpackages__",
+      "//test/robolectric-extensions:__subpackages__",
+    ],
     license_kinds: [
         "SPDX-license-identifier-Apache-2.0",
         "SPDX-license-identifier-MIT",
@@ -111,6 +117,7 @@
     visibility: [
         ":__subpackages__",
         "//prebuilts/misc/common/robolectric",
+        "//test/robolectric-extensions:__subpackages__",
     ],
 }
 
@@ -119,6 +126,12 @@
 //#############################################
 
 // This is a hack and should be removed with proper resource merging a la maven-shaded-plugin
+//
+// In order to use AndroidXTest APIs in Robolectric (e.g. ActivityScenario), it
+// is necessary to define the service metadata at the top-level. The classes
+// themselves (e.g. LocalUiController) do not use `@AutoService` at the moment.
+// When they are migrated to `@AutoService` the AndroidXTest service metadata
+// can be removed.
 java_genrule_host {
     name: "robolectric_meta_service_file",
     out: ["robolectric_meta_service_file.jar"],
@@ -126,6 +139,16 @@
     cmd: "mkdir -p $(genDir)/META-INF/services/ && " +
         "echo -e 'org.robolectric.Shadows\norg.robolectric.shadows.httpclient.Shadows\norg.robolectric.shadows.multidex.Shadows' > " +
         "$(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
+        "echo org.robolectric.android.internal.LocalUiController > " +
+        "$(genDir)/META-INF/services/androidx.test.platform.ui.UiController &&" +
+        "echo org.robolectric.android.internal.LocalActivityInvoker > " +
+        "$(genDir)/META-INF/services/androidx.test.internal.platform.app.ActivityInvoker &&" +
+        "echo org.robolectric.android.internal.LocalPermissionGranter  > " +
+        "$(genDir)/META-INF/services/androidx.test.internal.platform.content.PermissionGranter &&" +
+        "echo org.robolectric.android.internal.NoOpThreadChecker > " +
+        "$(genDir)/META-INF/services/androidx.test.internal.platform.ThreadChecker &&" +
+        "echo org.robolectric.android.internal.LocalControlledLooper > " +
+        "$(genDir)/META-INF/services/androidx.test.internal.platform.os.ControlledLooper &&" +
         "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/",
 }
 
@@ -133,10 +156,12 @@
     name: "Robolectric_all_upstream",
 
     static_libs: [
+        "Robolectric-aosp-plugins",
         "robolectric_meta_service_file",
         "Robolectric_shadows_httpclient_upstream",
         "Robolectric_shadows_framework_upstream",
         "Robolectric_shadows_multidex_upstream",
+        "Robolectric_shadows_versioning_upstream",
         "Robolectric_robolectric_upstream",
         "Robolectric_annotations_upstream",
         "Robolectric_resources_upstream",
@@ -178,6 +203,7 @@
       "//prebuilts/sdk/current/aaos-libs:__pkg__",
       "//packages/apps/TV/tests/common:__pkg__",
       //robolectric tests
+      "//platform_testing/libraries/runner:__pkg__",
       "//vendor:__subpackages__",
       "//platform_testing/robolab/roboStandaloneProj/tests:__pkg__",
       "//external/mobile-data-download/javatests:__pkg__",
diff --git a/README.md b/README.md
index 6b91ba3..b183647 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@
 
 ```groovy
 testImplementation "junit:junit:4.13.2"
-testImplementation "org.robolectric:robolectric:4.10-alpha-1"
+testImplementation "org.robolectric:robolectric:4.10.3"
 ```
 
 ## Building And Contributing
@@ -79,18 +79,3 @@
 
     ./gradlew connectedCheck
 
-### Using Snapshots
-
-If you would like to live on the bleeding edge, you can try running against a snapshot build. Keep in mind that snapshots represent the most recent changes on master and may contain bugs.
-
-#### build.gradle:
-
-```groovy
-repositories {
-    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
-}
-
-dependencies {
-    testImplementation "org.robolectric:robolectric:4.10-SNAPSHOT"
-}
-```
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 65b4f06..d8bd113 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -5,6 +5,6 @@
 apply plugin: DeployedRoboJavaModulePlugin
 
 dependencies {
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+    compileOnly libs.findbugs.jsr305
     compileOnly AndroidSdk.MAX_SDK.coordinates
 }
diff --git a/build.gradle b/build.gradle
index 1eb662b..f3ab387 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,18 +7,15 @@
         google()
         mavenCentral()
         gradlePluginPortal()
-        maven {
-            url "https://plugins.gradle.org/m2/"
-        }
     }
 
     dependencies {
         gradle
-        classpath 'com.android.tools.build:gradle:7.4.2'
-        classpath 'net.ltgt.gradle:gradle-errorprone-plugin:3.0.1'
-        classpath 'com.netflix.nebula:gradle-aggregate-javadocs-plugin:3.0.1'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
-        classpath "com.diffplug.spotless:spotless-plugin-gradle:6.17.0"
+        classpath libs.android.gradle
+        classpath libs.error.prone.gradle
+        classpath libs.aggregate.javadocs.gradle
+        classpath libs.kotlin.gradle
+        classpath libs.spotless.gradle
     }
 }
 
@@ -120,7 +117,7 @@
     dependsOn ':aggregateJsondocs'
 }
 
-task prefetchSdks() {
+tasks.register('prefetchSdks') {
     AndroidSdk.ALL_SDKS.each { androidSdk ->
         doLast {
             println("Prefetching ${androidSdk.coordinates}...")
@@ -139,7 +136,7 @@
     }
 }
 
-task prefetchInstrumentedSdks() {
+tasks.register('prefetchInstrumentedSdks') {
     AndroidSdk.ALL_SDKS.each { androidSdk ->
         doLast {
             println("Prefetching ${androidSdk.preinstrumentedCoordinates}...")
@@ -169,7 +166,7 @@
     if (process.exitValue() != 0) System.exit(1)
 }
 
-task prefetchDependencies() {
+tasks.register('prefetchDependencies') {
     doLast {
         allprojects.each { p ->
             p.configurations.each { config ->
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 6123663..67279ef 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -11,8 +11,8 @@
     implementation gradleApi()
     implementation localGroovy()
 
-    api "com.google.guava:guava:31.1-jre"
-    api 'org.jetbrains:annotations:24.0.1'
-    implementation "org.ow2.asm:asm-tree:9.4"
-    implementation 'com.android.tools.build:gradle:7.4.2'
+    api libs.guava
+    api libs.jetbrains.annotations
+    implementation libs.asm.tree
+    implementation libs.android.gradle
 }
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
new file mode 100644
index 0000000..6f31e6e
--- /dev/null
+++ b/buildSrc/settings.gradle
@@ -0,0 +1,7 @@
+dependencyResolutionManagement {
+    versionCatalogs {
+        libs {
+            from(files("../gradle/libs.versions.toml"))
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy b/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy
index c0671c5..2f1476c 100644
--- a/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy
+++ b/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy
@@ -28,7 +28,6 @@
             project.checkApiChanges.from.each {
                 project.dependencies.checkApiChangesFrom(it) {
                     transitive = false
-                    force = true
                 }
             }
 
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/DeployedRoboJavaModulePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/DeployedRoboJavaModulePlugin.groovy
index 324d04d..09e3b8d 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/DeployedRoboJavaModulePlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/DeployedRoboJavaModulePlugin.groovy
@@ -94,8 +94,8 @@
                     url = project.version.endsWith("-SNAPSHOT") ? snapshotsRepoUrl : releasesRepoUrl
 
                     credentials {
-                        username = System.properties["sonatype-login"] ?: System.env['sonatypeLogin']
-                        password = System.properties["sonatype-password"] ?: System.env['sonatypePassword']
+                        username = System.properties["sonatype-login"] ?: System.env['SONATYPE_LOGIN']
+                        password = System.properties["sonatype-password"] ?: System.env['SONATYPE_PASSWORD']
                     }
                 }
             }
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
index 6c0e058..deb97c9 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
@@ -13,8 +13,8 @@
         if (!skipErrorprone) {
           apply plugin: "net.ltgt.errorprone"
           project.dependencies {
-            errorprone("com.google.errorprone:error_prone_core:$errorproneVersion")
-            errorproneJavac("com.google.errorprone:javac:$errorproneJavacVersion")
+            errorprone(libs.error.prone.core)
+            errorproneJavac(libs.error.prone.javac)
           }
         }
 
diff --git a/dependencies.gradle b/dependencies.gradle
index b890dc1..204e3d3 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,40 +1,11 @@
 ext {
-    apiCompatVersion='4.9.2'
+    apiCompatVersion = libs.versions.robolectric.compat.get()
 
-    errorproneVersion='2.18.0'
-    errorproneJavacVersion='9+181-r4173-1'
-
-    // AndroidX test versions
-    axtMonitorVersion='1.6.1'
-    axtRunnerVersion='1.5.2'
-    axtRulesVersion='1.5.0'
-    axtCoreVersion='1.5.0'
-    axtTruthVersion='1.5.0'
-    espressoVersion='3.5.1'
-    axtJunitVersion='1.1.4'
-    axtTestServicesVersion='1.4.2'
-
-    // AndroidX versions
-    coreVersion='1.9.0'
-    appCompatVersion='1.6.1'
-    constraintlayoutVersion='2.1.4'
-    windowVersion='1.0.0'
-    fragmentVersion='1.5.5'
-
-    truthVersion='1.1.3'
-
-    junitVersion='4.13.2'
-
-    mockitoVersion='4.11.0'
-
-    jacocoVersion='0.8.8'
-
-    guavaJREVersion='31.1-jre'
-
-    asmVersion='9.4'
-
-    kotlinVersion='1.8.10'
-    autoServiceVersion='1.0.1'
-    multidexVersion='2.0.1'
-    sqlite4javaVersion='1.0.392'
+    // https://github.com/gradle/gradle/issues/21267
+    axtCoreVersion = libs.versions.androidx.test.core.get()
+    axtJunitVersion = libs.versions.androidx.test.ext.junit.get()
+    axtMonitorVersion = libs.versions.androidx.test.monitor.get()
+    axtRunnerVersion = libs.versions.androidx.test.runner.get()
+    axtTruthVersion = libs.versions.androidx.test.ext.truth.get()
+    espressoVersion = libs.versions.androidx.test.espresso.get()
 }
diff --git a/errorprone/build.gradle b/errorprone/build.gradle
index 1932066..5fc5616 100644
--- a/errorprone/build.gradle
+++ b/errorprone/build.gradle
@@ -20,14 +20,14 @@
     implementation project(":shadowapi")
 
     // Compile dependencies
-    implementation "com.google.errorprone:error_prone_annotation:$errorproneVersion"
-    implementation "com.google.errorprone:error_prone_refaster:$errorproneVersion"
-    implementation "com.google.errorprone:error_prone_check_api:$errorproneVersion"
-    compileOnly "com.google.auto.service:auto-service-annotations:$autoServiceVersion"
-    compileOnly(AndroidSdk.MAX_SDK.coordinates) { force = true }
+    implementation libs.error.prone.annotations
+    implementation libs.error.prone.refaster
+    implementation libs.error.prone.check.api
+    compileOnly libs.auto.service.annotations
+    compileOnly(AndroidSdk.MAX_SDK.coordinates)
 
-    annotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
-    annotationProcessor "com.google.errorprone:error_prone_core:$errorproneVersion"
+    annotationProcessor libs.auto.service
+    annotationProcessor libs.error.prone.core
 
     // in jdk 9, tools.jar disappears!
     def toolsJar = Jvm.current().getToolsJar()
@@ -36,10 +36,10 @@
     }
 
     // Testing dependencies
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation("com.google.errorprone:error_prone_test_helpers:${errorproneVersion}") {
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation(libs.error.prone.test.helpers) {
         exclude group: 'junit', module: 'junit' // because it depends on a snapshot!?
     }
-    testCompileOnly(AndroidSdk.MAX_SDK.coordinates) { force = true }
+    testCompileOnly(AndroidSdk.MAX_SDK.coordinates)
 }
diff --git a/gradle.properties b/gradle.properties
index dc9eb66..d97ed21 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
-thisVersion=4.10-SNAPSHOT
+thisVersion=4.11-SNAPSHOT
 android.useAndroidX=true
 kotlin.stdlib.default.dependency=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..7b43dfa
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,237 @@
+[versions]
+robolectric-compat = "4.10.2"
+robolectric-nativeruntime-dist-compat = "1.0.1"
+
+# https://developer.android.com/studio/releases
+android-gradle = "7.4.2"
+
+# https://github.com/google/conscrypt/tags
+conscrypt = "2.5.2"
+
+# https://github.com/bcgit/bc-java/tags
+bouncycastle = "1.73"
+
+# https://github.com/findbugsproject/findbugs/tags
+findbugs-jsr305 = "3.0.2"
+
+# https://github.com/hamcrest/JavaHamcrest/releases
+hamcrest = "2.0.0.0"
+
+# https://github.com/nebula-plugins/gradle-aggregate-javadocs-plugin/releases
+aggregate-javadocs-gradle = "3.0.1"
+
+# https://github.com/google/error-prone/releases
+error-prone = "2.19.1"
+error-prone-javac = "9+181-r4173-1"
+
+# https://github.com/tbroyer/gradle-errorprone-plugin/releases
+error-prone-gradle = "3.1.0"
+
+# https://kotlinlang.org/docs/releases.html#release-details
+kotlin = "1.8.10"
+
+# https://github.com/diffplug/spotless/blob/main/CHANGES.md
+spotless-gradle = "6.18.0"
+
+# https://hc.apache.org/news.html
+apache-http-core = "4.0.1"
+apache-http-client = "4.0.3"
+
+# https://asm.ow2.io/versions.html
+asm = "9.5"
+
+# https://github.com/google/auto/releases
+auto-common = "1.2.1"
+auto-service = "1.0.1"
+auto-value = "1.10.1"
+
+compile-testing = "0.21.0"
+
+# https://github.com/google/guava/releases
+guava-jre = "31.1-jre"
+
+# https://github.com/google/gson/releases
+gson = "2.10.1"
+
+# https://github.com/google/truth/releases
+truth = "1.1.3"
+
+# https://github.com/unicode-org/icu/releases
+icu4j = "73.1"
+
+jacoco = "0.8.10"
+
+# https://github.com/javaee/javax.annotation/tags
+javax-annotation-api = "1.3.2"
+javax-annotation-jsr250-api = "1.0"
+javax-inject = "1"
+
+# https://github.com/JetBrains/java-annotations/releases
+jetbrains-annotations = "24.0.1"
+
+# https://junit.org/junit4/
+junit4 = "4.13.2"
+
+# https://github.com/google/libphonenumber/releases
+libphonenumber = "8.13.11"
+
+# https://github.com/mockito/mockito/releases
+mockito = "4.11.0"
+
+# https://github.com/mockk/mockk/releases
+mockk = "1.13.5"
+
+# https://square.github.io/okhttp/changelogs/changelog/
+okhttp = "4.11.0"
+
+# https://github.com/powermock/powermock/releases
+powermock = "2.0.9"
+
+sqlite4java = "1.0.392"
+
+# https://developer.android.com/jetpack/androidx/versions
+androidx-annotation = "1.3.0"
+androidx-appcompat = "1.6.1"
+androidx-constraintlayout = "2.1.4"
+androidx-core = "1.10.1"
+androidx-fragment = "1.5.7"
+androidx-multidex = "2.0.1"
+androidx-window = "1.0.0"
+
+# https://github.com/android/android-test/tags
+androidx-test-annotation = "1.0.1"
+androidx-test-core = "1.5.0"
+androidx-test-espresso = "3.5.1"
+androidx-test-ext-junit = "1.1.5"
+androidx-test-ext-truth = "1.5.0"
+androidx-test-monitor="1.6.1"
+androidx-test-orchestrator="1.4.2"
+androidx-test-runner = "1.5.2"
+androidx-test-services = "1.4.2"
+
+# for shadows/playservices/build.gradle
+androidx-fragment-for-shadows = "1.2.0"
+play-services-base-for-shadows = "8.4.0"
+
+[libraries]
+android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
+kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless-gradle" }
+
+kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
+
+auto-common = { module = "com.google.auto:auto-common", version.ref = "auto-common" }
+auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "auto-service" }
+auto-service = { module = "com.google.auto.service:auto-service", version.ref = "auto-service" }
+auto-value-annotations = { module = "com.google.auto.value:auto-value-annotations", version.ref = "auto-value" }
+auto-value = { module = "com.google.auto.value:auto-value", version.ref = "auto-value" }
+
+apache-http-core = { module = "org.apache.httpcomponents:httpcore", version.ref = "apache-http-core" }
+apache-http-client = { module = "org.apache.httpcomponents:httpclient", version.ref = "apache-http-client" }
+
+asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
+asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
+asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" }
+asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" }
+
+compile-testing = { module = "com.google.testing.compile:compile-testing", version.ref = "compile-testing" }
+
+aggregate-javadocs-gradle = { module = "com.netflix.nebula:gradle-aggregate-javadocs-plugin", version.ref = "aggregate-javadocs-gradle" }
+
+error-prone-core =  { module = "com.google.errorprone:error_prone_core", version.ref = "error-prone" }
+error-prone-annotations = { module = "com.google.errorprone:error_prone_annotation", version.ref = "error-prone" }
+error-prone-refaster= { module = "com.google.errorprone:error_prone_refaster", version.ref = "error-prone" }
+error-prone-check-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "error-prone" }
+error-prone-test-helpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "error-prone" }
+error-prone-javac =  { module = "com.google.errorprone:javac", version.ref = "error-prone-javac" }
+
+error-prone-gradle = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "error-prone-gradle" }
+
+conscrypt-openjdk-uber = { module = "org.conscrypt:conscrypt-openjdk-uber", version.ref = "conscrypt" }
+bcprov-jdk18on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncycastle" }
+findbugs-jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "findbugs-jsr305" }
+
+guava = { module = "com.google.guava:guava", version.ref = "guava-jre" }
+guava-testlib = { module = "com.google.guava:guava-testlib", version.ref = "guava-jre" }
+gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
+hamcrest-junit = { module = "org.hamcrest:hamcrest-junit", version.ref = "hamcrest" }
+
+icu4j = { module = "com.ibm.icu:icu4j", version.ref = "icu4j" }
+
+jacoco-agent = { module = "org.jacoco:org.jacoco.agent", version.ref = "jacoco" }
+junit4 = { module = "junit:junit", version.ref = "junit4" }
+
+javax-annotation-api = { module = "javax.annotation:javax.annotation-api", version.ref = "javax-annotation-api" }
+javax-annotation-jsr250-api = { module = "javax.annotation:jsr250-api", version.ref = "javax-annotation-jsr250-api" }
+javax-inject = { module = "javax.inject:javax.inject", version.ref = "javax.inject" }
+
+jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" }
+
+libphonenumber = { module = "com.googlecode.libphonenumber:libphonenumber", version.ref = "libphonenumber" }
+
+okhttp = { module = "com.squareup.okhttp3:okhttp" }
+okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" }
+
+powermock-module-junit4 = { module = "org.powermock:powermock-module-junit4", version.ref = "powermock" }
+powermock-module-junit4-rule = { module = "org.powermock:powermock-module-junit4-rule", version.ref = "powermock" }
+powermock-api-mockito2 = { module = "org.powermock:powermock-api-mockito2", version.ref = "powermock" }
+powermock-classloading-xstream = { module = "org.powermock:powermock-classloading-xstream", version.ref = "powermock" }
+
+robolectric-nativeruntime-dist-compat = { module = "org.robolectric:nativeruntime-dist-compat", version.ref = "robolectric-nativeruntime-dist-compat" }
+
+sqlite4java = { module = "com.almworks.sqlite4java:sqlite4java", version.ref = "sqlite4java" }
+sqlite4java-osx = { module = "com.almworks.sqlite4java:libsqlite4java-osx", version.ref = "sqlite4java" }
+sqlite4java-linux-amd64 = { module = "com.almworks.sqlite4java:libsqlite4java-linux-amd64", version.ref = "sqlite4java" }
+sqlite4java-win32-x64 = { module = "com.almworks.sqlite4java:sqlite4java-win32-x64", version.ref = "sqlite4java" }
+sqlite4java-linux-i386 = { module = "com.almworks.sqlite4java:libsqlite4java-linux-i386", version.ref = "sqlite4java" }
+sqlite4java-win32-x86 = { module = "com.almworks.sqlite4java:sqlite4java-win32-x86", version.ref = "sqlite4java" }
+
+truth = { module = "com.google.truth:truth", version.ref = "truth" }
+truth-java8-extension = { module = "com.google.truth.extensions:truth-java8-extension", version.ref = "truth" }
+
+mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
+mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" }
+mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
+
+androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
+androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
+androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
+androidx-core = { module = "androidx.core:core", version.ref = "androidx-core" }
+androidx-fragment = { module = "androidx.fragment:fragment", version.ref = "androidx-fragment" }
+androidx-fragment-testing = { module = "androidx.fragment:fragment-testing", version.ref = "androidx-fragment" }
+androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "androidx-multidex" }
+androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" }
+
+androidx-test-annotation = { module = "androidx.test:annotation", version.ref = "androidx-test-annotation" }
+androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" }
+androidx-test-monitor = { module = "androidx.test:monitor", version.ref = "androidx-test-monitor" }
+androidx-test-orchestrator = { module = "androidx.test:orchestrator", version.ref = "androidx-test-orchestrator" }
+androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-core" }
+androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
+androidx-test-services = { module = "androidx.test.services:test-services", version.ref = "androidx-test-services" }
+androidx-test-services-storage = { module = "androidx.test.services:storage", version.ref = "androidx-test-services" }
+
+androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-accessibility = { module = "androidx.test.espresso:espresso-accessibility", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-remote = { module = "androidx.test.espresso:espresso-remote", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-web = { module = "androidx.test.espresso:espresso-web", version.ref = "androidx-test-espresso" }
+
+androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-idling-concurrent = { module = "androidx.test.espresso.idling:idling-concurrent", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-idling-net = { module = "androidx.test.espresso.idling:idling-net", version.ref = "androidx-test-espresso" }
+
+androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" }
+androidx-test-ext-truth = { module = "androidx.test.ext:truth", version.ref = "androidx-test-ext-truth" }
+
+androidx-fragment-for-shadows = { module = "androidx.fragment:fragment", version.ref = "androidx-fragment-for-shadows" }
+play-services-base-for-shadows = { module = "com.google.android.gms:play-services-base", version.ref = "play-services-base-for-shadows" }
+play-services-basement-for-shadows = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-base-for-shadows" }
+
+[bundles]
+play-services-base-for-shadows = [ "androidx-fragment-for-shadows", "play-services-base-for-shadows", "play-services-basement-for-shadows" ]
+powermock = [ "powermock-module-junit4", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream" ]
+sqlite4java-native = [ "sqlite4java-osx", "sqlite4java-linux-amd64", "sqlite4java-win32-x64", "sqlite4java-linux-i386", "sqlite4java-win32-x86" ]
+
+[plugins]
diff --git a/integration_tests/agp/build.gradle b/integration_tests/agp/build.gradle
index a079d1e..55d9051 100644
--- a/integration_tests/agp/build.gradle
+++ b/integration_tests/agp/build.gradle
@@ -5,6 +5,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.integrationtests.agp'
 
     defaultConfig {
         minSdk 16
@@ -25,8 +26,8 @@
     testImplementation project(":robolectric")
     testImplementation project(":integration_tests:agp:testsupport")
 
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation("androidx.test:core:$axtCoreVersion")
-    testImplementation("androidx.test:runner:$axtRunnerVersion")
-    testImplementation("androidx.test.ext:junit:$axtJunitVersion")
+    testImplementation libs.junit4
+    testImplementation libs.androidx.test.core
+    testImplementation libs.androidx.test.runner
+    testImplementation libs.androidx.test.ext.junit
 }
diff --git a/integration_tests/agp/testsupport/build.gradle b/integration_tests/agp/testsupport/build.gradle
index dcec3d4..e87274f 100644
--- a/integration_tests/agp/testsupport/build.gradle
+++ b/integration_tests/agp/testsupport/build.gradle
@@ -2,6 +2,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.integrationtests.agp.testsupport'
 
     defaultConfig {
         minSdk 16
diff --git a/integration_tests/androidx/build.gradle b/integration_tests/androidx/build.gradle
index 96535e1..10cc8c6 100644
--- a/integration_tests/androidx/build.gradle
+++ b/integration_tests/androidx/build.gradle
@@ -5,6 +5,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.integrationtests.androidx'
 
     defaultConfig {
         minSdk 16
@@ -25,19 +26,19 @@
 }
 
 dependencies {
-    implementation("androidx.appcompat:appcompat:$appCompatVersion")
-    implementation("androidx.window:window:$windowVersion")
+    implementation libs.androidx.appcompat
+    implementation libs.androidx.window
 
     // Testing dependencies
     testImplementation project(path: ':testapp')
     testImplementation project(":robolectric")
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation("androidx.test:core:$axtCoreVersion")
-    testImplementation("androidx.core:core:$coreVersion")
-    testImplementation("androidx.test:runner:$axtRunnerVersion")
-    testImplementation("androidx.test:rules:$axtRulesVersion")
-    testImplementation("androidx.test.espresso:espresso-intents:$espressoVersion")
-    testImplementation("androidx.test.ext:truth:$axtTruthVersion")
-    testImplementation("androidx.test.ext:junit:$axtJunitVersion")
-    testImplementation("com.google.truth:truth:$truthVersion")
+    testImplementation libs.junit4
+    testImplementation libs.androidx.test.core
+    testImplementation libs.androidx.core
+    testImplementation libs.androidx.test.runner
+    testImplementation libs.androidx.test.rules
+    testImplementation libs.androidx.test.espresso.intents
+    testImplementation libs.androidx.test.ext.truth
+    testImplementation libs.androidx.test.ext.junit
+    testImplementation libs.truth
 }
diff --git a/integration_tests/androidx_test/build.gradle b/integration_tests/androidx_test/build.gradle
index 7f6f621..d07ef2e 100644
--- a/integration_tests/androidx_test/build.gradle
+++ b/integration_tests/androidx_test/build.gradle
@@ -7,6 +7,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.integration.axt'
 
     defaultConfig {
         minSdk 16
@@ -41,33 +42,33 @@
 }
 
 dependencies {
-    implementation "androidx.appcompat:appcompat:$appCompatVersion"
-    implementation "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion"
-    implementation "androidx.multidex:multidex:$multidexVersion"
+    implementation libs.androidx.appcompat
+    implementation libs.androidx.constraintlayout
+    implementation libs.androidx.multidex
 
     // Testing dependencies
     testImplementation project(":robolectric")
-    testImplementation "androidx.test:runner:$axtRunnerVersion"
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "androidx.test:rules:$axtRulesVersion"
-    testImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
-    testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
-    testImplementation "androidx.test.ext:truth:$axtTruthVersion"
-    testImplementation "androidx.test:core:$axtCoreVersion"
-    testImplementation "androidx.fragment:fragment:$fragmentVersion"
-    testImplementation "androidx.fragment:fragment-testing:$fragmentVersion"
-    testImplementation "androidx.test.ext:junit:$axtJunitVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
+    testImplementation libs.androidx.test.runner
+    testImplementation libs.junit4
+    testImplementation libs.androidx.test.rules
+    testImplementation libs.androidx.test.espresso.intents
+    testImplementation libs.androidx.test.espresso.core
+    testImplementation libs.androidx.test.ext.truth
+    testImplementation libs.androidx.test.core
+    testImplementation libs.androidx.fragment
+    testImplementation libs.androidx.fragment.testing
+    testImplementation libs.androidx.test.ext.junit
+    testImplementation libs.truth
 
     androidTestImplementation project(':annotations')
-    androidTestImplementation "androidx.test:runner:$axtRunnerVersion"
-    androidTestImplementation "junit:junit:$junitVersion"
-    androidTestImplementation "androidx.test:rules:$axtRulesVersion"
-    androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
-    androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
-    androidTestImplementation "androidx.test.ext:truth:$axtTruthVersion"
-    androidTestImplementation "androidx.test:core:$axtCoreVersion"
-    androidTestImplementation "androidx.test.ext:junit:$axtJunitVersion"
-    androidTestImplementation "com.google.truth:truth:$truthVersion"
-    androidTestUtil "androidx.test.services:test-services:$axtTestServicesVersion"
+    androidTestImplementation libs.androidx.test.runner
+    androidTestImplementation libs.junit4
+    androidTestImplementation libs.androidx.test.rules
+    androidTestImplementation libs.androidx.test.espresso.intents
+    androidTestImplementation libs.androidx.test.espresso.core
+    androidTestImplementation libs.androidx.test.ext.truth
+    androidTestImplementation libs.androidx.test.core
+    androidTestImplementation libs.androidx.test.ext.junit
+    androidTestImplementation libs.truth
+    androidTestUtil libs.androidx.test.services
 }
diff --git a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml
index 1cbc197..c412b90 100644
--- a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml
+++ b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml
@@ -50,4 +50,10 @@
         android:layout_height="wrap_content"
         android:inputType="phone" />
 
+    <EditText
+        android:id="@+id/edit_text_number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:inputType="number" />
+
 </LinearLayout>
diff --git a/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java b/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java
index 494ce82..0c0df8b 100644
--- a/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java
+++ b/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java
@@ -25,6 +25,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -129,6 +130,15 @@
     onView(withId(R.id.edit_text_phone)).check(matches(withText("411")));
   }
 
+  /** use typeText with a inputType number */
+  @Test
+  @Ignore // TODO(#5110): fails
+  public void typeText_number() {
+    onView(withId(R.id.edit_text_number)).perform(typeText("411"));
+
+    onView(withId(R.id.edit_text_number)).check(matches(withText("411")));
+  }
+
   @Test
   public void textView() {
     onView(withText("Text View"))
diff --git a/integration_tests/compat-target28/build.gradle b/integration_tests/compat-target28/build.gradle
index 37a8568..1fc7485 100644
--- a/integration_tests/compat-target28/build.gradle
+++ b/integration_tests/compat-target28/build.gradle
@@ -14,6 +14,7 @@
 
 android {
     compileSdk 28
+    namespace 'org.robolectric.integrationtests.compattarget28'
 
     defaultConfig {
         minSdk 16
@@ -30,10 +31,10 @@
 }
 
 dependencies {
-    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+    implementation libs.kotlin.stdlib
 
     testImplementation project(path: ':testapp')
     testImplementation project(":robolectric")
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
+    testImplementation libs.junit4
+    testImplementation libs.truth
 }
diff --git a/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt b/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt
index ee56fc6..69bbf73 100644
--- a/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt
+++ b/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt
@@ -1,7 +1,9 @@
 package org.robolectric.integration.compat.target28
 
 import android.content.Context
+import android.content.Context.VIBRATOR_SERVICE
 import android.os.Build
+import android.os.Vibrator
 import android.speech.SpeechRecognizer
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -47,4 +49,9 @@
   fun `Create speech recognizer succeed`() {
     assertThat(SpeechRecognizer.createSpeechRecognizer(application)).isNotNull()
   }
+
+  @Test
+  fun `Get default Vibrator succeed`() {
+    assertThat(application.getSystemService(VIBRATOR_SERVICE) as Vibrator).isNotNull()
+  }
 }
diff --git a/integration_tests/ctesque/Android.bp b/integration_tests/ctesque/Android.bp
new file mode 100644
index 0000000..f500c35
--- /dev/null
+++ b/integration_tests/ctesque/Android.bp
@@ -0,0 +1,35 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_robolectric_test {
+    name: "CtesqueRoboTests",
+    srcs: [
+        "src/sharedTest/**/*.java",
+        "src/sharedTest/**/*.kt",
+    ],
+    exclude_srcs: [
+        // TODO(b/286353161): Re-enable this test once loading resources from XML
+        // for older SDK versions is resolved.
+        "src/sharedTest/java/android/content/res/ResourcesTest.java",
+        // TODO(b/286353719): Re-enable this test once the correct SQLite is used.
+        "src/sharedTest/java/android/database/SQLiteDatabaseTest.java",
+    ],
+    static_libs: [
+        "androidx.core_core",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.espresso.core",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth-prebuilt",
+        "guava",
+    ],
+    upstream: true,
+    java_resource_dirs: ["src/sharedTest/resources/android"],
+    instrumentation_for: "GlobalRobolectricTestStub",
+}
diff --git a/integration_tests/ctesque/build.gradle b/integration_tests/ctesque/build.gradle
index 11f27e1..3b40c88 100644
--- a/integration_tests/ctesque/build.gradle
+++ b/integration_tests/ctesque/build.gradle
@@ -7,6 +7,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.integrationtests.ctesque'
 
     defaultConfig {
         minSdk 16
@@ -48,24 +49,26 @@
     implementation project(':testapp')
 
     testImplementation project(':robolectric')
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation("androidx.test:monitor:$axtMonitorVersion")
-    testImplementation("androidx.test:runner:$axtRunnerVersion")
-    testImplementation("androidx.test:rules:$axtRulesVersion")
-    testImplementation("androidx.test.ext:junit:$axtJunitVersion")
-    testImplementation("androidx.test.ext:truth:$axtTruthVersion")
-    testImplementation("androidx.test:core:$axtCoreVersion")
-    testImplementation("com.google.truth:truth:${truthVersion}")
-    testImplementation("com.google.guava:guava:$guavaJREVersion")
+    testImplementation libs.junit4
+    testImplementation libs.androidx.test.monitor
+    testImplementation libs.androidx.test.runner
+    testImplementation libs.androidx.test.rules
+    testImplementation libs.androidx.test.ext.junit
+    testImplementation libs.androidx.test.ext.truth
+    testImplementation libs.androidx.test.core
+    testImplementation libs.androidx.test.espresso.core
+    testImplementation libs.truth
+    testImplementation libs.guava
 
     // Testing dependencies
     androidTestImplementation project(':shadowapi')
-    androidTestImplementation("androidx.test:monitor:$axtMonitorVersion")
-    androidTestImplementation("androidx.test:runner:$axtRunnerVersion")
-    androidTestImplementation("androidx.test:rules:$axtRulesVersion")
-    androidTestImplementation("androidx.test.ext:junit:$axtJunitVersion")
-    androidTestImplementation("androidx.test.ext:truth:$axtTruthVersion")
-    androidTestImplementation("com.google.truth:truth:${truthVersion}")
-    androidTestImplementation("com.google.guava:guava:$guavaJREVersion")
-    androidTestUtil "androidx.test.services:test-services:$axtTestServicesVersion"
+    androidTestImplementation libs.androidx.test.monitor
+    androidTestImplementation libs.androidx.test.runner
+    androidTestImplementation libs.androidx.test.rules
+    androidTestImplementation libs.androidx.test.ext.junit
+    androidTestImplementation libs.androidx.test.ext.truth
+    androidTestImplementation libs.androidx.test.espresso.core
+    androidTestImplementation libs.truth
+    androidTestImplementation libs.guava
+    androidTestUtil libs.androidx.test.services
 }
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/animation/AnimationTest.java b/integration_tests/ctesque/src/sharedTest/java/android/animation/AnimationTest.java
new file mode 100644
index 0000000..9e65318
--- /dev/null
+++ b/integration_tests/ctesque/src/sharedTest/java/android/animation/AnimationTest.java
@@ -0,0 +1,74 @@
+package android.animation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.animation.Animator.AnimatorListener;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.espresso.Espresso;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.lang.reflect.Modifier;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.testapp.ActivityWithoutTheme;
+
+/** Compatibility test for animation-related logic. */
+@DoNotInstrument
+@RunWith(AndroidJUnit4.class)
+public final class AnimationTest {
+
+  @Test
+  public void propertyValuesHolder() throws Exception {
+    PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat("x", 100f, 150f);
+    PropertyBag object = new PropertyBag();
+    assertThat(Modifier.isPrivate(PropertyBag.class.getModifiers())).isTrue();
+    ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(object, pVHolder);
+    objAnimator.setDuration(500);
+    AnimatorSet animatorSet = new AnimatorSet();
+    animatorSet.play(objAnimator);
+
+    CountDownLatch countDownLatch = new CountDownLatch(1);
+    objAnimator.addListener(
+        new AnimatorListener() {
+          @Override
+          public void onAnimationEnd(Animator a) {
+            countDownLatch.countDown();
+          }
+
+          @Override
+          public void onAnimationStart(Animator a) {}
+
+          @Override
+          public void onAnimationCancel(Animator a) {}
+
+          @Override
+          public void onAnimationRepeat(Animator a) {}
+        });
+
+    // In Android animations can only run on Looper threads.
+    try (ActivityScenario<ActivityWithoutTheme> scenario =
+        ActivityScenario.launch(ActivityWithoutTheme.class)) {
+      scenario.onActivity(
+          activity -> {
+            animatorSet.start();
+          });
+    }
+
+    Espresso.onIdle();
+    countDownLatch.await(5, TimeUnit.SECONDS);
+    assertThat(object.x).isEqualTo(150f);
+  }
+
+  /** Private class with a public member. */
+  @DoNotInstrument
+  @SuppressWarnings("unused")
+  private static class PropertyBag {
+    public float x;
+
+    public void setX(float x) {
+      this.x = x;
+    }
+  }
+}
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java b/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java
index c1922b6..5e4de01 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java
@@ -503,6 +503,14 @@
     assertThat(id).isEqualTo(0);
   }
 
+  @Test
+  @SdkSuppress(minSdkVersion = LOLLIPOP)
+  @Config(minSdk = LOLLIPOP)
+  public void getIdentifier_material() {
+    int id = Resources.getSystem().getIdentifier("btn_check_material_anim", "drawable", "android");
+    assertThat(id).isGreaterThan(0);
+  }
+
   /**
    * Public framework symbols are defined here:
    * https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/public.xml
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java b/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
index 319b873..75c3288 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
@@ -10,8 +10,8 @@
 import static android.os.Build.VERSION_CODES.Q;
 import static androidx.test.InstrumentationRegistry.getTargetContext;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeFalse;
 
 import android.content.res.Resources;
 import android.graphics.Bitmap.CompressFormat;
@@ -51,7 +51,7 @@
   @Config(minSdk = P)
   @SdkSuppress(minSdkVersion = P)
   @Test public void createBitmap() {
-    assumeFalse(Boolean.getBoolean("robolectric.nativeruntime.enableGraphics"));
+    assume().that(System.getProperty("robolectric.graphicsMode")).isNotEqualTo("NATIVE");
     // Bitmap.createBitmap(Picture) requires hardware-backed bitmaps
     HardwareRendererCompat.setDrawingEnabled(true);
     Picture picture = new Picture();
diff --git a/integration_tests/dependency-on-stubs/build.gradle b/integration_tests/dependency-on-stubs/build.gradle
index 6efe513..683de18 100644
--- a/integration_tests/dependency-on-stubs/build.gradle
+++ b/integration_tests/dependency-on-stubs/build.gradle
@@ -6,13 +6,13 @@
 
 dependencies {
     api project(":robolectric")
-    api "junit:junit:${junitVersion}"
+    api libs.junit4
 
     testImplementation files("${System.getenv("ANDROID_HOME")}/platforms/android-29/android.jar")
 
     testCompileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
-    testImplementation "org.hamcrest:hamcrest-junit:2.0.0.0"
+    testImplementation libs.truth
+    testImplementation libs.mockito
+    testImplementation libs.hamcrest.junit
 }
diff --git a/integration_tests/jacoco-offline/build.gradle b/integration_tests/jacoco-offline/build.gradle
index e5d3bb5..3db34c0 100644
--- a/integration_tests/jacoco-offline/build.gradle
+++ b/integration_tests/jacoco-offline/build.gradle
@@ -3,6 +3,8 @@
 apply plugin: RoboJavaModulePlugin
 apply plugin: "jacoco"
 
+def jacocoVersion = libs.versions.jacoco.get()
+
 jacoco {
     toolVersion = jacocoVersion
 }
@@ -18,7 +20,7 @@
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
 
     testImplementation project(":robolectric")
-    testImplementation "junit:junit:$junitVersion"
+    testImplementation libs.junit4
     testImplementation "org.jacoco:org.jacoco.agent:$jacocoVersion:runtime"
 }
 
diff --git a/integration_tests/kotlin/build.gradle b/integration_tests/kotlin/build.gradle
index 68c5c67..fd52d97 100644
--- a/integration_tests/kotlin/build.gradle
+++ b/integration_tests/kotlin/build.gradle
@@ -21,8 +21,8 @@
 
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
+    testImplementation libs.kotlin.stdlib
+    testImplementation libs.junit4
+    testImplementation libs.truth
     testImplementation "androidx.test:core:$axtCoreVersion@aar"
 }
diff --git a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/ParameterizedRobolectricTestRunnerTest.kt b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/ParameterizedRobolectricTestRunnerTest.kt
new file mode 100644
index 0000000..6d77aa9
--- /dev/null
+++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/ParameterizedRobolectricTestRunnerTest.kt
@@ -0,0 +1,28 @@
+package org.robolectric.integrationtests.kotlin
+
+import android.net.Uri
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.ParameterizedRobolectricTestRunner.Parameters
+import org.robolectric.annotation.Config
+
+@RunWith(ParameterizedRobolectricTestRunner::class)
+class ParameterizedRobolectricTestRunnerTest(private var uri: Uri) {
+  @Test
+  @Config(manifest = Config.NONE)
+  fun parse() {
+    val currentUri = Uri.parse("http://host/")
+    assertThat(currentUri).isEqualTo(uri)
+  }
+
+  companion object {
+    @Parameters
+    @JvmStatic
+    fun getTestData(): Collection<*> {
+      val data = arrayOf<Any>(Uri.parse("http://host/"))
+      return listOf(data)
+    }
+  }
+}
diff --git a/integration_tests/libphonenumber/build.gradle b/integration_tests/libphonenumber/build.gradle
index 2c27a79..61120f2 100644
--- a/integration_tests/libphonenumber/build.gradle
+++ b/integration_tests/libphonenumber/build.gradle
@@ -4,10 +4,10 @@
 
 dependencies {
     api project(":robolectric")
-    api "junit:junit:${junitVersion}"
+    api libs.junit4
     compileOnly AndroidSdk.MAX_SDK.coordinates
 
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation 'com.googlecode.libphonenumber:libphonenumber:8.13.8'
-}
\ No newline at end of file
+    testImplementation libs.truth
+    testImplementation libs.libphonenumber
+}
diff --git a/integration_tests/memoryleaks/build.gradle b/integration_tests/memoryleaks/build.gradle
index 2cc5124..91c5eb0 100644
--- a/integration_tests/memoryleaks/build.gradle
+++ b/integration_tests/memoryleaks/build.gradle
@@ -5,6 +5,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.integrationtests.memoryleaks'
 
     defaultConfig {
         minSdk 16
@@ -28,7 +29,7 @@
     // Testing dependencies
     testImplementation project(path: ':testapp')
     testImplementation project(":robolectric")
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "com.google.guava:guava-testlib:$guavaJREVersion"
-    testImplementation "androidx.fragment:fragment:$fragmentVersion"
+    testImplementation libs.junit4
+    testImplementation libs.guava.testlib
+    testImplementation libs.androidx.fragment
 }
diff --git a/integration_tests/mockito-experimental/build.gradle b/integration_tests/mockito-experimental/build.gradle
index 4aafcbc..f5172d6 100644
--- a/integration_tests/mockito-experimental/build.gradle
+++ b/integration_tests/mockito-experimental/build.gradle
@@ -8,7 +8,7 @@
 
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockito.inline
 }
diff --git a/integration_tests/mockito-kotlin/build.gradle b/integration_tests/mockito-kotlin/build.gradle
index ae97f1b..776f33b 100644
--- a/integration_tests/mockito-kotlin/build.gradle
+++ b/integration_tests/mockito-kotlin/build.gradle
@@ -18,8 +18,8 @@
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
     testImplementation "androidx.test.ext:junit:$axtJunitVersion@aar"
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.kotlin.stdlib
+    testImplementation libs.mockito
 }
diff --git a/integration_tests/mockito/build.gradle b/integration_tests/mockito/build.gradle
index e199cd7..31e6ce6 100644
--- a/integration_tests/mockito/build.gradle
+++ b/integration_tests/mockito/build.gradle
@@ -8,7 +8,7 @@
 
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
-}
\ No newline at end of file
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockito
+}
diff --git a/integration_tests/mockk/build.gradle b/integration_tests/mockk/build.gradle
index 78344a9..1d59071 100644
--- a/integration_tests/mockk/build.gradle
+++ b/integration_tests/mockk/build.gradle
@@ -21,7 +21,7 @@
 
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation 'io.mockk:mockk:1.13.4'
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockk
 }
diff --git a/integration_tests/nativegraphics/Android.bp b/integration_tests/nativegraphics/Android.bp
new file mode 100644
index 0000000..eb491ba
--- /dev/null
+++ b/integration_tests/nativegraphics/Android.bp
@@ -0,0 +1,60 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "NativeGraphicsTestsAssetsLib",
+    asset_dirs: ["src/main/assets"],
+    resource_dirs: ["src/main/res"],
+    min_sdk_version: "26",
+    target_sdk_version: "31",
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+    optimize: {
+        enabled: false
+    },
+}
+
+android_app {
+    name: "NativeGraphicsPseudoApp",
+    srcs: [],
+    static_libs: ["NativeGraphicsTestsAssetsLib"],
+    manifest: "robo-manifest.xml",
+    aaptflags: [
+        "--extra-packages",
+        "org.robolectric.integrationtests.nativegraphics",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    min_sdk_version: "26",
+    target_sdk_version: "31",
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: ["src/main/res"],
+    kotlincflags: ["-Xjvm-default=all"],
+
+    plugins: ["dagger2-compiler"],
+}
+
+android_robolectric_test {
+    name: "NativeGraphicsTests",
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.core_core",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.ext.junit",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth-prebuilt",
+    ],
+    upstream: true,
+    java_resource_dirs: ["config"],
+    instrumentation_for: "NativeGraphicsPseudoApp",
+}
diff --git a/integration_tests/nativegraphics/build.gradle b/integration_tests/nativegraphics/build.gradle
index 10e8f61..f88f53a 100644
--- a/integration_tests/nativegraphics/build.gradle
+++ b/integration_tests/nativegraphics/build.gradle
@@ -7,6 +7,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.integrationtests.nativegraphics'
 
     defaultConfig {
         minSdk 26
@@ -32,9 +33,9 @@
     testImplementation AndroidSdk.MAX_SDK.coordinates
     testImplementation project(':robolectric')
 
-    testImplementation "androidx.core:core:$coreVersion"
-    testImplementation "androidx.test.ext:junit:$axtJunitVersion"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
+    testImplementation libs.androidx.core
+    testImplementation libs.androidx.test.ext.junit
+    testImplementation libs.truth
+    testImplementation libs.junit4
+    testImplementation libs.mockito
 }
diff --git a/integration_tests/nativegraphics/config/robolectric.properties b/integration_tests/nativegraphics/config/robolectric.properties
new file mode 100644
index 0000000..0d16e6b
--- /dev/null
+++ b/integration_tests/nativegraphics/config/robolectric.properties
@@ -0,0 +1,15 @@
+# Copyright (C) 2022 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.
+#
+sdk=NEWEST_SDK
diff --git a/integration_tests/nativegraphics/robo-manifest.xml b/integration_tests/nativegraphics/robo-manifest.xml
new file mode 100644
index 0000000..56ec797
--- /dev/null
+++ b/integration_tests/nativegraphics/robo-manifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="org.robolectric.integrationtests.nativegraphics"
+          android:sharedUserId="android.uid.phone"
+          coreApp="true">
+  <uses-sdk
+      android:minSdkVersion="26"
+      android:targetSdkVersion="31" />
+  <application>
+    <activity
+        android:name="com.android.myroboapplication.WelcomeActivity"
+        android:exported="true">
+    </activity>
+  </application>
+</manifest>
diff --git a/integration_tests/nativegraphics/src/main/res/color/colors.xml b/integration_tests/nativegraphics/src/main/res/color/colors.xml
deleted file mode 100644
index 64f1589..0000000
--- a/integration_tests/nativegraphics/src/main/res/color/colors.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2008 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.
- -->
-
-<resources>
-    <drawable name="red">#7f00</drawable>
-    <drawable name="blue">#770000ff</drawable>
-    <drawable name="black">#77ffffff</drawable>
-    <drawable name="yellow">#77ffff00</drawable>
-    <color name="testcolor1">#ff00ff00</color>
-    <color name="testcolor2">#ffff0000</color>
-    <color name="failColor">#ff0000ff</color>
-    <color name="colorPrimary">#008577</color>
-    <color name="colorPrimaryDark">#00574B</color>
-    <color name="colorAccent">#D81B60</color>
-</resources>
diff --git a/integration_tests/nativegraphics/src/main/res/color/styles.xml b/integration_tests/nativegraphics/src/main/res/color/styles.xml
deleted file mode 100644
index 7e05f0a..0000000
--- a/integration_tests/nativegraphics/src/main/res/color/styles.xml
+++ /dev/null
@@ -1,198 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="Whatever">
-        <item name="type1">true</item>
-        <item name="type2">false</item>
-        <item name="type3">#ff0000ff</item>
-        <item name="type4">#ff00ff00</item>
-        <item name="type5">0.75px</item>
-        <item name="type6">10px</item>
-        <item name="type7">18px</item>
-        <item name="type8">@drawable/pass</item>
-        <item name="type9">3.14</item>
-        <item name="type10">100%</item>
-        <item name="type11">365</item>
-        <item name="type12">86400</item>
-        <item name="type13">@string/hello_android</item>
-        <item name="type14">TypedArray Test!</item>
-        <item name="type15">@array/difficultyLevel</item>
-        <item name="type16">Typed Value!</item>
-    </style>
-
-    <style name="TextViewWithoutColorAndAppearance">
-        <item name="android:textSize">18sp</item>
-    </style>
-
-    <style name="TextViewWithColorButWithOutAppearance">
-        <item name="android:textColor">#ff0000ff</item>
-    </style>
-
-    <style name="TextViewWithColorAndAppearance">
-        <item name="android:textColor">#ff0000ff</item>
-        <item name="android:textAppearance">@style/TextAppearance.WithColor</item>
-    </style>
-
-    <style name="TextViewWithoutColorButWithAppearance">
-        <item name="android:textAppearance">@style/TextAppearance.WithColor</item>
-    </style>
-
-    <style name="TextAppearance" parent="android:TextAppearance">
-    </style>
-
-    <style name="TextAppearance.WithColor">
-        <item name="android:textColor">#ffff0000</item>
-    </style>
-
-    <style name="TextAppearance.All">
-        <item name="android:textColor">@drawable/black</item>
-        <item name="android:textSize">20px</item>
-        <item name="android:textStyle">bold</item>
-        <item name="android:textColorHint">@drawable/red</item>
-        <item name="android:textColorLink">@drawable/blue</item>
-        <item name="android:textColorHighlight">@drawable/yellow</item>
-    </style>
-
-    <style name="TextAppearance.Colors">
-        <item name="android:textColor">@drawable/black</item>
-        <item name="android:textColorHint">@drawable/blue</item>
-        <item name="android:textColorLink">@drawable/yellow</item>
-        <item name="android:textColorHighlight">@drawable/red</item>
-    </style>
-
-    <style name="TextAppearance.NotColors">
-        <item name="android:textSize">17px</item>
-        <item name="android:typeface">sans</item>
-        <item name="android:textStyle">normal</item>
-    </style>
-
-    <style name="TextAppearance.Style">
-        <item name="android:textStyle">normal</item>
-    </style>
-
-    <style name="TestEnum1">
-        <item name="testEnum">val1</item>
-    </style>
-
-    <style name="TestEnum2">
-        <item name="testEnum">val2</item>
-    </style>
-
-    <style name="TestEnum10">
-        <item name="testEnum">val10</item>
-    </style>
-
-    <style name="TestFlag1">
-        <item name="testFlags">bit1</item>
-    </style>
-
-    <style name="TestFlag2">
-        <item name="testFlags">bit2</item>
-    </style>
-
-    <style name="TestFlag31">
-        <item name="testFlags">bit31</item>
-    </style>
-
-    <style name="TestFlag1And2">
-        <item name="testFlags">bit1|bit2</item>
-    </style>
-
-    <style name="TestFlag1And2And31">
-        <item name="testFlags">bit1|bit2|bit31</item>
-    </style>
-
-    <style name="TestEnum1.EmptyInherit" />
-
-    <style name="Theme_AlertDialog">
-        <item name="android:textSize">18sp</item>
-    </style>
-
-    <style name="TestProgressBar">
-        <item name="android:indeterminateOnly">false</item>
-        <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
-        <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
-        <item name="android:minHeight">20dip</item>
-        <item name="android:maxHeight">20dip</item>
-        <item name="android:focusable">true</item>
-    </style>
-
-    <style name="Test_Theme">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:panelColorForeground">#ff000000</item>
-        <item name="android:panelColorBackground">#ffffffff</item>
-    </style>
-
-    <style name="Theme_OverrideOuter">
-        <item name="themeType">1</item>
-    </style>
-
-    <style name="Theme_OverrideInner">
-        <item name="themeType">2</item>
-        <item name="themeOverrideAttr">@style/Theme_OverrideAttr</item>
-    </style>
-
-    <style name="Theme_OverrideAttr">
-        <item name="themeType">3</item>
-    </style>
-    
-    <style name="Theme_ThemedDrawableTest">
-        <item name="themeBoolean">true</item>
-        <item name="themeColor">@android:color/black</item>
-        <item name="themeFloat">1.0</item>
-        <item name="themeAngle">45.0</item>
-        <item name="themeInteger">1</item>
-        <item name="themeDimension">1px</item>
-        <item name="themeDrawable">@drawable/icon_black</item>
-        <item name="themeBitmap">@drawable/icon_black</item>
-        <item name="themeNinePatch">@drawable/ninepatch_0</item>
-        <item name="themeGravity">48</item>
-        <item name="themeTileMode">2</item>
-        <item name="themeType">0</item>
-        <item name="themeVectorDrawableFillColor">#F00F</item>
-    </style>
-
-    <attr name="colorPrimary" format="reference|color" />
-    <attr name="colorPrimaryDark" format="reference|color" />
-    <attr name="colorAccent" format="reference|color" />
-
-    <style name="Theme_MixedGradientTheme">
-        <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
-
-        <!-- overwrite the default gradient type to be radial in the theme -->
-        <item name="GradientType">1</item>
-    </style>
-
-    <!-- default gradient type of linear -->
-    <attr name="GradientType" format="integer">0</attr>
-
-    <style name="WhiteBackgroundNoWindowAnimation"
-           parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowFullscreen">true</item>
-        <item name="android:windowOverscan">true</item>
-        <item name="android:fadingEdge">none</item>
-        <item name="android:windowBackground">@android:color/white</item>
-        <item name="android:windowContentTransitions">false</item>
-        <item name="android:windowAnimationStyle">@null</item>
-    </style>
-
-</resources>
diff --git a/integration_tests/play_services/build.gradle b/integration_tests/play_services/build.gradle
index f7499dd..0e05dd6 100644
--- a/integration_tests/play_services/build.gradle
+++ b/integration_tests/play_services/build.gradle
@@ -9,7 +9,7 @@
 
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
+    testImplementation libs.junit4
+    testImplementation libs.truth
     testImplementation "com.google.android.gms:play-services-basement:18.0.1"
-}
\ No newline at end of file
+}
diff --git a/integration_tests/powermock/build.gradle b/integration_tests/powermock/build.gradle
index be4180c..6d5cf68 100644
--- a/integration_tests/powermock/build.gradle
+++ b/integration_tests/powermock/build.gradle
@@ -7,11 +7,8 @@
     compileOnly AndroidSdk.MAX_SDK.coordinates
 
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
 
-    testImplementation "org.powermock:powermock-module-junit4:2.0.9"
-    testImplementation "org.powermock:powermock-module-junit4-rule:2.0.9"
-    testImplementation "org.powermock:powermock-api-mockito2:2.0.9"
-    testImplementation "org.powermock:powermock-classloading-xstream:2.0.9"
-}
\ No newline at end of file
+    testImplementation libs.bundles.powermock
+}
diff --git a/integration_tests/security-providers/build.gradle b/integration_tests/security-providers/build.gradle
index f96df56..8ac0264 100644
--- a/integration_tests/security-providers/build.gradle
+++ b/integration_tests/security-providers/build.gradle
@@ -4,12 +4,12 @@
 
 dependencies {
     api project(":robolectric")
-    api "junit:junit:${junitVersion}"
+    api libs.junit4
     compileOnly AndroidSdk.MAX_SDK.coordinates
 
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.4.0"
-    testImplementation "com.squareup.okhttp3:okhttp"
-    testImplementation platform("com.squareup.okhttp3:okhttp-bom:4.10.0")
+    testImplementation libs.truth
+    testImplementation libs.conscrypt.openjdk.uber
+    testImplementation libs.okhttp
+    testImplementation platform(libs.okhttp.bom)
 }
diff --git a/integration_tests/sparsearray/build.gradle b/integration_tests/sparsearray/build.gradle
index 7627177..1e4ba1d 100644
--- a/integration_tests/sparsearray/build.gradle
+++ b/integration_tests/sparsearray/build.gradle
@@ -14,6 +14,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.sparsearray'
 
     defaultConfig {
         minSdk 16
@@ -41,7 +42,7 @@
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
     testImplementation project(":robolectric")
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.kotlin.stdlib
 }
diff --git a/junit/build.gradle b/junit/build.gradle
index 9a11978..5d58552 100644
--- a/junit/build.gradle
+++ b/junit/build.gradle
@@ -11,6 +11,6 @@
     api project(":shadowapi")
     api project(":utils:reflector")
 
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
-    compileOnly "junit:junit:${junitVersion}"
+    compileOnly libs.findbugs.jsr305
+    compileOnly libs.junit4
 }
diff --git a/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java b/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java
index bfa0053..414b410 100644
--- a/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java
+++ b/junit/src/main/java/org/robolectric/internal/SandboxTestRunner.java
@@ -237,7 +237,6 @@
       @Override
       public void evaluate() throws Throwable {
         PerfStatsCollector perfStatsCollector = PerfStatsCollector.getInstance();
-        perfStatsCollector.reset();
         perfStatsCollector.setEnabled(!perfStatsReporters.isEmpty());
 
         Event initialization = perfStatsCollector.startEvent("initialization");
@@ -259,6 +258,10 @@
               HelperTestRunner helperTestRunner = getCachedHelperTestRunner(bootstrappedTestClass);
               helperTestRunner.frameworkMethod = method;
 
+              // The method class may be different than the test class if the method annotated @Test
+              // is declared on a superclass of the test.
+              Class<?> bootstrappedMethodClass =
+                  sandbox.bootstrappedClass(method.getMethod().getDeclaringClass());
               final Method bootstrappedMethod;
               try {
                 Class<?>[] parameterTypes =
@@ -266,7 +269,7 @@
                         .map(type -> type.isPrimitive() ? type : sandbox.bootstrappedClass(type))
                         .toArray(Class[]::new);
                 bootstrappedMethod =
-                    bootstrappedTestClass.getMethod(method.getMethod().getName(), parameterTypes);
+                    bootstrappedMethodClass.getMethod(method.getMethod().getName(), parameterTypes);
               } catch (NoSuchMethodException e) {
                 throw new RuntimeException(e);
               }
@@ -292,11 +295,7 @@
                 throw Util.sneakyThrow(throwable);
               } finally {
                 Thread.currentThread().setContextClassLoader(priorContextClassLoader);
-                try {
-                  finallyAfterTest(method);
-                } catch (Exception e) {
-                  e.printStackTrace();
-                }
+                finallyAfterTest(method);
                 reportPerfStats(perfStatsCollector);
                 perfStatsCollector.reset();
               }
diff --git a/nativeruntime/build.gradle b/nativeruntime/build.gradle
index f8d496d..e784984 100644
--- a/nativeruntime/build.gradle
+++ b/nativeruntime/build.gradle
@@ -49,8 +49,8 @@
         url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
 
         credentials {
-          username = System.properties["sonatype-login"] ?: System.env['sonatypeLogin']
-          password = System.properties["sonatype-password"] ?: System.env['sonatypePassword']
+          username = System.properties["sonatype-login"] ?: System.env['SONATYPE_LOGIN']
+          password = System.properties["sonatype-password"] ?: System.env['SONATYPE_PASSWORD']
         }
       }
     }
@@ -64,17 +64,17 @@
 dependencies {
   api project(":utils")
   api project(":utils:reflector")
-  api "com.google.guava:guava:$guavaJREVersion"
+  api libs.guava
 
-  implementation "org.robolectric:nativeruntime-dist-compat:1.0.1"
+  implementation libs.robolectric.nativeruntime.dist.compat
 
-  annotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
-  compileOnly "com.google.auto.service:auto-service-annotations:$autoServiceVersion"
+  annotationProcessor libs.auto.service
+  compileOnly libs.auto.service.annotations
   compileOnly AndroidSdk.MAX_SDK.coordinates
 
   testCompileOnly AndroidSdk.MAX_SDK.coordinates
   testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
   testImplementation project(":robolectric")
-  testImplementation "junit:junit:${junitVersion}"
-  testImplementation "com.google.truth:truth:${truthVersion}"
+  testImplementation libs.junit4
+  testImplementation libs.truth
 }
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java
index 5221a4b..3892453 100644
--- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java
@@ -11,6 +11,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.Files;
 import com.google.common.io.Resources;
+import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -137,7 +138,9 @@
       }
     }
     System.setProperty(
-        "robolectric.nativeruntime.fontdir", fontsOutputPath.toAbsolutePath().toString());
+        "robolectric.nativeruntime.fontdir",
+        // Android's FontListParser expects a trailing slash for the base font directory.
+        fontsOutputPath.toAbsolutePath() + File.separator);
     if (zipfs != null) {
       zipfs.close();
     }
diff --git a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java
index 0391159..e5d395f 100644
--- a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java
+++ b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java
@@ -2,7 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.O;
 import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeTrue;
+import static com.google.common.truth.TruthJUnit.assume;
 
 import android.database.CursorWindow;
 import android.database.sqlite.SQLiteDatabase;
@@ -33,8 +33,8 @@
 
   @Test
   public void extracts_fontsAndIcuData() {
-    assumeTrue(hasResource("fonts"));
-    assumeTrue(hasResource("icu/icudt68l.dat"));
+    assume().that(hasResource("fonts")).isTrue();
+    assume().that(hasResource("icu/icudt68l.dat")).isTrue();
     DefaultNativeRuntimeLoader defaultNativeRuntimeLoader = new DefaultNativeRuntimeLoader();
     defaultNativeRuntimeLoader.ensureLoaded();
     // Check that extraction of some key files worked.
diff --git a/pluginapi/build.gradle b/pluginapi/build.gradle
index 375cd10..9d78852 100644
--- a/pluginapi/build.gradle
+++ b/pluginapi/build.gradle
@@ -5,11 +5,11 @@
 apply plugin: DeployedRoboJavaModulePlugin
 
 dependencies {
-    compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
+    compileOnly libs.findbugs.jsr305
     api project(":annotations")
-    api "com.google.guava:guava:$guavaJREVersion"
+    api libs.guava
 
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockito
 }
diff --git a/plugins/maven-dependency-resolver/build.gradle b/plugins/maven-dependency-resolver/build.gradle
index de20b2b..2aa33d9 100644
--- a/plugins/maven-dependency-resolver/build.gradle
+++ b/plugins/maven-dependency-resolver/build.gradle
@@ -1,3 +1,4 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 import org.robolectric.gradle.DeployedRoboJavaModulePlugin
 import org.robolectric.gradle.RoboJavaModulePlugin
 
@@ -13,7 +14,7 @@
     }
 }
 
-tasks.withType(GenerateModuleMetadata) {
+tasks.withType(GenerateModuleMetadata).configureEach {
     // We don't want to release gradle module metadata now to avoid
     // potential compatibility problems.
     enabled = false
@@ -22,11 +23,11 @@
 compileKotlin {
     // Use java/main classes directory to replace default kotlin/main to
     // avoid d8 error when dexing & desugaring kotlin classes with non-exist
-    // kotlin/main directory because utils module doesn't have kotlin code
+    // kotlin/main directory because this module doesn't have kotlin code
     // in production. If utils module starts to add Kotlin code in main source
     // set, we can remove this destinationDirectory modification.
     destinationDirectory = file("${projectDir}/build/classes/java/main")
-    compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
+    compilerOptions.jvmTarget = JvmTarget.JVM_1_8
 }
 
 afterEvaluate {
@@ -48,10 +49,12 @@
 dependencies {
     api project(":pluginapi")
     api project(":utils")
-    api "com.google.guava:guava:$guavaJREVersion"
+    api libs.auto.value.annotations
+    api libs.guava
+    annotationProcessor libs.auto.value
 
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
-    testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+    testImplementation libs.junit4
+    testImplementation libs.mockito
+    testImplementation libs.truth
+    testImplementation libs.kotlin.stdlib
 }
diff --git a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java
index 60f852d..adeda9e 100644
--- a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java
+++ b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java
@@ -2,6 +2,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
 import com.google.common.hash.HashCode;
 import com.google.common.hash.Hashing;
@@ -24,6 +25,7 @@
 import java.util.Base64;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import javax.annotation.Nonnull;
 import org.robolectric.util.Logger;
 
 /**
@@ -82,15 +84,27 @@
                   return Futures.immediateFuture(null);
                 }
                 createArtifactSubdirectory(artifact, localRepositoryDir);
-                boolean pomValid =
+                ValidationResult pomResult =
                     validateStagedFiles(artifact.pomPath(), artifact.pomSha512Path());
-                if (!pomValid) {
-                  throw new AssertionError("SHA512 mismatch for POM file fetched in " + artifact);
+                if (!pomResult.isSuccess()) {
+                  throw new AssertionError(
+                      "SHA-512 mismatch for POM file for "
+                          + artifact
+                          + ", expected SHA-512="
+                          + pomResult.expectedHashCode()
+                          + ", actual SHA-512="
+                          + pomResult.calculatedHashCode());
                 }
-                boolean jarValid =
+                ValidationResult jarResult =
                     validateStagedFiles(artifact.jarPath(), artifact.jarSha512Path());
-                if (!jarValid) {
-                  throw new AssertionError("SHA512 mismatch for JAR file fetched in " + artifact);
+                if (!jarResult.isSuccess()) {
+                  throw new AssertionError(
+                      "SHA-512 mismatch for POM file for "
+                          + artifact
+                          + ", expected SHA-512="
+                          + jarResult.expectedHashCode()
+                          + ", actual SHA-512="
+                          + jarResult.calculatedHashCode());
                 }
                 Logger.info(
                     String.format(
@@ -123,7 +137,8 @@
     new File(repositoryDir, artifact.pomSha512Path()).delete();
   }
 
-  private boolean validateStagedFiles(String filePath, String sha512Path) throws IOException {
+  private ValidationResult validateStagedFiles(String filePath, String sha512Path)
+      throws IOException {
     File tempFile = new File(this.stagingRepositoryDir, filePath);
     File sha512File = new File(this.stagingRepositoryDir, sha512Path);
 
@@ -131,7 +146,24 @@
         HashCode.fromString(new String(Files.asByteSource(sha512File).read(), UTF_8));
 
     HashCode actual = Files.asByteSource(tempFile).hash(Hashing.sha512());
-    return expected.equals(actual);
+    return ValidationResult.create(expected.equals(actual), expected.toString(), actual.toString());
+  }
+
+  @AutoValue
+  abstract static class ValidationResult {
+    abstract boolean isSuccess();
+
+    @Nonnull
+    abstract String expectedHashCode();
+
+    @Nonnull
+    abstract String calculatedHashCode();
+
+    static ValidationResult create(
+        boolean isSuccess, String expectedHashCode, String calculatedHashCode) {
+      return new AutoValue_MavenArtifactFetcher_ValidationResult(
+          isSuccess, expectedHashCode, calculatedHashCode);
+    }
   }
 
   private void createArtifactSubdirectory(MavenJarArtifact artifact, File repositoryDir)
@@ -218,6 +250,9 @@
       try (InputStream inputStream = connection.getInputStream();
           FileOutputStream outputStream = new FileOutputStream(localFile)) {
         ByteStreams.copy(inputStream, outputStream);
+        // Ensure all contents are written to disk.
+        outputStream.flush();
+        outputStream.getFD().sync();
       }
       return Futures.immediateFuture(null);
     }
diff --git a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenDependencyResolver.java b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenDependencyResolver.java
old mode 100755
new mode 100644
diff --git a/preinstrumented/build.gradle b/preinstrumented/build.gradle
index 95d533e..8ffb5bb 100644
--- a/preinstrumented/build.gradle
+++ b/preinstrumented/build.gradle
@@ -17,11 +17,12 @@
 }
 
 dependencies {
-    implementation "com.google.guava:guava:$guavaJREVersion"
+    implementation libs.guava
     implementation project(":sandbox")
+    implementation project(":shadows:versioning")
 }
 
-task instrumentAll {
+tasks.register('instrumentAll') {
     dependsOn ':prefetchSdks'
     dependsOn 'build'
 
@@ -42,11 +43,11 @@
     }
 }
 
-task('sourcesJar', type: Jar) {
+tasks.register('sourcesJar', Jar) {
     archiveClassifier = "sources"
 }
 
-task('javadocJar', type: Jar) {
+tasks.register('javadocJar', Jar) {
     archiveClassifier = "javadoc"
 }
 
@@ -102,8 +103,8 @@
                 url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
 
                 credentials {
-                    username = System.properties["sonatype-login"] ?: System.env['sonatypeLogin']
-                    password = System.properties["sonatype-password"] ?: System.env['sonatypePassword']
+                    username = System.properties["sonatype-login"] ?: System.env['SONATYPE_LOGIN']
+                    password = System.properties["sonatype-password"] ?: System.env['SONATYPE_PASSWORD']
                 }
             }
         }
@@ -132,4 +133,4 @@
     AndroidSdk.ALL_SDKS.each { androidSdk ->
         delete "${buildDir}/${androidSdk.preinstrumentedJarFileName}"
     }
-}
\ No newline at end of file
+}
diff --git a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
index a9c5ace..753c167 100644
--- a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
+++ b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
@@ -8,7 +8,6 @@
 import java.io.InputStream;
 import java.util.Enumeration;
 import java.util.Locale;
-import java.util.Properties;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.jar.JarOutputStream;
@@ -21,6 +20,8 @@
 import org.robolectric.internal.bytecode.InstrumentationConfiguration;
 import org.robolectric.internal.bytecode.Interceptors;
 import org.robolectric.util.inject.Injector;
+import org.robolectric.versioning.AndroidVersionInitTools;
+import org.robolectric.versioning.AndroidVersions.AndroidRelease;
 
 /** Runs Robolectric invokedynamic instrumentation on an android-all jar. */
 public class JarInstrumentor {
@@ -146,14 +147,7 @@
   }
 
   private int getJarAndroidSDKVersion(JarFile jarFile) throws IOException {
-    ZipEntry buildProp = jarFile.getEntry("build.prop");
-    Properties buildProps = new Properties();
-    buildProps.load(jarFile.getInputStream(buildProp));
-    String codename = buildProps.getProperty("ro.build.version.codename");
-    // Check for a prerelease SDK.
-    if (!"REL".equals(codename)) {
-      return 10000;
-    }
-    return Integer.parseInt(buildProps.getProperty("ro.build.version.sdk"));
+    AndroidRelease release = AndroidVersionInitTools.computeReleaseVersion(jarFile);
+    return release.getSdkInt();
   }
 }
diff --git a/processor/Android.bp b/processor/Android.bp
index 46a8dc3..90a4981 100644
--- a/processor/Android.bp
+++ b/processor/Android.bp
@@ -77,12 +77,6 @@
 
     // Disable annotation processing while compiling tests to avoid executing RobolectricProcessor.
     javacflags: ["-proc:none"],
-
-    // Disabling tests as they fail in the bramble build.
-    test_options: {
-        unit_test: false,
-    },
-
 }
 
 // Workaround: java_resource_dirs ignores *.java files
diff --git a/processor/build.gradle b/processor/build.gradle
index ac14e42..9185d08 100644
--- a/processor/build.gradle
+++ b/processor/build.gradle
@@ -35,21 +35,21 @@
     api project(":annotations")
     api project(":shadowapi")
 
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
-    api "org.ow2.asm:asm:${asmVersion}"
-    api "org.ow2.asm:asm-commons:${asmVersion}"
-    api "com.google.guava:guava:$guavaJREVersion"
-    api "com.google.code.gson:gson:2.10.1"
-    implementation 'com.google.auto:auto-common:1.1.2'
+    compileOnly libs.findbugs.jsr305
+    api libs.asm
+    api libs.asm.commons
+    api libs.guava
+    api libs.gson
+    implementation libs.auto.common
 
     def toolsJar = Jvm.current().getToolsJar()
     if (toolsJar != null) {
         implementation files(toolsJar)
     }
 
-    testImplementation "javax.annotation:jsr250-api:1.0"
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
-    testImplementation "com.google.testing.compile:compile-testing:0.21.0"
-    testImplementation "com.google.truth:truth:${truthVersion}"
+    testImplementation libs.javax.annotation.jsr250.api
+    testImplementation libs.junit4
+    testImplementation libs.mockito
+    testImplementation libs.compile.testing
+    testImplementation libs.truth
 }
diff --git a/processor/sdks.txt b/processor/sdks.txt
index b9931ca..d39d959 100644
--- a/processor/sdks.txt
+++ b/processor/sdks.txt
@@ -10,6 +10,5 @@
 prebuilts/misc/common/robolectric/android-all/android-all-8.0.0_r4-robolectric-r1.jar
 prebuilts/misc/common/robolectric/android-all/android-all-8.1.0-robolectric-4611349.jar
 prebuilts/misc/common/robolectric/android-all/android-all-9-robolectric-4913185-2.jar
-prebuilts/misc/common/robolectric/android-all/android-all-9plus-robolectric-5616371.jar
 prebuilts/misc/common/robolectric/android-all/android-all-10-robolectric-5803371.jar
-prebuilts/misc/common/robolectric/android-all/android-all-R-beta2-robolectric-6625208.jar
+prebuilts/misc/common/robolectric/android-all/android-all-11-robolectric-6757853.jar
diff --git a/resources/build.gradle b/resources/build.gradle
index 129dc20..077cdd3 100644
--- a/resources/build.gradle
+++ b/resources/build.gradle
@@ -9,11 +9,11 @@
     api project(":annotations")
     api project(":pluginapi")
 
-    api "com.google.guava:guava:$guavaJREVersion"
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+    api libs.guava
+    compileOnly libs.findbugs.jsr305
 
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "com.google.testing.compile:compile-testing:0.21.0"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.compile.testing
+    testImplementation libs.mockito
 }
diff --git a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
index cfabcbb..7402b49 100644
--- a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
+++ b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
@@ -53,6 +53,7 @@
   private String processName;
   private String themeRef;
   private String labelRef;
+  private String appComponentFactory; // Added from SDK 28
   private Integer minSdkVersion;
   private Integer targetSdkVersion;
   private Integer maxSdkVersion;
@@ -191,6 +192,7 @@
         rClassName = packageName + ".R";
 
         Node applicationNode = findApplicationNode(manifestDocument);
+        // Parse application node of the AndroidManifest.xml
         if (applicationNode != null) {
           NamedNodeMap attributes = applicationNode.getAttributes();
           int attrCount = attributes.getLength();
@@ -204,6 +206,7 @@
           processName = applicationAttributes.get("android:process");
           themeRef = applicationAttributes.get("android:theme");
           labelRef = applicationAttributes.get("android:label");
+          appComponentFactory = applicationAttributes.get("android:appComponentFactory");
 
           parseReceivers(applicationNode);
           parseServices(applicationNode);
@@ -605,6 +608,11 @@
     return labelRef;
   }
 
+  public String getAppComponentFactory() {
+    parseAndroidManifest();
+    return appComponentFactory;
+  }
+
   /**
    * Returns the minimum Android SDK version that this package expects to be runnable on, as
    * specified in the manifest.
diff --git a/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java b/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java
index 0de3390..8b8165b 100644
--- a/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java
+++ b/resources/src/main/java/org/robolectric/res/android/LoadedArsc.java
@@ -526,32 +526,79 @@
         return 0;
       }
 
+      // for (const auto& type_entry : type_spec->type_entries) {
       for (ResTable_type iter : type_spec.types) {
+        // const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type;
         ResTable_type type = iter;
+        // const size_t entry_count = dtohl(type->entryCount);
         int entry_count = type.entryCount;
+        // const auto entry_offsets = type.offset(dtohs(type->header.headerSize));
 
+        // for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
         for (int entry_idx = 0; entry_idx < entry_count; entry_idx++) {
-          // const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
-          //     reinterpret_cast<const uint8_t*>(type.type) + dtohs(type.type.header.headerSize));
-          // ResTable_type entry_offsets = new ResTable_type(type.myBuf(),
-          //     type.myOffset() + type.header.headerSize);
-          // int offset = dtohl(entry_offsets[entry_idx]);
-          int offset = dtohl(type.entryOffset(entry_idx));
+          // uint32_t offset;
+          int offset;
+          // uint16_t res_idx;
+          short res_idx;
+          // if (type->flags & ResTable_type::FLAG_SPARSE) {
+          if (isTruthy(type.flags & ResTable_type.FLAG_SPARSE)) {
+            // auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx;
+
+            ResTable_sparseTypeEntry sparse_entry =
+                new ResTable_sparseTypeEntry(
+                    type.myBuf(), type.myOffset() + entry_idx * ResTable_sparseTypeEntry.SIZEOF);
+            // if (!sparse_entry) {
+            //   return base::unexpected(IOError::PAGES_MISSING);
+            // }
+            // TODO: implement above
+            // offset = dtohs(sparse_entry->offset) * 4u;
+            offset = dtohs(sparse_entry.offset) * 4;
+            // res_idx  = dtohs(sparse_entry->idx);
+            res_idx = dtohs(sparse_entry.idx);
+            // } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+          } else if (isTruthy(type.flags & ResTable_type.FLAG_OFFSET16)) {
+            // auto entry = entry_offsets.convert<uint16_t>() + entry_idx;
+            int entry = type.entryOffset(entry_idx);
+            // if (!entry) {
+            //   return base::unexpected(IOError::PAGES_MISSING);
+            // }
+            // offset = offset_from16(entry.value());
+            offset = entry;
+            // res_idx = entry_idx;
+            res_idx = (short) entry_idx;
+          } else {
+            // auto entry = entry_offsets.convert<uint32_t>() + entry_idx;
+            int entry = type.entryOffset(entry_idx);
+            // if (!entry) {
+            //   return base::unexpected(IOError::PAGES_MISSING);
+            // }
+            // offset = dtohl(entry.value());
+            offset = dtohl(entry);
+            res_idx = (short) entry_idx;
+          }
+
           if (offset != ResTable_type.NO_ENTRY) {
-            // const ResTable_entry* entry =
-            //     reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type.type) +
-            //     dtohl(type.type.entriesStart) + offset);
+            // auto entry = type.offset(dtohl(type->entriesStart) +
+            // offset).convert<ResTable_entry>();
             ResTable_entry entry =
-                new ResTable_entry(type.myBuf(), type.myOffset() +
-                    dtohl(type.entriesStart) + offset);
+                new ResTable_entry(
+                    type.myBuf(), type.myOffset() + dtohl(type.entriesStart) + offset);
+            // if (!entry) {
+            //   return base::unexpected(IOError::PAGES_MISSING);
+            // }
+            // TODO implement above
+            // if (entry->key() == static_cast<uint32_t>(*key_idx)) {
             if (dtohl(entry.getKeyIndex()) == key_idx) {
-              // The package ID will be overridden by the caller (due to runtime assignment of package
+              // The package ID will be overridden by the caller (due to runtime assignment of
+              // package
               // IDs for shared libraries).
-              return make_resid((byte) 0x00, (byte) (type_idx + type_id_offset_ + 1), (short) entry_idx);
+              // return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
+              return make_resid((byte) 0x00, (byte) (type_idx + type_id_offset_ + 1), res_idx);
             }
           }
         }
       }
+      // return base::unexpected(std::nullopt);
       return 0;
     }
 
diff --git a/robolectric/Android.bp b/robolectric/Android.bp
index 139062c..e690895 100644
--- a/robolectric/Android.bp
+++ b/robolectric/Android.bp
@@ -15,6 +15,7 @@
     name: "Robolectric_robolectric_upstream",
     libs: [
         "Robolectric_shadows_framework_upstream",
+        "Robolectric_shadows_versioning_upstream",
         "Robolectric_annotations_upstream",
         "Robolectric_shadowapi_upstream",
         "Robolectric_resources_upstream",
diff --git a/robolectric/build.gradle b/robolectric/build.gradle
index faaa8b3..b826e92 100644
--- a/robolectric/build.gradle
+++ b/robolectric/build.gradle
@@ -5,8 +5,8 @@
 apply plugin: DeployedRoboJavaModulePlugin
 
 dependencies {
-    annotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
-    annotationProcessor "com.google.errorprone:error_prone_core:$errorproneVersion"
+    annotationProcessor libs.auto.service
+    annotationProcessor libs.error.prone.core
 
     api project(":annotations")
     api project(":junit")
@@ -16,33 +16,34 @@
     api project(":utils")
     api project(":utils:reflector")
     api project(":plugins:maven-dependency-resolver")
-    api "javax.inject:javax.inject:1"
-    compileOnly "com.google.auto.service:auto-service-annotations:$autoServiceVersion"
-    api "javax.annotation:javax.annotation-api:1.3.2"
+    api libs.javax.inject
+    compileOnly libs.auto.service.annotations
+    api libs.javax.annotation.api
 
     // We need to have shadows-framework.jar on the runtime system classpath so ServiceLoader
     //   can find its META-INF/services/org.robolectric.shadows.ShadowAdapter.
     api project(":shadows:framework")
 
-    implementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.2'
-    api "org.bouncycastle:bcprov-jdk18on:1.72"
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+    implementation libs.conscrypt.openjdk.uber
+    api libs.bcprov.jdk18on
+    compileOnly libs.findbugs.jsr305
 
     compileOnly AndroidSdk.MAX_SDK.coordinates
-    compileOnly "junit:junit:${junitVersion}"
+    compileOnly libs.junit4
+
     api "androidx.test:monitor:$axtMonitorVersion@aar"
     implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion@aar"
 
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "com.google.truth.extensions:truth-java8-extension:${truthVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
-    testImplementation "org.hamcrest:hamcrest-junit:2.0.0.0"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.truth.java8.extension
+    testImplementation libs.mockito
+    testImplementation libs.hamcrest.junit
     testImplementation "androidx.test:core:$axtCoreVersion@aar"
     testImplementation "androidx.test.ext:junit:$axtJunitVersion@aar"
     testImplementation "androidx.test.ext:truth:$axtTruthVersion@aar"
     testImplementation "androidx.test:runner:$axtRunnerVersion@aar"
-    testImplementation("com.google.guava:guava:$guavaJREVersion")
+    testImplementation libs.guava
     testCompileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates // run against whatever this JDK supports
 }
diff --git a/robolectric/src/main/java/org/robolectric/Robolectric.java b/robolectric/src/main/java/org/robolectric/Robolectric.java
index 47a52c5..3ce637e 100644
--- a/robolectric/src/main/java/org/robolectric/Robolectric.java
+++ b/robolectric/src/main/java/org/robolectric/Robolectric.java
@@ -1,5 +1,6 @@
 package org.robolectric;
 
+import static com.google.common.base.Preconditions.checkState;
 import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
 
 import android.annotation.IdRes;
@@ -11,6 +12,7 @@
 import android.content.ContentProvider;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.Looper;
 import android.util.AttributeSet;
 import android.view.View;
 import javax.annotation.Nullable;
@@ -104,6 +106,9 @@
    */
   public static <T extends Activity> ActivityController<T> buildActivity(
       Class<T> activityClass, Intent intent, @Nullable Bundle activityOptions) {
+    checkState(
+        Thread.currentThread() == Looper.getMainLooper().getThread(),
+        "buildActivity must be called on main Looper thread");
     return ActivityController.of(
         ReflectionHelpers.callConstructor(activityClass), intent, activityOptions);
   }
diff --git a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java
old mode 100755
new mode 100644
index a807d4e..f250381
--- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java
+++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java
@@ -86,6 +86,7 @@
 import org.robolectric.shadows.ShadowPackageManager;
 import org.robolectric.shadows.ShadowPackageParser;
 import org.robolectric.shadows.ShadowPackageParser._Package_;
+import org.robolectric.shadows.ShadowView;
 import org.robolectric.util.Logger;
 import org.robolectric.util.PerfStatsCollector;
 import org.robolectric.util.ReflectionHelpers;
@@ -96,6 +97,8 @@
 public class AndroidTestEnvironment implements TestEnvironment {
 
   private static final String CONSCRYPT_PROVIDER = "Conscrypt";
+  private static final int MAX_DATA_DIR_NAME_LENGTH = 120;
+
   private final Sdk runtimeSdk;
   private final Sdk compileSdk;
 
@@ -155,7 +158,6 @@
       loggingInitialized = true;
     }
 
-    Logger.debug("Robolectric Test Configuration: " + configuration.map());
     ConscryptMode.Mode conscryptMode = configuration.get(ConscryptMode.Mode.class);
     Security.removeProvider(CONSCRYPT_PROVIDER);
     if (conscryptMode != ConscryptMode.Mode.OFF) {
@@ -178,7 +180,7 @@
 
     androidConfiguration.fontScale = config.fontScale();
 
-    if (Boolean.getBoolean("robolectric.nativeruntime.enableGraphics")) {
+    if (ShadowView.useRealGraphics()) {
       Bitmap.setDefaultDensity(displayMetrics.densityDpi);
     }
     Locale locale =
@@ -201,7 +203,7 @@
 
     RuntimeEnvironment.setAndroidFrameworkJarPath(sdkJarPath);
     Bootstrap.setDisplayConfiguration(androidConfiguration, displayMetrics);
-    RuntimeEnvironment.setActivityThread(ReflectionHelpers.newInstance(ActivityThread.class));
+    RuntimeEnvironment.setActivityThread(ReflectionHelpers.callConstructor(ActivityThread.class));
     ReflectionHelpers.setStaticField(
         ActivityThread.class, "sMainThreadHandler", new Handler(Looper.myLooper()));
 
@@ -321,7 +323,7 @@
       } catch (ClassNotFoundException e) {
         throw new RuntimeException(e);
       }
-      final Object appBindData = ReflectionHelpers.newInstance(appBindDataClass);
+      final Object appBindData = ReflectionHelpers.callConstructor(appBindDataClass);
       final _AppBindData_ _appBindData_ = reflector(_AppBindData_.class, appBindData);
       _appBindData_.setProcessName(parsedPackage.packageName);
       _appBindData_.setAppInfo(applicationInfo);
@@ -361,7 +363,7 @@
         // Preload fonts resources
         FontsContract.setApplicationContextForResources(application);
       }
-      registerBroadcastReceivers(application, appManifest);
+      registerBroadcastReceivers(application, appManifest, loadedApk);
 
       appResources.updateConfiguration(androidConfiguration, displayMetrics);
       // propagate any updates to configuration via RuntimeEnvironment.setQualifiers
@@ -412,6 +414,11 @@
       Path packageFile = appManifest.getApkFile();
       parsedPackage = ShadowPackageParser.callParsePackage(packageFile);
     }
+    if (parsedPackage != null
+        && parsedPackage.applicationInfo != null
+        && RuntimeEnvironment.getApiLevel() >= P) {
+      parsedPackage.applicationInfo.appComponentFactory = appManifest.getAppComponentFactory();
+    }
     return parsedPackage;
   }
 
@@ -584,9 +591,14 @@
   /** Create a file system safe directory path name for the current test. */
   @SuppressWarnings("DoNotCall")
   private String createTestDataDirRootPath(Method method) {
-    return method.getClass().getSimpleName()
-        + "_"
-        + method.getName().replaceAll("[^a-zA-Z0-9.-]", "_");
+    // Cap the size to 120 to avoid unnecessarily long directory names.
+    String directoryName =
+        (method.getDeclaringClass().getSimpleName() + "_" + method.getName())
+            .replaceAll("[^a-zA-Z0-9.-]", "_");
+    if (directoryName.length() > MAX_DATA_DIR_NAME_LENGTH) {
+      directoryName = directoryName.substring(0, MAX_DATA_DIR_NAME_LENGTH);
+    }
+    return directoryName;
   }
 
   @Override
@@ -691,15 +703,39 @@
         .toString();
   }
 
+  private static BroadcastReceiver newBroadcastReceiverFromP(
+      String receiverClassName, LoadedApk loadedApk) {
+    ClassLoader classLoader = Shadow.class.getClassLoader();
+    if (loadedApk == null || loadedApk.getAppFactory() == null) {
+      return (BroadcastReceiver) newInstanceOf(receiverClassName);
+    } else {
+      try {
+        return loadedApk.getAppFactory().instantiateReceiver(classLoader, receiverClassName, null);
+      } catch (ReflectiveOperationException e) {
+        Logger.warn(
+            "Failed to initialize receiver %s with AppComponentFactory %s: %s",
+            receiverClassName, loadedApk.getAppFactory(), e);
+      }
+    }
+    return null;
+  }
+
   // TODO move/replace this with packageManager
   @VisibleForTesting
-  static void registerBroadcastReceivers(Application application, AndroidManifest androidManifest) {
+  static void registerBroadcastReceivers(
+      Application application, AndroidManifest androidManifest, LoadedApk loadedApk) {
     for (BroadcastReceiverData receiver : androidManifest.getBroadcastReceivers()) {
       IntentFilter filter = new IntentFilter();
       for (String action : receiver.getActions()) {
         filter.addAction(action);
       }
-      application.registerReceiver((BroadcastReceiver) newInstanceOf(receiver.getName()), filter);
+      String receiverClassName = receiver.getName();
+      if (loadedApk != null && RuntimeEnvironment.getApiLevel() >= P) {
+        application.registerReceiver(
+            newBroadcastReceiverFromP(receiverClassName, loadedApk), filter);
+      } else {
+        application.registerReceiver((BroadcastReceiver) newInstanceOf(receiverClassName), filter);
+      }
     }
   }
 
diff --git a/robolectric/src/main/java/org/robolectric/android/internal/LocalActivityInvoker.java b/robolectric/src/main/java/org/robolectric/android/internal/LocalActivityInvoker.java
index 31a53df..fc10913 100644
--- a/robolectric/src/main/java/org/robolectric/android/internal/LocalActivityInvoker.java
+++ b/robolectric/src/main/java/org/robolectric/android/internal/LocalActivityInvoker.java
@@ -40,13 +40,13 @@
     startActivity(intent, /* activityOptions= */ null);
   }
 
-  // TODO(paigemca): Omitting @Override until androidx.test.monitor version can be upgraded
+  @Override
   public void startActivityForResult(Intent intent, @Nullable Bundle activityOptions) {
     isActivityLaunchedForResult = true;
     controller = getInstrumentation().startActivitySyncInternal(intent, activityOptions);
   }
 
-  // TODO(paigemca): Omitting @Override until androidx.test.monitor version can be upgraded
+  @Override
   public void startActivityForResult(Intent intent) {
     isActivityLaunchedForResult = true;
     startActivityForResult(intent, /* activityOptions= */ null);
diff --git a/robolectric/src/main/java/org/robolectric/internal/AndroidSandbox.java b/robolectric/src/main/java/org/robolectric/internal/AndroidSandbox.java
old mode 100755
new mode 100644
diff --git a/robolectric/src/main/java/org/robolectric/internal/dependency/PropertiesDependencyResolver.java b/robolectric/src/main/java/org/robolectric/internal/dependency/PropertiesDependencyResolver.java
old mode 100755
new mode 100644
diff --git a/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java b/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java
index 5b0b9e9..775823c 100644
--- a/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java
+++ b/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java
@@ -12,6 +12,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.regex.Pattern;
+import org.hamcrest.BaseMatcher;
 import org.hamcrest.Matcher;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -22,6 +23,52 @@
 /**
  * Allows tests to assert about the presence of log messages, and turns logged errors that are not
  * explicitly expected into test failures.
+ *
+ * <h3>Null Expectations</h3>
+ *
+ * <p>It is permitted to pass {@code null} for any expected value or matcher (i.e. for expected
+ * tags, messages or throwables) and this cause the expectation to ignore that attribute during
+ * matching. For example:
+ *
+ * <pre>{@code
+ * // Matches any INFO level log statement with the specified tag.
+ * logged.expectLogMessage(Log.INFO, "tag", null);
+ * // Matches any INFO level log statement with the message "expected", regardless of the tag.
+ * logged.expectLogMessage(Log.INFO, null, "message");
+ * }<pre>
+ *
+ * <p>However in general it is not recommended to use this behaviour, since it can cause tests to
+ * pass when they should have failed.
+ *
+ * <h3>Matchers Vs Expected Values</h3>
+ *
+ * <p>Using a {code Matcher} which can support substring matching and other non-trivial behaviour
+ * can be a good way to avoid brittle tests. However there is a  difference between the behaviour of
+ * methods which accept Hamcrest matchers and those which only accept expected value (e.g. {@code
+ * String} or {@code Pattern}).
+ *
+ * If an expected value is used to match a log statement, then duplicate expectations will be
+ * removed:
+ *
+ * <pre>{@code
+ * logged.expectLogMessage(Log.INFO, "tag", "exact message");
+ * // This call has no effect and only 1 log statements is expected.
+ * logged.expectLogMessage(Log.INFO, "tag", "exact message");
+ * }<pre>
+ *
+ * <p>When using a {@code Matcher} in any parameter, the existence of duplicate expectations can no
+ * longer be determined, so de-duplication does not occur:
+ *
+ * <pre>{@code
+ * logged.expectLogMessage(Log.INFO, "tag", Matchers.equalTo("exact message"));
+ * // This adds a 2nd expectation, so 2 log statements with the same value must be present.
+ * logged.expectLogMessage(Log.INFO, "tag", Matchers.equalTo("exact message"));
+ * }<pre>
+ *
+ * <p>This means that you may not be able to trivially convert from using one style of expectation
+ * to the other. In general it is preferable to match the number of expectations to the number of
+ * expected log messages (i.e. using the {@code Matcher} APIs) but some existing tests may rely on
+ * the older de-duplication behaviour.
  */
 public final class ExpectedLogMessagesRule implements TestRule {
   /** Tags that apps can't prevent. We exempt them globally. */
@@ -111,14 +158,31 @@
    * Adds an expected log statement. If this log is not printed during test execution, the test case
    * will fail.
    *
-   * <p>This will also match any log statement which contain a throwable as well. For verifying the
+   * <p>This will also match any log statement which contains a throwable as well. For verifying the
+   * throwable, please see {@link #expectLogMessageWithThrowable(int, String, Matcher<String>,
+   * Matcher<Throwable>)}.
+   *
+   * <p>Do not use this to suppress failures. Use this to test that expected error cases in your
+   * code cause log messages to be printed.
+   *
+   * <p>See class level documentation for a note about using {@code Matcher}s.
+   */
+  public void expectLogMessage(int level, String tag, Matcher<String> messageMatcher) {
+    expectLog(level, tag, messageMatcher, null);
+  }
+
+  /**
+   * Adds an expected log statement. If this log is not printed during test execution, the test case
+   * will fail.
+   *
+   * <p>This will also match any log statement which contains a throwable as well. For verifying the
    * throwable, please see {@link #expectLogMessageWithThrowable(int, String, String, Throwable)}.
    *
    * <p>Do not use this to suppress failures. Use this to test that expected error cases in your
    * code cause log messages to be printed.
    */
   public void expectLogMessage(int level, String tag, String message) {
-    expectedLogs.add(ExpectedLogItem.create(level, tag, message));
+    expectLogMessage(level, tag, MsgEq.of(message));
   }
 
   /**
@@ -133,21 +197,19 @@
    * code cause log messages to be printed.
    */
   public void expectLogMessagePattern(int level, String tag, Pattern messagePattern) {
-    expectedLogs.add(ExpectedLogItem.create(level, tag, messagePattern));
+    expectLogMessage(level, tag, MsgRegex.of(messagePattern));
   }
 
   /**
-   * Adds an expected log statement using a regular expression, with an extra check of {@link
-   * Matcher<Throwable>}. If this log is not printed during test execution, the test case will fail.
-   * When possible, log output should be made deterministic and {@link #expectLogMessage(int,
-   * String, String)} used instead.
+   * Adds an expected log statement with extra check of {@link Throwable}. If this log is not
+   * printed during test execution, the test case will fail. Do not use this to suppress failures.
+   * Use this to test that expected error cases in your code cause log messages to be printed.
    *
-   * <p>Do not use this to suppress failures. Use this to test that expected error cases in your
-   * code cause log messages to be printed.
+   * <p>See class level documentation for a note about using {@code Matcher}s.
    */
-  public void expectLogMessagePatternWithThrowableMatcher(
-      int level, String tag, Pattern messagePattern, Matcher<Throwable> throwableMatcher) {
-    expectedLogs.add(ExpectedLogItem.create(level, tag, messagePattern, throwableMatcher));
+  public void expectLogMessageWithThrowable(
+      int level, String tag, Matcher<String> messagMatcher, Matcher<Throwable> throwableMatcher) {
+    expectLog(level, tag, messagMatcher, throwableMatcher);
   }
 
   /**
@@ -157,17 +219,37 @@
    */
   public void expectLogMessageWithThrowable(
       int level, String tag, String message, Throwable throwable) {
-    expectLogMessageWithThrowableMatcher(level, tag, message, equalTo(throwable));
+    expectLogMessageWithThrowable(level, tag, MsgEq.of(message), equalTo(throwable));
+  }
+
+  /**
+   * Adds an expected log statement using a regular expression, with an extra check of {@link
+   * Matcher<Throwable>}. If this log is not printed during test execution, the test case will fail.
+   * When possible, log output should be made deterministic and {@link #expectLogMessage(int,
+   * String, String)} used instead.
+   *
+   * <p>See class level documentation for a note about using {@code Matcher}s.
+   */
+  public void expectLogMessagePatternWithThrowableMatcher(
+      int level, String tag, Pattern messagePattern, Matcher<Throwable> throwableMatcher) {
+    expectLogMessageWithThrowable(level, tag, MsgRegex.of(messagePattern), throwableMatcher);
   }
 
   /**
    * Adds an expected log statement with extra check of {@link Matcher}. If this log is not printed
    * during test execution, the test case will fail. Do not use this to suppress failures. Use this
    * to test that expected error cases in your code cause log messages to be printed.
+   *
+   * <p>See class level documentation for a note about using {@code Matcher}s.
    */
   public void expectLogMessageWithThrowableMatcher(
       int level, String tag, String message, Matcher<Throwable> throwableMatcher) {
-    expectedLogs.add(ExpectedLogItem.create(level, tag, message, throwableMatcher));
+    expectLogMessageWithThrowable(level, tag, MsgEq.of(message), throwableMatcher);
+  }
+
+  private void expectLog(
+      int level, String tag, Matcher<String> messageMatcher, Matcher<Throwable> throwableMatcher) {
+    expectedLogs.add(new ExpectedLogItem(level, tag, messageMatcher, throwableMatcher));
   }
 
   /**
@@ -196,12 +278,12 @@
     shouldIgnoreMissingLoggedTags = shouldIgnore;
   }
 
-  private static boolean updateExpected(
+  private boolean updateExpected(
       LogItem logItem, Map<ExpectedLogItem, Boolean> expectedLogItemMap) {
     for (ExpectedLogItem expectedLogItem : expectedLogItemMap.keySet()) {
       if (expectedLogItem.type == logItem.type
-          && equals(expectedLogItem.tag, logItem.tag)
-          && matchMessage(expectedLogItem, logItem.msg)
+          && Objects.equals(expectedLogItem.tag, logItem.tag)
+          && expectedLogItem.msgMatcher.matches(logItem.msg)
           && matchThrowable(expectedLogItem, logItem.throwable)) {
         expectedLogItemMap.put(expectedLogItem, true);
         return true;
@@ -211,22 +293,6 @@
     return false;
   }
 
-  private static boolean equals(String a, String b) {
-    return a == null ? b == null : a.equals(b);
-  }
-
-  private static boolean matchMessage(ExpectedLogItem logItem, String msg) {
-    if (logItem.msg != null) {
-      return logItem.msg.equals(msg);
-    }
-
-    if (logItem.msgPattern != null) {
-      return logItem.msgPattern.matcher(msg).matches();
-    }
-
-    return msg == null;
-  }
-
   private static boolean matchThrowable(ExpectedLogItem logItem, Throwable throwable) {
     if (logItem.throwableMatcher != null) {
       return logItem.throwableMatcher.matches(throwable);
@@ -239,34 +305,15 @@
   private static class ExpectedLogItem {
     final int type;
     final String tag;
-    final String msg;
-    final Pattern msgPattern;
+    // Either or both matches can be null ()
+    final Matcher<String> msgMatcher;
     final Matcher<Throwable> throwableMatcher;
 
-    static ExpectedLogItem create(int type, String tag, String msg) {
-      return new ExpectedLogItem(type, tag, msg, null, null);
-    }
-
-    static ExpectedLogItem create(int type, String tag, Pattern msg) {
-      return new ExpectedLogItem(type, tag, null, msg, null);
-    }
-
-    static ExpectedLogItem create(
-        int type, String tag, String msg, Matcher<Throwable> throwableMatcher) {
-      return new ExpectedLogItem(type, tag, msg, null, throwableMatcher);
-    }
-
-    static ExpectedLogItem create(
-        int type, String tag, Pattern pattern, Matcher<Throwable> throwableMatcher) {
-      return new ExpectedLogItem(type, tag, null, pattern, throwableMatcher);
-    }
-
     private ExpectedLogItem(
-        int type, String tag, String msg, Pattern msgPattern, Matcher<Throwable> throwableMatcher) {
+        int type, String tag, Matcher<String> msgMatcher, Matcher<Throwable> throwableMatcher) {
       this.type = type;
       this.tag = tag;
-      this.msg = msg;
-      this.msgPattern = msgPattern;
+      this.msgMatcher = msgMatcher;
       this.throwableMatcher = throwableMatcher;
     }
 
@@ -283,47 +330,99 @@
       ExpectedLogItem log = (ExpectedLogItem) o;
       return type == log.type
           && !(tag != null ? !tag.equals(log.tag) : log.tag != null)
-          && !(msg != null ? !msg.equals(log.msg) : log.msg != null)
-          && !(msgPattern != null ? !isEqual(msgPattern, log.msgPattern) : log.msgPattern != null)
-          && !(throwableMatcher != null
-              ? !throwableMatcher.equals(log.throwableMatcher)
-              : log.throwableMatcher != null);
+          && Objects.equals(msgMatcher, log.msgMatcher)
+          && Objects.equals(throwableMatcher, log.throwableMatcher);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(type, tag, msg, hash(msgPattern), throwableMatcher);
+      return Objects.hash(type, tag, msgMatcher, throwableMatcher);
     }
 
     @Override
     public String toString() {
+      String msgStr = (msgMatcher == null) ? "" : (", msg=" + msgMatcher);
       String throwableStr = (throwableMatcher == null) ? "" : (", throwable=" + throwableMatcher);
-      return "ExpectedLogItem{"
-          + "timeString='"
-          + null
-          + '\''
-          + ", type="
-          + type
-          + ", tag='"
-          + tag
-          + '\''
-          + ", msg='"
-          + (msg != null ? msg : msgPattern)
-          + '\''
-          + throwableStr
-          + '}';
+      return String.format(
+          "ExpectedLogItem{timeString='null', type=%s, tag='%s'%s%s}",
+          type, tag, msgStr, throwableStr);
+    }
+  }
+
+  // Similar to IsEqualTo matcher in Hamcrest, but supports equals/hashCode for de-duplication.
+  private static final class MsgEq extends BaseMatcher<String> {
+    static Matcher<String> of(String msg) {
+      return msg != null ? new MsgEq(msg) : null;
+    }
+
+    private final String msg;
+
+    private MsgEq(String msg) {
+      this.msg = msg;
+    }
+
+    @Override
+    public boolean matches(Object other) {
+      // Allow direct cast since we only use this matcher in a type-safe way.
+      return msg.equals((String) other);
+    }
+
+    // Designed to match legacy toString() behaviour - do not modify.
+    @Override
+    public void describeTo(org.hamcrest.Description description) {
+      description.appendText("'" + msg + "'");
+    }
+
+    // This matches legacy behaviour to allow ExpectedLogItem to de-duplicate expectations.
+    @Override
+    public boolean equals(Object matcher) {
+      return matcher instanceof MsgEq && msg.equals(((MsgEq) matcher).msg);
+    }
+
+    @Override
+    public int hashCode() {
+      return msg.hashCode() ^ MsgEq.class.hashCode();
+    }
+  }
+
+  // Similar to MatchesPattern in Hamcrest, but supports equals/hashCode for de-duplication.
+  private static final class MsgRegex extends BaseMatcher<String> {
+    static Matcher<String> of(Pattern pattern) {
+      return pattern != null ? new MsgRegex(pattern) : null;
+    }
+
+    private final Pattern pattern;
+
+    private MsgRegex(Pattern pattern) {
+      this.pattern = pattern;
+    }
+
+    @Override
+    public boolean matches(Object other) {
+      // Allow direct cast since we only use this matcher in a type-safe way.
+      return pattern.matcher((String) other).matches();
+    }
+
+    // Designed to match legacy toString() behaviour - do not modify.
+    @Override
+    public void describeTo(org.hamcrest.Description description) {
+      description.appendText("'" + pattern + "'");
+    }
+
+    // This matches legacy behaviour to allow ExpectedLogItem to de-duplicate regex expectations.
+    @Override
+    public boolean equals(Object other) {
+      return other instanceof MsgRegex ? isEqual(pattern, ((MsgRegex) other).pattern) : false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(pattern.pattern(), pattern.flags());
     }
 
     /** Returns true if the pattern and flags compiled in a {@link Pattern} were the same. */
     private static boolean isEqual(Pattern a, Pattern b) {
-      return a != null && b != null
-          ? a.pattern().equals(b.pattern()) && a.flags() == b.flags()
-          : Objects.equals(a, b);
-    }
-
-    /** Returns hash for a {@link Pattern} based on the pattern and flags it was compiled with. */
-    private static int hash(Pattern pattern) {
-      return pattern == null ? 0 : Objects.hash(pattern.pattern(), pattern.flags());
+      return a.pattern().equals(b.pattern()) && a.flags() == b.flags();
     }
   }
 }
diff --git a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
index c8b2693..f45a0ef 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
@@ -1,26 +1,5 @@
 package org.robolectric.plugins;
 
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
-import static android.os.Build.VERSION_CODES.LOLLIPOP;
-import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
-import static android.os.Build.VERSION_CODES.M;
-import static android.os.Build.VERSION_CODES.N;
-import static android.os.Build.VERSION_CODES.N_MR1;
-import static android.os.Build.VERSION_CODES.O;
-import static android.os.Build.VERSION_CODES.O_MR1;
-import static android.os.Build.VERSION_CODES.P;
-import static android.os.Build.VERSION_CODES.Q;
-import static android.os.Build.VERSION_CODES.R;
-import static android.os.Build.VERSION_CODES.S;
-import static android.os.Build.VERSION_CODES.S_V2;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-import static android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
-
-import android.os.Build;
-
 import com.google.auto.service.AutoService;
 import com.google.common.base.Preconditions;
 import java.net.URL;
@@ -38,6 +17,26 @@
 import org.robolectric.pluginapi.Sdk;
 import org.robolectric.pluginapi.SdkProvider;
 import org.robolectric.util.Util;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
+import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.S_V2;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import android.os.Build;
 
 /**
  * Robolectric's default {@link SdkProvider}.
@@ -85,8 +84,6 @@
     knownSdks.put(S, new DefaultSdk(S, "12", "7732740", "REL", 9));
     knownSdks.put(S_V2, new DefaultSdk(S_V2, "12.1", "8229987", "REL", 9));
     knownSdks.put(TIRAMISU, new DefaultSdk(TIRAMISU, "13", "9030017", "Tiramisu", 9));
-    // TODO(rexhoffman): should this have a dedicated mechanism?  Should we maintain a known good version?
-    knownSdks.put(CUR_DEVELOPMENT, new DefaultSdk(CUR_DEVELOPMENT, "current", "r0", "UpsideDownCake", 9));
   }
 
   @Override
diff --git a/robolectric/src/main/java/org/robolectric/plugins/HierarchicalConfigurationStrategy.java b/robolectric/src/main/java/org/robolectric/plugins/HierarchicalConfigurationStrategy.java
index cfa250f..d172761 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/HierarchicalConfigurationStrategy.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/HierarchicalConfigurationStrategy.java
@@ -10,6 +10,7 @@
 import javax.annotation.Priority;
 import org.robolectric.pluginapi.config.ConfigurationStrategy;
 import org.robolectric.pluginapi.config.Configurer;
+import org.robolectric.util.PerfStatsCollector;
 
 /**
  * Robolectric's default {@link ConfigurationStrategy}.
@@ -23,6 +24,7 @@
 
   /** The cache is sized to avoid repeated resolutions for any node. */
   private int highWaterMark = 0;
+
   private final Map<String, Object[]> cache =
       new LinkedHashMap<String, Object[]>() {
         @Override
@@ -47,12 +49,16 @@
   @Override
   public ConfigurationImpl getConfig(Class<?> testClass, Method method) {
     final Counter counter = new Counter();
-    Object[] configs = cache(testClass.getName() + "/" + method.getName(), counter, s -> {
-      counter.incr();
-      Object[] methodConfigs = getConfigs(counter,
-          configurer -> configurer.getConfigFor(method));
-      return merge(getFirstClassConfig(testClass, counter), methodConfigs);
-    });
+    Object[] configs =
+        cache(
+            testClass.getName() + "/" + method.getName(),
+            counter,
+            s -> {
+              counter.incr();
+              Object[] methodConfigs =
+                  getConfigs(counter, configurer -> configurer.getConfigFor(method));
+              return merge(getFirstClassConfig(testClass, counter), methodConfigs);
+            });
 
     ConfigurationImpl testConfig = new ConfigurationImpl();
     for (int i = 0; i < configurers.length; i++) {
@@ -64,19 +70,28 @@
 
   private Object[] getFirstClassConfig(Class<?> testClass, Counter counter) {
     // todo: should parent class configs have lower precedence than package configs?
-    return cache("first:" + testClass, counter, s -> {
+    return cache(
+        "first:" + testClass,
+        counter,
+        s -> {
           Object[] configsForClass = getClassConfig(testClass, counter);
-      Package pkg = testClass.getPackage();
-      Object[] configsForPackage = getPackageConfig(pkg == null ? "" : pkg.getName(), counter);
+          Package pkg = testClass.getPackage();
+          Object[] configsForPackage =
+              PerfStatsCollector.getInstance()
+                  .measure(
+                      "getPackageConfig",
+                      () -> getPackageConfig(pkg == null ? "" : pkg.getName(), counter));
           return merge(configsForPackage, configsForClass);
-        }
-    );
+        });
   }
 
   private Object[] getPackageConfig(String packageName, Counter counter) {
-    return cache(packageName, counter, s -> {
-          Object[] packageConfigs = getConfigs(counter,
-              configurer -> configurer.getConfigFor(packageName));
+    return cache(
+        packageName,
+        counter,
+        s -> {
+          Object[] packageConfigs =
+              getConfigs(counter, configurer -> configurer.getConfigFor(packageName));
           String parentPackage = parentPackage(packageName);
           if (parentPackage == null) {
             return merge(defaultConfigs, packageConfigs);
@@ -96,16 +111,20 @@
   }
 
   private Object[] getClassConfig(Class<?> testClass, Counter counter) {
-    return cache(testClass.getName(), counter, s -> {
-      Object[] classConfigs = getConfigs(counter, configurer -> configurer.getConfigFor(testClass));
+    return cache(
+        testClass.getName(),
+        counter,
+        s -> {
+          Object[] classConfigs =
+              getConfigs(counter, configurer -> configurer.getConfigFor(testClass));
 
-      Class<?> superclass = testClass.getSuperclass();
-      if (superclass != Object.class) {
-        Object[] superclassConfigs = getClassConfig(superclass, counter);
-        return merge(superclassConfigs, classConfigs);
-      }
-      return classConfigs;
-    });
+          Class<?> superclass = testClass.getSuperclass();
+          if (superclass != Object.class) {
+            Object[] superclassConfigs = getClassConfig(superclass, counter);
+            return merge(superclassConfigs, classConfigs);
+          }
+          return classConfigs;
+        });
   }
 
   private Object[] cache(String name, Counter counter, Function<String, Object[]> fn) {
@@ -146,11 +165,10 @@
       Configurer configurer = configurers[i];
       Object childConfig = childConfigs[i];
       Object parentConfig = parentConfigs[i];
-      objects[i] = childConfig == null
-          ? parentConfig
-          : parentConfig == null
-              ? childConfig
-              : configurer.merge(parentConfig, childConfig);
+      objects[i] =
+          childConfig == null
+              ? parentConfig
+              : parentConfig == null ? childConfig : configurer.merge(parentConfig, childConfig);
     }
     return objects;
   }
diff --git a/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java
new file mode 100644
index 0000000..22df750
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java
@@ -0,0 +1,22 @@
+package org.robolectric;
+
+import android.app.AppComponentFactory;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithEmptyActionReceiver;
+import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithOneActionReceiver;
+
+public final class CustomAppComponentFactory extends AppComponentFactory {
+  @Override
+  public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)
+      throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+    if (className != null) {
+      if (className.contains(CustomConstructorWithOneActionReceiver.class.getName())) {
+        return new CustomConstructorWithOneActionReceiver(100);
+      } else if (className.contains(CustomConstructorWithEmptyActionReceiver.class.getName())) {
+        return new CustomConstructorWithEmptyActionReceiver(100);
+      }
+    }
+    return super.instantiateReceiver(cl, className, intent);
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/CustomConstructorReceiverWrapper.java b/robolectric/src/test/java/org/robolectric/CustomConstructorReceiverWrapper.java
new file mode 100644
index 0000000..1132f6e
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/CustomConstructorReceiverWrapper.java
@@ -0,0 +1,32 @@
+package org.robolectric;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class CustomConstructorReceiverWrapper {
+  private static class CustomConstructorReceiver extends BroadcastReceiver {
+    private final int intValue;
+
+    public CustomConstructorReceiver(int intValue) {
+      // We don't use intValue actually, and we only want to use this class to test the
+      // initialization of BroadcastReceiver with a custom constructor.
+      this.intValue = intValue;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {}
+  }
+
+  public static class CustomConstructorWithOneActionReceiver extends CustomConstructorReceiver {
+    public CustomConstructorWithOneActionReceiver(int intValue) {
+      super(intValue);
+    }
+  }
+
+  public static class CustomConstructorWithEmptyActionReceiver extends CustomConstructorReceiver {
+    public CustomConstructorWithEmptyActionReceiver(int intValue) {
+      super(intValue);
+    }
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
index c0108de..737af48 100644
--- a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
+++ b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
@@ -268,6 +268,24 @@
   }
 
   @Test
+  public void failedTest_shouldStillReportPerfStats() throws Exception {
+    List<Metric> metrics = new ArrayList<>();
+    PerfStatsReporter reporter = (metadata, metrics1) -> metrics.addAll(metrics1);
+
+    RobolectricTestRunner runner =
+        new SingleSdkRobolectricTestRunner(
+            TestThatFails.class,
+            RobolectricTestRunner.defaultInjector()
+                .bind(PerfStatsReporter[].class, new PerfStatsReporter[] {reporter})
+                .build());
+
+    runner.run(notifier);
+
+    Set<String> metricNames = metrics.stream().map(Metric::getName).collect(toSet());
+    assertThat(metricNames).contains("initialization");
+  }
+
+  @Test
   public void shouldResetThreadInterrupted() throws Exception {
     RobolectricTestRunner runner = new SingleSdkRobolectricTestRunner(TestWithInterrupt.class);
     runner.run(notifier);
@@ -358,6 +376,15 @@
 
   @Ignore
   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
+  public static class TestThatFails {
+    @Test
+    public void first() throws Exception {
+      throw new AssertionError();
+    }
+  }
+
+  @Ignore
+  @FixMethodOrder(MethodSorters.NAME_ASCENDING)
   @Config(application = TestWithBrokenAppCreate.MyTestApplication.class)
   @LazyApplication(LazyLoad.OFF)
   public static class TestWithBrokenAppCreate {
diff --git a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java
index 942b923..26a1970 100644
--- a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java
+++ b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java
@@ -1,18 +1,25 @@
 package org.robolectric;
 
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.os.Build.VERSION_CODES.KITKAT;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
 
 import android.app.Application;
+import android.content.Context;
 import android.content.res.Configuration;
+import android.text.format.DateUtils;
+import android.util.DisplayMetrics;
 import android.view.Surface;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.shadows.ShadowDisplay;
 import org.robolectric.util.Scheduler;
@@ -110,9 +117,36 @@
   }
 
   @Test
+  public void testSetFontScale_updatesFontScale() {
+    Context context = ApplicationProvider.getApplicationContext();
+    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+
+    assertThat(context.getResources().getConfiguration().fontScale).isEqualTo(1.0f);
+    assertThat(displayMetrics.scaledDensity).isEqualTo(displayMetrics.density);
+    assertThat(RuntimeEnvironment.getFontScale()).isEqualTo(1.0f);
+
+    RuntimeEnvironment.setFontScale(1.3f);
+
+    assertThat(context.getResources().getConfiguration().fontScale).isEqualTo(1.3f);
+    assertThat(displayMetrics.scaledDensity).isEqualTo(displayMetrics.density * 1.3f);
+    assertThat(RuntimeEnvironment.getFontScale()).isEqualTo(1.3f);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
   public void testGetRotation() {
     RuntimeEnvironment.setQualifiers("+land");
     int screenRotation = ShadowDisplay.getDefaultDisplay().getRotation();
-    assertThat(screenRotation).isEqualTo(Surface.ROTATION_0);
+    assertThat(screenRotation).isEqualTo(Surface.ROTATION_90);
+  }
+
+  @Test
+  @Config(minSdk = KITKAT)
+  public void setQualifiers_resetsDateUtilsFormatCache() {
+    RuntimeEnvironment.setQualifiers("ar-rXB");
+    // Populate the DateUtils static format cache.
+    String unused = DateUtils.formatElapsedTime(120);
+    RuntimeEnvironment.setQualifiers("en-rUS");
+    assertThat(DateUtils.formatElapsedTime(120)).isEqualTo("02:00");
   }
 }
diff --git a/robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java
index 87ddb07..0428c44 100644
--- a/robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java
@@ -3,9 +3,9 @@
 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
 import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
 
 import android.animation.Animator;
@@ -31,7 +31,7 @@
 
   @Before
   public void setup() throws Exception {
-    assumeTrue(useLegacy());
+    assume().that(useLegacy()).isTrue();
     resources = ApplicationProvider.getApplicationContext().getResources();
   }
 
diff --git a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java
index b895d65..15ca52c 100644
--- a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java
@@ -2,7 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.O;
 import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeTrue;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
 
 import android.content.res.Configuration;
@@ -32,7 +32,7 @@
 
   @Before
   public void setUp() {
-    assumeTrue(useLegacy());
+    assume().that(useLegacy()).isTrue();
 
     optsForO = RuntimeEnvironment.getApiLevel() >= O
         ? "nowidecg-lowdr-"
@@ -71,7 +71,11 @@
 
   private void checkForPollutionHelper() {
     assertThat(RuntimeEnvironment.getQualifiers())
-        .isEqualTo("en-rUS-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-" + optsForO + "port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav-v" + Build.VERSION.RESOURCES_SDK_INT);
+        .isEqualTo(
+            "en-rUS-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-"
+                + optsForO
+                + "port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav-v"
+                + Build.VERSION.RESOURCES_SDK_INT);
 
     View view =
         LayoutInflater.from(ApplicationProvider.getApplicationContext())
@@ -97,7 +101,10 @@
     assertThat(resId).isNotNull();
     assertThat(resourceProvider.getResName(resId)).isEqualTo(internalResource);
 
-    Class<?> internalRIdClass = Robolectric.class.getClassLoader().loadClass("com.android.internal.R$" + internalResource.type);
+    Class<?> internalRIdClass =
+        Robolectric.class
+            .getClassLoader()
+            .loadClass("com.android.internal.R$" + internalResource.type);
     int internalResourceId;
     internalResourceId = (Integer) internalRIdClass.getDeclaredField(internalResource.name).get(null);
     assertThat(resId).isEqualTo(internalResourceId);
diff --git a/robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java b/robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java
index 0ae4675..d4395c5 100644
--- a/robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java
@@ -1,7 +1,7 @@
 package org.robolectric.android;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeTrue;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
 
 import android.os.Build;
@@ -17,7 +17,7 @@
 public class ResourceTableFactoryIntegrationTest {
   @Test
   public void shouldIncludeStyleableAttributesThatDoNotHaveACorrespondingEntryInAttrClass() throws Exception {
-    assumeTrue(useLegacy());
+    assume().that(useLegacy()).isTrue();
     // This covers a corner case in Framework resources where an attribute is mentioned in a styleable array, e.g: R.styleable.Toolbar_buttonGravity but there is no corresponding R.attr.buttonGravity
     assertThat(RuntimeEnvironment.getSystemResourceTable()
           .getResourceId(new ResName("android", "attr", "buttonGravity"))).isGreaterThan(0);
diff --git a/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java b/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java
index b57f606..0c8d977 100644
--- a/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java
@@ -2,11 +2,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.TruthJUnit.assume;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.asList;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 
 import android.app.Application;
 import android.content.res.XmlResourceParser;
@@ -276,7 +276,7 @@
 
   @Test
   public void testIsWhitespace() throws Exception {
-    assumeTrue(RuntimeEnvironment.useLegacyResources());
+    assume().that(RuntimeEnvironment.useLegacyResources()).isTrue();
 
     XmlResourceParserImpl parserImpl = (XmlResourceParserImpl) parser;
     assertThat(parserImpl.isWhitespace("bar")).isFalse();
diff --git a/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java b/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java
index 7c73aaf..181a801 100644
--- a/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java
@@ -11,6 +11,7 @@
 
 import android.app.Activity;
 import android.app.Fragment;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -300,6 +301,31 @@
         .isEqualTo(newFontScale);
   }
 
+  @Config(minSdk = P)
+  @Test
+  public void configurationChange_windowConfigurationChanges_doesNotRecreateActivity() {
+    Configuration config =
+        new Configuration(
+            ApplicationProvider.getApplicationContext().getResources().getConfiguration());
+    WindowConfiguration windowConfiguration = config.windowConfiguration;
+    windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+    ActivityController<ConfigAwareActivity> controller =
+        Robolectric.buildActivity(ConfigAwareActivity.class).setup();
+    transcript.clear();
+    controller.configurationChange(config);
+
+    assertThat(transcript).containsAtLeast("onConfigurationChanged", "View.onConfigurationChanged");
+    assertThat(
+            controller
+                .get()
+                .getResources()
+                .getConfiguration()
+                .windowConfiguration
+                .getWindowingMode())
+        .isEqualTo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+  }
+
   @Test
   @Config(maxSdk = O_MR1)
   public void configurationChange_callsLifecycleMethodsAndAppliesConfigWhenAnyNonManaged_beforeP() {
diff --git a/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentCreateApplicationTest.java b/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentCreateApplicationTest.java
index 6edc428..dc6ac31 100644
--- a/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentCreateApplicationTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentCreateApplicationTest.java
@@ -88,7 +88,7 @@
     Application application = AndroidTestEnvironment.createApplication(appManifest, null,
         new ApplicationInfo());
     shadowOf(application).callAttach(RuntimeEnvironment.systemContext);
-    registerBroadcastReceivers(application, appManifest);
+    registerBroadcastReceivers(application, appManifest, null);
 
     List<ShadowApplication.Wrapper> receivers = shadowOf(application).getRegisteredReceivers();
     assertThat(receivers).hasSize(1);
diff --git a/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java
index 0511881..3756994 100644
--- a/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java
@@ -2,8 +2,8 @@
 
 import static android.os.Build.VERSION_CODES.O;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 import static org.robolectric.annotation.ConscryptMode.Mode.OFF;
 import static org.robolectric.annotation.ConscryptMode.Mode.ON;
 import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
@@ -241,7 +241,7 @@
   @Test
   public void testResourceNotFound() {
     // not relevant for binary resources mode
-    assumeTrue(bootstrapWrapper.isLegacyResources());
+    assume().that(bootstrapWrapper.isLegacyResources()).isTrue();
 
     try {
       bootstrapWrapper.changeAppManifest(new ThrowingManifest(bootstrapWrapper.getAppManifest()));
@@ -356,4 +356,14 @@
     assertThat(RuntimeEnvironment.getQualifiers()).contains("w640dp-h480dp");
     assertThat(RuntimeEnvironment.getQualifiers()).contains("land");
   }
+
+  @Test
+  public void
+      thisTestNameHasMoreThan255Characters1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890() {
+    bootstrapWrapper.callSetUpApplicationState();
+    ApplicationInfo applicationInfo =
+        ApplicationProvider.getApplicationContext().getApplicationInfo();
+    assertThat(applicationInfo.dataDir).isNotNull();
+    assertThat(new File(applicationInfo.dataDir).isDirectory()).isTrue();
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/interceptors/AndroidInterceptorsIntegrationTest.java b/robolectric/src/test/java/org/robolectric/interceptors/AndroidInterceptorsIntegrationTest.java
index 679b456..af1d8ba 100644
--- a/robolectric/src/test/java/org/robolectric/interceptors/AndroidInterceptorsIntegrationTest.java
+++ b/robolectric/src/test/java/org/robolectric/interceptors/AndroidInterceptorsIntegrationTest.java
@@ -187,6 +187,6 @@
         callsite
             .dynamicInvoker()
             .invokeWithArguments(
-                Arrays.stream(params).map(param -> param.val).collect(Collectors.toList()));
+                Arrays.stream(params).map(param -> param.value).collect(Collectors.toList()));
   }
 }
diff --git a/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java
old mode 100755
new mode 100644
diff --git a/robolectric/src/test/java/org/robolectric/internal/dependency/PropertiesDependencyResolverTest.java b/robolectric/src/test/java/org/robolectric/internal/dependency/PropertiesDependencyResolverTest.java
old mode 100755
new mode 100644
diff --git a/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java b/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java
index a84ad06..5821af5 100644
--- a/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java
+++ b/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java
@@ -1,11 +1,12 @@
 package org.robolectric.junit.rules;
 
-import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.Matchers.instanceOf;
 
 import android.util.Log;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.util.regex.Pattern;
 import org.hamcrest.Description;
+import org.hamcrest.Matchers;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.Rule;
 import org.junit.Test;
@@ -209,9 +210,24 @@
   }
 
   @Test
-  public void expectLogMessageWithPattern_duplicatePatterns() {
+  public void expectLogMessage_duplicateExpectatedValues_areDeduplicated() {
+    Log.e("Mytag", "message1");
+    rule.expectLogMessage(Log.ERROR, "Mytag", "message1");
+    rule.expectLogMessage(Log.ERROR, "Mytag", "message1");
+  }
+
+  @Test
+  public void expectLogMessageWithPattern_duplicateExpectatedValues_areDeduplicated() {
     Log.e("Mytag", "message1");
     rule.expectLogMessagePattern(Log.ERROR, "Mytag", Pattern.compile("message1"));
     rule.expectLogMessagePattern(Log.ERROR, "Mytag", Pattern.compile("message1"));
   }
+
+  @Test
+  public void expectLogMessage_duplicateMatchers_areNotDeduplicated() {
+    Log.e("Mytag", "message1");
+    rule.expectLogMessage(Log.ERROR, "Mytag", Matchers.equalTo("message1"));
+    rule.expectLogMessage(Log.ERROR, "Mytag", Matchers.equalTo("message1"));
+    expectedException.expect(Matchers.isA(AssertionError.class));
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java
index 8b493ba..dc20266 100644
--- a/robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java
+++ b/robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java
@@ -2,7 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN;
 import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeTrue;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.robolectric.util.TestUtil.sdkResources;
 
 import org.junit.Before;
@@ -18,14 +18,16 @@
 
   @Before
   public void setUp() throws Exception {
-    assumeTrue(RuntimeEnvironment.useLegacyResources());
+    assume().that(RuntimeEnvironment.useLegacyResources()).isTrue();
     ResourcePath resourcePath = sdkResources(JELLY_BEAN);
     resourceTable = new ResourceTableFactory().newResourceTable("android", resourcePath);
   }
 
   @Test
   public void testStyleDataIsLoadedCorrectly() throws Exception {
-    TypedResource typedResource = resourceTable.getValue(new ResName("android", "style", "Theme_Holo"), new ResTable_config());
+    TypedResource typedResource =
+        resourceTable.getValue(
+            new ResName("android", "style", "Theme_Holo"), new ResTable_config());
     StyleData styleData = (StyleData) typedResource.getData();
     assertThat(styleData.getName()).isEqualTo("Theme_Holo");
     assertThat(styleData.getParent()).isEqualTo("Theme");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/AssociationInfoBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AssociationInfoBuilderTest.java
new file mode 100644
index 0000000..b1d8489
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/AssociationInfoBuilderTest.java
@@ -0,0 +1,54 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.companion.AssociationInfo;
+import android.net.MacAddress;
+import android.os.Build.VERSION_CODES;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+public final class AssociationInfoBuilderTest {
+  private static final int ID = 7;
+  private static final int USER_ID = 8;
+  private static final String PACKAGE_NAME = "com.google.foo";
+  private static final String DEVICE_MAC_ADDRESS = "AA:BB:CC:DD:EE:FF";
+  private static final String DISPLAY_NAME = "Display Name";
+  private static final String DEVICE_PROFILE = "Device Profile";
+  private static final boolean SELF_MANAGED = true;
+  private static final boolean NOTIFY_ON_DEVICE_NEARBY = true;
+  private static final long APPROVED_MS = 1234L;
+  private static final long LAST_TIME_CONNECTED_MS = 5678L;
+
+  @Test
+  @Config(minSdk = VERSION_CODES.TIRAMISU)
+  public void obtain() {
+    AssociationInfo info =
+        AssociationInfoBuilder.newBuilder()
+            .setId(ID)
+            .setUserId(USER_ID)
+            .setPackageName(PACKAGE_NAME)
+            .setDeviceMacAddress(DEVICE_MAC_ADDRESS)
+            .setDisplayName(DISPLAY_NAME)
+            .setDeviceProfile(DEVICE_PROFILE)
+            .setSelfManaged(SELF_MANAGED)
+            .setNotifyOnDeviceNearby(NOTIFY_ON_DEVICE_NEARBY)
+            .setApprovedMs(APPROVED_MS)
+            .setLastTimeConnectedMs(LAST_TIME_CONNECTED_MS)
+            .build();
+
+    assertThat(info.getId()).isEqualTo(ID);
+    assertThat(info.getUserId()).isEqualTo(USER_ID);
+    assertThat(info.getPackageName()).isEqualTo(PACKAGE_NAME);
+    assertThat(info.getDeviceMacAddress()).isEqualTo(MacAddress.fromString(DEVICE_MAC_ADDRESS));
+    assertThat(info.getDisplayName().toString()).isEqualTo(DISPLAY_NAME);
+    assertThat(info.getDeviceProfile()).isEqualTo(DEVICE_PROFILE);
+    assertThat(info.isSelfManaged()).isEqualTo(SELF_MANAGED);
+    assertThat(info.isNotifyOnDeviceNearby()).isEqualTo(NOTIFY_ON_DEVICE_NEARBY);
+    assertThat(info.getTimeApprovedMs()).isEqualTo(APPROVED_MS);
+    assertThat(info.getLastTimeConnectedMs()).isEqualTo(LAST_TIME_CONNECTED_MS);
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/BluetoothConnectionManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/BluetoothConnectionManagerTest.java
new file mode 100644
index 0000000..45e377b
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/BluetoothConnectionManagerTest.java
@@ -0,0 +1,126 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class BluetoothConnectionManagerTest {
+  private static final String REMOTE_ADDRESS1 = "R-A-1";
+  private static final String REMOTE_ADDRESS2 = "R-A-2";
+
+  private final BluetoothConnectionManager manager = BluetoothConnectionManager.getInstance();
+
+  @After
+  public void tearDown() {
+    manager.resetConnections();
+  }
+
+  @Test
+  public void test_getInstance() {
+    BluetoothConnectionManager a = BluetoothConnectionManager.getInstance();
+    assertThat(a).isNotNull();
+    assertThat(a).isEqualTo(manager);
+    assertThat(a.isConnected(REMOTE_ADDRESS1)).isFalse();
+    assertThat(manager.isConnected(REMOTE_ADDRESS1)).isFalse();
+    a.registerGattClientConnection(REMOTE_ADDRESS1);
+    assertThat(a.isConnected(REMOTE_ADDRESS1)).isTrue();
+    assertThat(manager.isConnected(REMOTE_ADDRESS1)).isTrue();
+  }
+
+  @Test
+  public void test_hasGattClientConnection() {
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.hasGattClientConnection(REMOTE_ADDRESS1)).isTrue();
+    assertThat(this.manager.hasGattClientConnection(REMOTE_ADDRESS2)).isFalse();
+  }
+
+  @Test
+  public void test_hasGattClientConnection_multiple() {
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS1);
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS2);
+    assertThat(this.manager.hasGattClientConnection(REMOTE_ADDRESS1)).isTrue();
+    assertThat(this.manager.hasGattClientConnection(REMOTE_ADDRESS2)).isTrue();
+  }
+
+  @Test
+  public void test_hasGattServerConnection() {
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.hasGattServerConnection(REMOTE_ADDRESS1)).isTrue();
+    assertThat(this.manager.hasGattServerConnection(REMOTE_ADDRESS2)).isFalse();
+  }
+
+  @Test
+  public void test_hasGattServerConnection_multiple() {
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS2);
+    assertThat(this.manager.hasGattServerConnection(REMOTE_ADDRESS1)).isTrue();
+    assertThat(this.manager.hasGattServerConnection(REMOTE_ADDRESS2)).isTrue();
+  }
+
+  @Test
+  public void test_isNotConnected_noConnection() {
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isFalse();
+  }
+
+  @Test
+  public void test_isConnected_gattServerConnection() {
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isTrue();
+  }
+
+  @Test
+  public void test_isConnected_gattClientConnection() {
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isTrue();
+  }
+
+  @Test
+  public void test_isConnected_gattClientandServerConnection() {
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS1);
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isTrue();
+  }
+
+  @Test
+  public void test_isNotConnected_unregistedGattClientConnection() {
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS1);
+    this.manager.unregisterGattClientConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isFalse();
+  }
+
+  @Test
+  public void test_isNotConnected_unregistedGattServerConnection() {
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    this.manager.unregisterGattServerConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isFalse();
+  }
+
+  @Test
+  public void test_isConnected_gattClientConnection_unregistedGattServerConnection() {
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS1);
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    this.manager.unregisterGattServerConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isTrue();
+  }
+
+  @Test
+  public void test_isConnected_gattServerConnection_unregistedGattClientConnection() {
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS1);
+    this.manager.unregisterGattClientConnection(REMOTE_ADDRESS1);
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isTrue();
+  }
+
+  @Test
+  public void test_resetConnections() {
+    this.manager.registerGattServerConnection(REMOTE_ADDRESS1);
+    this.manager.registerGattClientConnection(REMOTE_ADDRESS2);
+    this.manager.resetConnections();
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS1)).isFalse();
+    assertThat(this.manager.isConnected(REMOTE_ADDRESS2)).isFalse();
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellIdentityNrBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellIdentityNrBuilderTest.java
new file mode 100644
index 0000000..8474b21
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/CellIdentityNrBuilderTest.java
@@ -0,0 +1,90 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link CellIdentityNrBuilder} */
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = Build.VERSION_CODES.Q)
+public class CellIdentityNrBuilderTest {
+
+  private static final int PCI = 1;
+  private static final int TAC = 2;
+  private static final int NRARFCN = 4;
+  private static final int[] BANDS =
+      new int[] {
+        AccessNetworkConstants.NgranBands.BAND_1, AccessNetworkConstants.NgranBands.BAND_2
+      };
+  private static final String MCC = "310";
+  private static final String MNC = "260";
+  private static final int NCI = 0;
+  private static final String LONG_OPERATOR_NAME = "long operator name";
+  private static final String SHORT_OPERATOR_NAME = "short operator name";
+  private static final ImmutableList<String> ADDITIONAL_PLMNS = ImmutableList.of("310240");
+
+  @Test
+  public void build_noArguments() {
+    // The intent is to primarily verify that there are no issues setting default values i.e., no
+    // exceptions thrown or invalid inputs.
+    CellIdentityNr cellIdentity = CellIdentityNrBuilder.newBuilder().build();
+
+    assertThat(cellIdentity.getPci()).isEqualTo(CellInfo.UNAVAILABLE);
+  }
+
+  @Test
+  @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.R)
+  public void build_sdkQtoR() {
+    CellIdentityNr cellIdentity = getCellIdentityNr();
+
+    assertCellIdentityFieldsForAllSdks(cellIdentity);
+  }
+
+  @Test
+  @Config(minSdk = Build.VERSION_CODES.S)
+  public void build_fromSdkS() {
+    CellIdentityNr cellIdentity = getCellIdentityNr();
+
+    assertCellIdentityFieldsForAllSdks(cellIdentity);
+    assertThat(cellIdentity.getBands()).isEqualTo(BANDS);
+    assertThat(cellIdentity.getAdditionalPlmns()).containsExactlyElementsIn(ADDITIONAL_PLMNS);
+  }
+
+  /**
+   * Assertions on {@link android.telephony.CellIdentityNr} values that are common across all tested
+   * SDKs.
+   */
+  private void assertCellIdentityFieldsForAllSdks(CellIdentityNr cellIdentity) {
+    assertThat(cellIdentity.getPci()).isEqualTo(PCI);
+    assertThat(cellIdentity.getTac()).isEqualTo(TAC);
+    assertThat(cellIdentity.getNrarfcn()).isEqualTo(NRARFCN);
+    assertThat(cellIdentity.getMccString()).isEqualTo(MCC);
+    assertThat(cellIdentity.getMncString()).isEqualTo(MNC);
+    assertThat(cellIdentity.getNci()).isEqualTo(NCI);
+    assertThat(cellIdentity.getOperatorAlphaLong().toString()).isEqualTo(LONG_OPERATOR_NAME);
+    assertThat(cellIdentity.getOperatorAlphaShort().toString()).isEqualTo(SHORT_OPERATOR_NAME);
+  }
+
+  private CellIdentityNr getCellIdentityNr() {
+    return CellIdentityNrBuilder.newBuilder()
+        .setPci(PCI)
+        .setTac(TAC)
+        .setNrarfcn(NRARFCN)
+        .setBands(BANDS)
+        .setMcc(MCC)
+        .setMnc(MNC)
+        .setNci(NCI)
+        .setLongOperatorName(LONG_OPERATOR_NAME)
+        .setShortOperatorName(SHORT_OPERATOR_NAME)
+        .setAdditionalPlmns(ADDITIONAL_PLMNS)
+        .build();
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellInfoNrBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellInfoNrBuilderTest.java
new file mode 100644
index 0000000..80e7dcc
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/CellInfoNrBuilderTest.java
@@ -0,0 +1,76 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfoNr;
+import android.telephony.CellSignalStrengthNr;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.time.Duration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link CellInfoNrBuilder} */
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = Build.VERSION_CODES.Q)
+public class CellInfoNrBuilderTest {
+
+  private static final boolean REGISTERED = false;
+  private static final long TIMESTAMP_NANOS = 123L;
+  private static final long TIMESTAMP_MILLIS = Duration.ofNanos(TIMESTAMP_NANOS).toMillis();
+  private static final int CELL_CONNECTION_STATUS = 1;
+
+  private static final CellIdentityNr cellIdentity =
+      CellIdentityNrBuilder.newBuilder().setMcc("310").build();
+  private static final CellSignalStrengthNr cellSignalStrength =
+      CellSignalStrengthNrBuilder.newBuilder().setCsiRsrp(-100).build();
+
+  @Test
+  public void build_noArguments() {
+    // The intent is to primarily verify that there are no issues setting default values i.e., no
+    // exceptions thrown or invalid inputs.
+    CellInfoNr cellInfo = CellInfoNrBuilder.newBuilder().build();
+
+    assertThat(cellInfo.getTimeStamp()).isEqualTo(0);
+  }
+
+  @Test
+  @Config(sdk = Build.VERSION_CODES.Q)
+  public void build_sdkQ() {
+    CellInfoNr cellInfo = getCellInfoNr();
+
+    assertCellInfoFieldsForAllSdks(cellInfo);
+  }
+
+  @Test
+  @Config(minSdk = Build.VERSION_CODES.R)
+  public void build_fromSdkR() {
+    CellInfoNr cellInfo = getCellInfoNr();
+
+    assertCellInfoFieldsForAllSdks(cellInfo);
+    assertThat(cellInfo.getTimestampMillis()).isEqualTo(TIMESTAMP_MILLIS);
+  }
+
+  /**
+   * Assertions on {@link android.telephony.CellInfo} values that are common across all tested SDKs.
+   */
+  private void assertCellInfoFieldsForAllSdks(CellInfoNr cellInfo) {
+    assertThat(cellInfo.isRegistered()).isFalse();
+    assertThat(cellInfo.getTimeStamp()).isEqualTo(TIMESTAMP_NANOS);
+    assertThat(cellInfo.getCellConnectionStatus()).isEqualTo(CELL_CONNECTION_STATUS);
+    assertThat(cellInfo.getCellIdentity()).isEqualTo(cellIdentity);
+    assertThat(cellInfo.getCellSignalStrength()).isEqualTo(cellSignalStrength);
+  }
+
+  private CellInfoNr getCellInfoNr() {
+    return CellInfoNrBuilder.newBuilder()
+        .setRegistered(REGISTERED)
+        .setTimeStampNanos(TIMESTAMP_NANOS)
+        .setCellConnectionStatus(CELL_CONNECTION_STATUS)
+        .setCellIdentity(cellIdentity)
+        .setCellSignalStrength(cellSignalStrength)
+        .build();
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthNrBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthNrBuilderTest.java
new file mode 100644
index 0000000..1f18ee8
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthNrBuilderTest.java
@@ -0,0 +1,84 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import android.telephony.CellInfo;
+import android.telephony.CellSignalStrengthNr;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Test for {@link CellSignalStrengthNrBuilder} */
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = Build.VERSION_CODES.Q)
+public class CellSignalStrengthNrBuilderTest {
+
+  // The platform enforces that some of these values are within a certain range - otherwise, it will
+  // default to {@link android.telephony.CellInfo.UNAVAILABLE}.
+  private static final int CSI_RSRP = -100;
+  private static final int CSI_RSRQ = -10;
+  private static final int CSI_SINR = -20;
+  private static final int CSI_CQI_TABLE_INDEX = 1;
+  private static final ImmutableList<Byte> CSI_CQI_REPORT = ImmutableList.of((byte) 7);
+  private static final int SS_RSRP = -140;
+  private static final int SS_RSRQ = -15;
+  private static final int SS_SINR = -20;
+  private static final int TIMING_ADVANCE = 10;
+
+  @Test
+  public void build_noArguments() {
+    // The intent is to primarily verify that there are no issues setting default values i.e., no
+    // exceptions thrown or invalid inputs.
+    CellSignalStrengthNr cellSignalStrength = CellSignalStrengthNrBuilder.newBuilder().build();
+
+    assertThat(cellSignalStrength.getCsiRsrp()).isEqualTo(CellInfo.UNAVAILABLE);
+  }
+
+  @Test
+  @Config(minSdk = Build.VERSION_CODES.Q, maxSdk = Build.VERSION_CODES.S_V2)
+  public void build_sdkQtoS() {
+    CellSignalStrengthNr cellSignalStrength = getCellSignalStrength();
+
+    assertCellSignalStrengthFieldsForAllSdks(cellSignalStrength);
+  }
+
+  @Test
+  @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+  public void build_fromSdkT() {
+    CellSignalStrengthNr cellSignalStrength = getCellSignalStrength();
+
+    assertCellSignalStrengthFieldsForAllSdks(cellSignalStrength);
+    assertThat(cellSignalStrength.getCsiCqiTableIndex()).isEqualTo(CSI_CQI_TABLE_INDEX);
+    assertThat(cellSignalStrength.getCsiCqiReport()).containsExactly(7);
+  }
+
+  /**
+   * Assertions on {@link android.telephony.CellSignalStrengthNr} values that are common across all
+   * tested SDKs.
+   */
+  private void assertCellSignalStrengthFieldsForAllSdks(CellSignalStrengthNr cellSignalStrength) {
+    assertThat(cellSignalStrength.getCsiRsrp()).isEqualTo(CSI_RSRP);
+    assertThat(cellSignalStrength.getCsiRsrq()).isEqualTo(CSI_RSRQ);
+    assertThat(cellSignalStrength.getCsiSinr()).isEqualTo(CSI_SINR);
+    assertThat(cellSignalStrength.getSsRsrp()).isEqualTo(SS_RSRP);
+    assertThat(cellSignalStrength.getSsRsrq()).isEqualTo(SS_RSRQ);
+    assertThat(cellSignalStrength.getSsSinr()).isEqualTo(SS_SINR);
+  }
+
+  private CellSignalStrengthNr getCellSignalStrength() {
+    return CellSignalStrengthNrBuilder.newBuilder()
+        .setCsiRsrp(CSI_RSRP)
+        .setCsiRsrq(CSI_RSRQ)
+        .setCsiSinr(CSI_SINR)
+        .setCsiCqiTableIndex(CSI_CQI_TABLE_INDEX)
+        .setCsiCqiReport(CSI_CQI_REPORT)
+        .setSsRsrp(SS_RSRP)
+        .setSsRsrq(SS_RSRQ)
+        .setSsSinr(SS_SINR)
+        .setTimingAdvance(TIMING_ADVANCE)
+        .build();
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAbsSpinnerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAbsSpinnerTest.java
index ec4b985..9b1b09d 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAbsSpinnerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAbsSpinnerTest.java
@@ -3,16 +3,23 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.robolectric.Shadows.shadowOf;
 
+import android.app.Activity;
 import android.content.Context;
+import android.os.Looper;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.ArrayAdapter;
 import android.widget.Spinner;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
 
 @RunWith(AndroidJUnit4.class)
 public class ShadowAbsSpinnerTest {
@@ -22,8 +29,10 @@
 
   @Before
   public void setUp() throws Exception {
+    Activity activity = Robolectric.setupActivity(Activity.class);
     Context context = ApplicationProvider.getApplicationContext();
-    spinner = new Spinner(context);
+    spinner = new Spinner(activity);
+    ((ViewGroup) activity.findViewById(android.R.id.content)).addView(spinner);
     shadowSpinner = shadowOf(spinner);
     String [] testItems = {"foo", "bar"};
     arrayAdapter = new MyArrayAdapter(context, testItems);
@@ -62,6 +71,57 @@
     assertThat(shadowSpinner.isAnimatedTransition()).isTrue();
   }
 
+  @Test
+  public void useRealSelection_doesNotCauseInfiniteLoop() {
+    try {
+      System.setProperty("robolectric.useRealSpinnerSelection", "true");
+      final AtomicBoolean itemSelected = new AtomicBoolean(false);
+      spinner.setOnItemSelectedListener(
+          new OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+              itemSelected.set(true);
+              // This will result in an stack overflow if the fake selection ShadowAbsSpinner
+              // selection logic is used.
+              spinner.setSelection(0);
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {}
+          });
+      spinner.setAdapter(arrayAdapter);
+      spinner.setSelection(1);
+      shadowOf(Looper.getMainLooper()).idle();
+      assertThat(itemSelected.get()).isTrue();
+    } finally {
+      System.clearProperty("robolectric.useRealSpinnerSelection");
+    }
+  }
+
+  @Test
+  public void useRealSelection_callbackCalledOnce() {
+    try {
+      System.setProperty("robolectric.useRealSpinnerSelection", "true");
+      final AtomicInteger invocations = new AtomicInteger(0);
+      spinner.setOnItemSelectedListener(
+          new OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+              invocations.incrementAndGet();
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {}
+          });
+      spinner.setAdapter(arrayAdapter);
+      spinner.setSelection(1);
+      shadowOf(Looper.getMainLooper()).idle();
+      assertThat(invocations.get()).isEqualTo(1);
+    } finally {
+      System.clearProperty("robolectric.useRealSpinnerSelection");
+    }
+  }
+
   private static class MyArrayAdapter extends ArrayAdapter<String> {
     public MyArrayAdapter(Context context, String[] testItems) {
       super(context, android.R.layout.simple_spinner_item, testItems);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityServiceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityServiceTest.java
index 9cabcdf..195d673 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityServiceTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityServiceTest.java
@@ -224,12 +224,100 @@
     w2.setId(2);
     AccessibilityWindowInfo w3 = AccessibilityWindowInfo.obtain();
     w3.setId(3);
+
     shadow.setWindows(Arrays.asList(w1, w2, w3));
+
     assertThat(service.getWindows()).hasSize(3);
     assertThat(service.getWindows()).containsExactly(w1, w2, w3).inOrder();
   }
 
   @Test
+  @Config(minSdk = R)
+  public void getWindowsforDefaultDisplay_returnEmptyList() {
+    assertThat(service.getWindowsOnAllDisplays().get(Display.DEFAULT_DISPLAY)).isEmpty();
+  }
+
+  @Test
+  @Config(minSdk = R)
+  public void getWindowsforNonDefaultDisplay_returnNullList() {
+    assertThat(service.getWindowsOnAllDisplays().get(Display.DEFAULT_DISPLAY + 1)).isNull();
+  }
+
+  @Test
+  @Config(minSdk = R)
+  public void setWindowsOnDisplay_returnPopulatedWindowsOnAllDisplays() {
+    AccessibilityWindowInfo w1 = AccessibilityWindowInfo.obtain();
+    w1.setId(1);
+    AccessibilityWindowInfo w2 = AccessibilityWindowInfo.obtain();
+    w2.setId(2);
+    AccessibilityWindowInfo w3 = AccessibilityWindowInfo.obtain();
+    w3.setId(3);
+    AccessibilityWindowInfo w4 = AccessibilityWindowInfo.obtain();
+    w4.setId(4);
+    AccessibilityWindowInfo w5 = AccessibilityWindowInfo.obtain();
+    w5.setId(5);
+    AccessibilityWindowInfo w6 = AccessibilityWindowInfo.obtain();
+    w6.setId(6);
+    AccessibilityWindowInfo w7 = AccessibilityWindowInfo.obtain();
+    w7.setId(7);
+
+    shadow.setWindowsOnDisplay(Display.DEFAULT_DISPLAY, Arrays.asList(w1, w2, w3));
+    shadow.setWindowsOnDisplay(Display.DEFAULT_DISPLAY + 1, Arrays.asList(w4, w5, w6, w7));
+
+    assertThat(service.getWindows()).hasSize(3);
+    assertThat(service.getWindows()).containsExactly(w1, w2, w3).inOrder();
+    assertThat(service.getWindowsOnAllDisplays().size()).isEqualTo(2);
+    assertThat(service.getWindowsOnAllDisplays().get(Display.DEFAULT_DISPLAY))
+        .containsExactly(w1, w2, w3)
+        .inOrder();
+    assertThat(service.getWindowsOnAllDisplays().get(Display.DEFAULT_DISPLAY + 1))
+        .containsExactly(w4, w5, w6, w7)
+        .inOrder();
+  }
+
+  @Test
+  @Config(minSdk = R)
+  public void setNullWindowsOnNonDefaultDisplay_nonDefaultDisplayHasWindows_displayIsRemoved() {
+    AccessibilityWindowInfo w1 = AccessibilityWindowInfo.obtain();
+    w1.setId(1);
+    AccessibilityWindowInfo w2 = AccessibilityWindowInfo.obtain();
+    w2.setId(2);
+    AccessibilityWindowInfo w3 = AccessibilityWindowInfo.obtain();
+    w3.setId(3);
+    shadow.setWindowsOnDisplay(Display.DEFAULT_DISPLAY + 1, Arrays.asList(w1, w2, w3));
+
+    shadow.setWindowsOnDisplay(Display.DEFAULT_DISPLAY + 1, null);
+
+    assertThat(service.getWindowsOnAllDisplays().get(Display.DEFAULT_DISPLAY + 1)).isNull();
+  }
+
+  @Test
+  @Config(minSdk = R)
+  public void setWindows_nonDefaultDisplayHasWindows_nonDefaultDisplayWindowsNotRemoved() {
+    AccessibilityWindowInfo w1 = AccessibilityWindowInfo.obtain();
+    w1.setId(1);
+    AccessibilityWindowInfo w2 = AccessibilityWindowInfo.obtain();
+    w2.setId(2);
+    AccessibilityWindowInfo w3 = AccessibilityWindowInfo.obtain();
+    w3.setId(3);
+    AccessibilityWindowInfo w4 = AccessibilityWindowInfo.obtain();
+    w4.setId(4);
+    AccessibilityWindowInfo w5 = AccessibilityWindowInfo.obtain();
+    w5.setId(5);
+    AccessibilityWindowInfo w6 = AccessibilityWindowInfo.obtain();
+    w6.setId(6);
+    AccessibilityWindowInfo w7 = AccessibilityWindowInfo.obtain();
+    w7.setId(7);
+    shadow.setWindowsOnDisplay(Display.DEFAULT_DISPLAY + 1, Arrays.asList(w4, w5, w6, w7));
+
+    shadow.setWindows(Arrays.asList(w1, w2, w3));
+
+    assertThat(service.getWindowsOnAllDisplays().get(Display.DEFAULT_DISPLAY + 1))
+        .containsExactly(w4, w5, w6, w7)
+        .inOrder();
+  }
+
+  @Test
   @Config(minSdk = S)
   public void getSystemActions_returnsNull() {
     assertThat(service.getSystemActions()).isNull();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityWindowInfoTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityWindowInfoTest.java
index 7e5b783..9407c09 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityWindowInfoTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityWindowInfoTest.java
@@ -59,4 +59,11 @@
     shadow.addChild(window);
     assertThat(shadow.getChild(0)).isEqualTo(window);
   }
+
+  @Test
+  public void testSetPictureInPicture() {
+    assertThat(shadow.isInPictureInPictureMode()).isFalse();
+    shadow.setPictureInPicture(true);
+    assertThat(shadow.isInPictureInPictureMode()).isTrue();
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java
index 9ecb63d..5de84d8 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java
@@ -1,8 +1,8 @@
 package org.robolectric.shadows;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf;
@@ -46,7 +46,7 @@
 
   @Test
   public void openFd_shouldProvideFileDescriptorForDeflatedAsset() throws Exception {
-    assumeTrue(!useLegacy());
+    assume().that(useLegacy()).isFalse();
     expectedException.expect(FileNotFoundException.class);
     expectedException.expectMessage(
         "This file can not be opened as a file descriptor; it is probably compressed");
@@ -79,7 +79,7 @@
 
   @Test
   public void openNonAssetShouldThrowExceptionWhenFileDoesNotExist() throws IOException {
-    assumeTrue(useLegacy());
+    assume().that(useLegacy()).isTrue();
 
     expectedException.expect(IOException.class);
     expectedException.expectMessage(
@@ -90,7 +90,7 @@
 
   @Test
   public void unknownResourceIdsShouldReportPackagesSearched() throws IOException {
-    assumeTrue(useLegacy());
+    assume().that(useLegacy()).isTrue();
 
     expectedException.expect(Resources.NotFoundException.class);
     expectedException.expectMessage("Resource ID #0xffffffff");
@@ -102,7 +102,7 @@
   @Test
   public void forSystemResources_unknownResourceIdsShouldReportPackagesSearched()
       throws IOException {
-    if (!useLegacy()) return;
+    assume().that(useLegacy()).isTrue();
     expectedException.expect(Resources.NotFoundException.class);
     expectedException.expectMessage("Resource ID #0xffffffff");
 
@@ -113,8 +113,7 @@
   @Test
   @Config(qualifiers = "mdpi")
   public void openNonAssetShouldOpenCorrectAssetBasedOnQualifierMdpi() throws IOException {
-    if (!useLegacy()) return;
-
+    assume().that(useLegacy()).isTrue();
     InputStream inputStream = assetManager.openNonAsset(0, "res/drawable/robolectric.png", 0);
     assertThat(countBytes(inputStream)).isEqualTo(8141);
   }
@@ -122,8 +121,7 @@
   @Test
   @Config(qualifiers = "hdpi")
   public void openNonAssetShouldOpenCorrectAssetBasedOnQualifierHdpi() throws IOException {
-    if (!useLegacy()) return;
-
+    assume().that(useLegacy()).isTrue();
     InputStream inputStream = assetManager.openNonAsset(0, "res/drawable/robolectric.png", 0);
     assertThat(countBytes(inputStream)).isEqualTo(23447);
   }
@@ -178,8 +176,7 @@
 
   @Test
   public void attrsToTypedArray_shouldAllowMockedAttributeSets() {
-    if (!useLegacy()) return;
-
+    assume().that(useLegacy()).isTrue();
     AttributeSet mockAttributeSet = mock(AttributeSet.class);
     when(mockAttributeSet.getAttributeCount()).thenReturn(1);
     when(mockAttributeSet.getAttributeNameResource(0)).thenReturn(android.R.attr.windowBackground);
@@ -191,7 +188,7 @@
 
   @Test
   public void whenStyleAttrResolutionFails_attrsToTypedArray_returnsNiceErrorMessage() {
-    if (!useLegacy()) return;
+    assume().that(useLegacy()).isTrue();
     expectedException.expect(RuntimeException.class);
     expectedException.expectMessage(
         "no value for org.robolectric:attr/styleNotSpecifiedInAnyTheme in theme with applied"
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
index d5c0c46..1498562 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
@@ -50,6 +50,17 @@
   private Context appContext;
   private AudioManager audioManager;
 
+  // When creating Audio Device Info, we need to pass external device type instead of internal input
+  // device(e.g. AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
+  // The mapping between external device type and internal input device is:
+  // http://shortn/_7pV0nML4Cr
+  // Copied from
+  // http://cs/android-internal/frameworks/base/media/java/android/media/AudioSystem.java;l=989
+  private static final int DEVICE_OUT_BLUETOOTH_SCO = 0x10;
+  // Copied from
+  // http://cs/android-internal/frameworks/base/media/java/android/media/AudioSystem.java;l=1000
+  private static final int DEVICE_OUT_BLUETOOTH_A2DP = 0x80;
+
   @Before
   public void setUp() {
     appContext = ApplicationProvider.getApplicationContext();
@@ -405,7 +416,7 @@
   @Config(minSdk = M)
   public void registerAudioDeviceCallback_availableDevices_onAudioDevicesAddedCallback()
       throws Exception {
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
 
     AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
@@ -421,7 +432,7 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
 
     verifyNoMoreInteractions(callback);
@@ -436,7 +447,7 @@
     audioManager.unregisterAudioDeviceCallback(callback);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
 
     verifyNoMoreInteractions(callback);
@@ -450,7 +461,7 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
 
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {device});
@@ -465,7 +476,7 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
 
     verifyNoMoreInteractions(callback);
@@ -478,7 +489,7 @@
     AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
 
     shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
@@ -494,7 +505,7 @@
     AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
 
     shadowOf(audioManager).removeInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
@@ -510,7 +521,7 @@
     AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
 
     shadowOf(audioManager).removeInputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
@@ -526,7 +537,7 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).removeInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
 
     verifyNoMoreInteractions(callback);
@@ -539,7 +550,7 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
 
     verifyNoMoreInteractions(callback);
@@ -553,7 +564,7 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).addOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
 
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {device});
@@ -568,7 +579,7 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).addOutputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
 
     verifyNoMoreInteractions(callback);
@@ -581,7 +592,7 @@
     AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
 
     shadowOf(audioManager).addOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
@@ -597,7 +608,7 @@
     AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
 
     shadowOf(audioManager).removeOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
@@ -613,7 +624,7 @@
     AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
 
     shadowOf(audioManager).removeOutputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
@@ -629,17 +640,130 @@
     audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
     verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
 
-    AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).removeOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
 
     verifyNoMoreInteractions(callback);
   }
 
   @Test
+  @Config(minSdk = S)
+  public void setAvailableCommunicationDevices_withCallbackRegistered_noNotificationCallback()
+      throws Exception {
+    AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+    audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    shadowOf(audioManager).setAvailableCommunicationDevices(Collections.singletonList(device));
+
+    verifyNoMoreInteractions(callback);
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      addAvailableCommunicationDevice_withCallbackRegisteredAndNoDevice_deviceAddedAndNotifiesCallback()
+          throws Exception {
+    AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+    audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    shadowOf(audioManager)
+        .addAvailableCommunicationDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {device});
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      addAvailableCommunicationDeviceNoCallbackNotification_withCallbackRegisteredAndNoDevice_noNotificationCallback()
+          throws Exception {
+    AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+    audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    shadowOf(audioManager)
+        .addAvailableCommunicationDevice(device, /* notifyAudioDeviceCallbacks= */ false);
+
+    verifyNoMoreInteractions(callback);
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      addAvailableCommunicationDevice_withCallbackRegisteredAndDevicePresent_noNotificationCallback()
+          throws Exception {
+    AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+    audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    shadowOf(audioManager).setAvailableCommunicationDevices(Collections.singletonList(device));
+
+    shadowOf(audioManager)
+        .addAvailableCommunicationDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+    verifyNoMoreInteractions(callback);
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      removeAvailableCommunicationDevice_withCallbackRegisteredAndDevicePresent_deviceRemovedAndNotifiesCallback()
+          throws Exception {
+    AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+    audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    shadowOf(audioManager).setAvailableCommunicationDevices(Collections.singletonList(device));
+
+    shadowOf(audioManager)
+        .removeAvailableCommunicationDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+    verify(callback).onAudioDevicesRemoved(new AudioDeviceInfo[] {device});
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      removeAvailableCommunicationDeviceNoCallbackNotification_withCallbackRegisteredAndDevicePresent_noNotificationCallback()
+          throws Exception {
+    AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+    audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    shadowOf(audioManager).setAvailableCommunicationDevices(Collections.singletonList(device));
+
+    shadowOf(audioManager)
+        .removeAvailableCommunicationDevice(device, /* notifyAudioDeviceCallbacks= */ false);
+
+    verifyNoMoreInteractions(callback);
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      removeAvailableCommunicationDevice_withCallbackRegisteredAndNoDevice_noNotificationCallback()
+          throws Exception {
+    AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+    audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+    verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+    AudioDeviceInfo device = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    shadowOf(audioManager)
+        .removeAvailableCommunicationDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+    verifyNoMoreInteractions(callback);
+  }
+
+  @Test
   @Config(minSdk = M)
   public void getDevices_criteriaInputs_getsAllInputDevices() throws Exception {
-    AudioDeviceInfo scoDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
-    AudioDeviceInfo a2dpDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+    AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    AudioDeviceInfo a2dpDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_A2DP);
     shadowOf(audioManager).setInputDevices(ImmutableList.of(scoDevice));
     shadowOf(audioManager).setOutputDevices(ImmutableList.of(a2dpDevice));
 
@@ -650,8 +774,8 @@
   @Test
   @Config(minSdk = M)
   public void getDevices_criteriaOutputs_getsAllOutputDevices() throws Exception {
-    AudioDeviceInfo scoDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
-    AudioDeviceInfo a2dpDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+    AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    AudioDeviceInfo a2dpDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_A2DP);
     shadowOf(audioManager).setInputDevices(ImmutableList.of(scoDevice));
     shadowOf(audioManager).setOutputDevices(ImmutableList.of(a2dpDevice));
 
@@ -662,8 +786,8 @@
   @Test
   @Config(minSdk = M)
   public void getDevices_criteriaInputsAndOutputs_getsAllDevices() throws Exception {
-    AudioDeviceInfo scoDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
-    AudioDeviceInfo a2dpDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+    AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+    AudioDeviceInfo a2dpDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_A2DP);
     shadowOf(audioManager).setInputDevices(ImmutableList.of(scoDevice));
     shadowOf(audioManager).setOutputDevices(ImmutableList.of(a2dpDevice));
 
@@ -674,7 +798,7 @@
   @Test
   @Config(minSdk = S)
   public void setCommunicationDevice_updatesCommunicationDevice() throws Exception {
-    AudioDeviceInfo scoDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setCommunicationDevice(scoDevice);
 
     assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice);
@@ -683,7 +807,7 @@
   @Test
   @Config(minSdk = S)
   public void clearCommunicationDevice_clearsCommunicationDevice() throws Exception {
-    AudioDeviceInfo scoDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+    AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
     shadowOf(audioManager).setCommunicationDevice(scoDevice);
     assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice);
 
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
index 10cb148..9fa2ef2 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
@@ -599,7 +599,8 @@
                 PendingIntent.getBroadcast(
                     getApplicationContext(),
                     /* requestCode= */ 0,
-                    new Intent("com.dummy.action.DUMMY_ACTION"),
+                    new Intent("com.dummy.action.DUMMY_ACTION")
+                            .setPackage(getApplicationContext().getPackageName()),
                     /* flags= */ PendingIntent.FLAG_MUTABLE)));
   }
 
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java
new file mode 100644
index 0000000..1216595
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java
@@ -0,0 +1,199 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import androidx.test.core.app.ApplicationProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link ShadowBluetoothGattServer}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = O)
+public class ShadowBluetoothGattServerTest {
+
+  private static final int INITIAL_VALUE = -99;
+  private static final String ACTION_CONNECTION = "CONNECT/DISCONNECT";
+  private static final String MOCK_MAC_ADDRESS = "00:11:22:33:AA:BB";
+  private static final byte[] RESPONSE_VALUE1 = new byte[] {'a', 'b', 'c'};
+  private static final byte[] RESPONSE_VALUE2 = new byte[] {'1', '2', '3'};
+
+  private BluetoothManager manager;
+  private Context context;
+  private BluetoothGattServer server;
+  private BluetoothDevice device;
+
+  private int resultStatus = INITIAL_VALUE;
+  private int resultState = INITIAL_VALUE;
+  private String resultAction;
+
+  private final BluetoothGattServerCallback callback =
+      new BluetoothGattServerCallback() {
+        @Override
+        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+          resultStatus = status;
+          resultState = newState;
+          resultAction = ACTION_CONNECTION;
+        }
+      };
+
+  @Before
+  @Config()
+  public void setUp() {
+    context = ApplicationProvider.getApplicationContext();
+    manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+    server = manager.openGattServer(context, new BluetoothGattServerCallback() {}, 0);
+    device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
+  }
+
+  @After
+  public void tearDown() {
+    shadowOf(server).getBluetoothConnectionManager().resetConnections();
+  }
+
+  @Test
+  public void test_setAndGetGattServerCallback() {
+    shadowOf(server).setGattServerCallback(callback);
+    assertThat(shadowOf(server).getGattServerCallback()).isSameInstanceAs(callback);
+  }
+
+  @Test
+  public void test_getConnectionManager() {
+    assertThat(shadowOf(server).getBluetoothConnectionManager()).isNotNull();
+  }
+
+  @Test
+  public void test_isNotClosed_initially() {
+    assertThat(shadowOf(server).isClosed()).isFalse();
+  }
+
+  @Test
+  public void test_isClosed_afterClose() {
+    server.close();
+    assertThat(shadowOf(server).isClosed()).isTrue();
+  }
+
+  @Test
+  public void test_getResponses_initially() {
+    assertThat(shadowOf(server).getResponses()).isEmpty();
+  }
+
+  @Test
+  public void test_getResponses_afterSendResponses() {
+    shadowOf(server).sendResponse(device, 0, 0, 0, RESPONSE_VALUE1);
+    assertThat(shadowOf(server).getResponses()).hasSize(1);
+    shadowOf(server).sendResponse(device, 0, 0, 0, RESPONSE_VALUE2);
+    assertThat(shadowOf(server).getResponses()).hasSize(2);
+    assertThat(shadowOf(server).getResponses().get(0)).isEqualTo(RESPONSE_VALUE1);
+    assertThat(shadowOf(server).getResponses().get(1)).isEqualTo(RESPONSE_VALUE2);
+  }
+
+  @Test
+  public void test_getResponses_afterClearResponses() {
+    shadowOf(server).sendResponse(device, 0, 0, 0, RESPONSE_VALUE1);
+    shadowOf(server).sendResponse(device, 0, 0, 0, RESPONSE_VALUE2);
+    shadowOf(server).clearResponses();
+    assertThat(shadowOf(server).getResponses()).isEmpty();
+  }
+
+  @Test
+  public void test_isConnectedToDevice_initially() {
+    assertThat(shadowOf(server).isConnectedToDevice(device)).isFalse();
+  }
+
+  @Test
+  public void test_notifyConnection_withoutCallback() {
+    shadowOf(server).setGattServerCallback(null);
+    shadowOf(server).notifyConnection(device);
+    assertThat(shadowOf(server).isConnectedToDevice(device)).isTrue();
+    assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+    assertThat(resultState).isEqualTo(INITIAL_VALUE);
+    assertThat(resultAction).isNull();
+  }
+
+  @Test
+  public void test_notifyConnection_withCallback() {
+    shadowOf(server).setGattServerCallback(callback);
+    shadowOf(server).notifyConnection(device);
+    assertThat(shadowOf(server).isConnectedToDevice(device)).isTrue();
+    assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+    assertThat(resultState).isEqualTo(BluetoothAdapter.STATE_CONNECTED);
+    assertThat(resultAction).isEqualTo(ACTION_CONNECTION);
+  }
+
+  @Test
+  public void test_notifyDisconnection_withoutCallback() {
+    shadowOf(server).setGattServerCallback(null);
+    shadowOf(server).notifyConnection(device);
+    shadowOf(server).notifyDisconnection(device);
+    assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue();
+    assertThat(shadowOf(server).isConnectedToDevice(device)).isFalse();
+    assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+    assertThat(resultState).isEqualTo(INITIAL_VALUE);
+    assertThat(resultAction).isNull();
+  }
+
+  @Test
+  public void test_notifyDisconnection_withCallback() {
+    shadowOf(server).setGattServerCallback(callback);
+    shadowOf(server).notifyConnection(device);
+    shadowOf(server).notifyDisconnection(device);
+    assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue();
+    assertThat(shadowOf(server).isConnectedToDevice(device)).isFalse();
+    assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+    assertThat(resultState).isEqualTo(BluetoothAdapter.STATE_DISCONNECTED);
+    assertThat(resultAction).isEqualTo(ACTION_CONNECTION);
+  }
+
+  @Test
+  public void test_notifyDisconnection_withCallback_beforeConnection() {
+    shadowOf(server).setGattServerCallback(callback);
+    shadowOf(server).notifyDisconnection(device);
+    assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue();
+    assertThat(shadowOf(server).isConnectedToDevice(device)).isFalse();
+    assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+    assertThat(resultState).isEqualTo(BluetoothAdapter.STATE_DISCONNECTED);
+    assertThat(resultAction).isEqualTo(ACTION_CONNECTION);
+  }
+
+  @Test
+  public void test_isConnectionCancelled_afterCancelConnection() {
+    server.cancelConnection(device);
+    assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue();
+  }
+
+  @Test
+  public void test_isConnectionCancelled_afterCancelConnection_whileConnected() {
+    server.connect(device, true);
+    server.cancelConnection(device);
+    assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue();
+  }
+
+  @Test
+  public void test_isConnectionCancelled_afterCancelConnection_aftereNotifyConnection() {
+    shadowOf(server).setGattServerCallback(callback);
+    shadowOf(server).notifyConnection(device);
+    server.cancelConnection(device);
+    assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue();
+  }
+
+  @Test
+  public void test_removeCancelledDevice_afterNotifyConnection() {
+    shadowOf(server).setGattServerCallback(callback);
+    shadowOf(server).notifyDisconnection(device);
+    shadowOf(server).notifyConnection(device);
+    assertThat(shadowOf(server).isConnectionCancelled(device)).isFalse();
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
index 78b9edb..01e4bdc 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
@@ -14,6 +14,7 @@
 import android.bluetooth.BluetoothProfile;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.util.UUID;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -99,6 +100,11 @@
     bluetoothGatt = ShadowBluetoothGatt.newInstance(bluetoothDevice);
   }
 
+  @After
+  public void tearDown() {
+    shadowOf(bluetoothGatt).getBluetoothConnectionManager().resetConnections();
+  }
+
   @Test
   public void canCreateBluetoothGattViaNewInstance() {
     assertThat(bluetoothGatt).isNotNull();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java
index a2c5f5f..5e9848a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java
@@ -1,12 +1,17 @@
 package org.robolectric.shadows;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.S;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertThrows;
 import static org.robolectric.Shadows.shadowOf;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
@@ -31,14 +36,16 @@
   private static final int PROFILE_STATE_CONNECTED = BluetoothProfile.STATE_CONNECTED;
   private static final int PROFILE_STATE_CONNECTING = BluetoothProfile.STATE_CONNECTING;
   private static final int[] CONNECTED_STATES = new int[] {PROFILE_STATE_CONNECTED};
+  private static final BluetoothGattServerCallback callback = new BluetoothGattServerCallback() {};
 
   private BluetoothManager manager;
   private BluetoothAdapter adapter;
   private ShadowBluetoothManager shadowManager;
+  private Context context;
 
   @Before
   public void setUp() {
-    Context context = ApplicationProvider.getApplicationContext();
+    context = ApplicationProvider.getApplicationContext();
     manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
     adapter = manager.getAdapter();
     shadowManager = shadowOf(manager);
@@ -135,4 +142,20 @@
   private BluetoothDevice createBluetoothDevice(String address) {
     return adapter.getRemoteDevice(address);
   }
+
+  @Test
+  @Config(minSdk = O, maxSdk = R)
+  public void openGattServer_doesNotCrash() {
+    BluetoothGattServer gattServer = manager.openGattServer(context, callback, 0);
+    assertThat(gattServer).isNotNull();
+    assertThat(shadowOf(gattServer).getGattServerCallback()).isSameInstanceAs(callback);
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void openGattServerWithTransport_doesNotCrash() {
+    BluetoothGattServer gattServer = manager.openGattServer(context, callback, 0, true);
+    assertThat(gattServer).isNotNull();
+    assertThat(shadowOf(gattServer).getGattServerCallback()).isSameInstanceAs(callback);
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java
index 624508b..cb3d975 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java
@@ -74,6 +74,7 @@
     shadowMainLooper().idle();
 
     assertThat(shadowBugreportManager.isBugreportInProgress()).isTrue();
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNotNull();
     verify(callback, never()).onFinished();
     verify(callback, never()).onError(anyInt());
   }
@@ -217,6 +218,7 @@
     assertThat(shadowBugreportManager.isBugreportInProgress()).isTrue();
     verify(callback, never()).onFinished();
     verify(callback, never()).onError(anyInt());
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNotNull();
 
     shadowBugreportManager.executeOnFinished();
     shadowMainLooper().idle();
@@ -224,6 +226,7 @@
     assertThat(shadowBugreportManager.isBugreportInProgress()).isFalse();
     verify(callback).onFinished();
     verify(callback, never()).onError(anyInt());
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNull();
   }
 
   @Test
@@ -244,6 +247,7 @@
     verify(callback, never()).onProgress(anyFloat());
     verify(callback, never()).onFinished();
     verify(callback, never()).onError(anyInt());
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNotNull();
 
     shadowBugreportManager.executeOnProgress(50.0f);
     shadowMainLooper().idle();
@@ -252,6 +256,7 @@
     verify(callback).onProgress(50.0f);
     verify(callback, never()).onFinished();
     verify(callback, never()).onError(anyInt());
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNotNull();
 
     shadowBugreportManager.executeOnFinished();
     shadowMainLooper().idle();
@@ -263,6 +268,7 @@
     verify(callback).onFinished();
     verify(callback, never()).onError(anyInt());
     verifyNoMoreInteractions(callback);
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNull();
   }
 
   @Test
@@ -281,6 +287,7 @@
     assertThat(shadowBugreportManager.isBugreportInProgress()).isTrue();
     verify(callback, never()).onFinished();
     verify(callback, never()).onError(anyInt());
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNotNull();
 
     shadowBugreportManager.executeOnFinished();
     shadowMainLooper().idle();
@@ -288,6 +295,7 @@
     assertThat(shadowBugreportManager.isBugreportInProgress()).isFalse();
     verify(callback).onFinished();
     verify(callback, never()).onError(anyInt());
+    assertThat(shadowBugreportManager.getScreenshotFd()).isNull();
   }
 
   private ParcelFileDescriptor createWriteFile(String fileName) throws IOException {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java
index 2b40685..ff359a0 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowChoreographerTest.java
@@ -9,12 +9,20 @@
 import java.util.concurrent.atomic.AtomicLong;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.shadow.api.Shadow;
 
 /** Unit tests for {@link ShadowChoreographer}. */
 @RunWith(AndroidJUnit4.class)
 public class ShadowChoreographerTest {
 
   @Test
+  public void isValid() {
+    ShadowPausedChoreographer shadowPausedChoreographer =
+        Shadow.extract(Choreographer.getInstance());
+    assertThat(shadowPausedChoreographer.isInitialized()).isTrue();
+  }
+
+  @Test
   public void setPaused_isPaused_doesntRun() {
     ShadowChoreographer.setPaused(true);
     long startTime = ShadowSystem.nanoTime();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCompanionDeviceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCompanionDeviceManagerTest.java
index 8437c7a..e49cfcd 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCompanionDeviceManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCompanionDeviceManagerTest.java
@@ -7,6 +7,7 @@
 import static org.robolectric.Shadows.shadowOf;
 
 import android.app.Application;
+import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -20,6 +21,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
 
 /** Unit test for ShadowCompanionDeviceManager. */
 @RunWith(AndroidJUnit4.class)
@@ -127,10 +130,13 @@
             MacAddress.fromString(MAC_ADDRESS),
             "displayName",
             "deviceProfile",
+            /* AssociatedDevice*/ null,
             /* selfManaged= */ false,
             /* notifyOnDeviceNearby= */ false,
+            /* revoked */ false,
             /* timeApprovedMs= */ 0,
-            /* lastTimeConnectedMs= */ 0);
+            /* lastTimeConnectedMs= */ 0,
+            /* systemDataSyncFlags= */ -1);
     assertThat(companionDeviceManager.getAssociations()).isEmpty();
     shadowCompanionDeviceManager.addAssociation(info);
     assertThat(companionDeviceManager.getMyAssociations()).contains(info);
@@ -188,6 +194,13 @@
         .isSameInstanceAs(callback);
   }
 
+  @Test
+  @Config(minSdk = VERSION_CODES.TIRAMISU)
+  public void notifyDeviceAppeared() {
+    ReflectionHelpers.callInstanceMethod(
+        companionDeviceManager, "notifyDeviceAppeared", ClassParameter.from(int.class, 1));
+  }
+
   private CompanionDeviceManager.Callback createCallback() {
     return new CompanionDeviceManager.Callback() {
       @Override
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java
index 2c9452c..6e2e2d0 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java
@@ -8,6 +8,7 @@
 import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
 import static com.google.common.truth.Truth.assertThat;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.doReturn;
@@ -488,8 +489,25 @@
   }
 
   @Test
-  public void openOutputStream_shouldReturnAnOutputStream() throws Exception {
-    assertThat(contentResolver.openOutputStream(uri21)).isInstanceOf(OutputStream.class);
+  public void openOutputStream_withNoRealOrRegisteredProvider_doesNotThrow() throws Exception {
+    Uri uri = Uri.parse("content://invalidauthority/test/1");
+    assertThat(contentResolver.openOutputStream(uri)).isNotNull();
+  }
+
+  @Test
+  public void openOutputStream_withRealContentProvider_canReadBytesWrittenToOutputStream()
+      throws IOException, RemoteException {
+    Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY);
+    Uri uri = Uri.parse("content://" + AUTHORITY + "/test/1");
+
+    // Write content through given outputstream
+    try (OutputStream outputStream = contentResolver.openOutputStream(uri)) {
+      outputStream.write("foo".getBytes(UTF_8));
+    }
+
+    // Verify written content can be read back
+    InputStream inputStream = contentResolver.openInputStream(uri);
+    assertThat(new String(inputStream.readAllBytes(), UTF_8)).isEqualTo("foo");
   }
 
   @Test
@@ -565,6 +583,98 @@
   }
 
   @Test
+  public void openOutputStream_withModeWithNoRealOrRegisteredProvider_throws() {
+    Uri uri = Uri.parse("content://invalidauthority/test/1");
+    assertThrows(FileNotFoundException.class, () -> contentResolver.openOutputStream(uri, "wt"));
+  }
+
+  @Test
+  public void openOutputStream_withModeWithRealContentProvider_canReadBytesWrittenToOutputStream()
+      throws IOException, RemoteException {
+    Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY);
+    Uri uri = Uri.parse("content://" + AUTHORITY + "/test/1");
+
+    // Write content through given outputstream
+    try (OutputStream outputStream = contentResolver.openOutputStream(uri, "wt")) {
+      outputStream.write("foo".getBytes(UTF_8));
+    }
+
+    // Verify written content can be read back
+    InputStream inputStream = contentResolver.openInputStream(uri);
+    assertThat(new String(inputStream.readAllBytes(), UTF_8)).isEqualTo("foo");
+  }
+
+  @Test
+  public void openOutputStream_withModeShouldReturnRegisteredStream() throws Exception {
+    final Uri uri = Uri.parse("content://registeredProvider/path");
+
+    AtomicInteger callCount = new AtomicInteger();
+    OutputStream outputStream =
+        new OutputStream() {
+
+          @Override
+          public void write(int arg0) throws IOException {
+            callCount.incrementAndGet();
+          }
+
+          @Override
+          public String toString() {
+            return "outputstream for " + uri;
+          }
+        };
+
+    shadowOf(contentResolver).registerOutputStream(uri, outputStream);
+
+    assertThat(callCount.get()).isEqualTo(0);
+    contentResolver.openOutputStream(uri, "wt").write(5);
+    assertThat(callCount.get()).isEqualTo(1);
+  }
+
+  @Test
+  public void openOutputStream_withModeShouldReturnNewStreamFromRegisteredSupplier()
+      throws Exception {
+    final Uri uri = Uri.parse("content://registeredProvider/path");
+
+    AtomicInteger streamCreateCount = new AtomicInteger();
+    shadowOf(contentResolver)
+        .registerOutputStreamSupplier(
+            uri,
+            () -> {
+              streamCreateCount.incrementAndGet();
+              AtomicBoolean isClosed = new AtomicBoolean();
+              isClosed.set(false);
+              OutputStream outputStream =
+                  new OutputStream() {
+                    @Override
+                    public void close() {
+                      isClosed.set(true);
+                    }
+
+                    @Override
+                    public void write(int arg0) throws IOException {
+                      if (isClosed.get()) {
+                        throw new IOException();
+                      }
+                    }
+
+                    @Override
+                    public String toString() {
+                      return "outputstream for " + uri;
+                    }
+                  };
+              return outputStream;
+            });
+
+    assertThat(streamCreateCount.get()).isEqualTo(0);
+    OutputStream outputStream1 = contentResolver.openOutputStream(uri, "wt");
+    outputStream1.close();
+    assertThat(streamCreateCount.get()).isEqualTo(1);
+
+    contentResolver.openOutputStream(uri, "wt").write(5);
+    assertThat(streamCreateCount.get()).isEqualTo(2);
+  }
+
+  @Test
   public void shouldTrackNotifiedUris() {
     contentResolver.notifyChange(Uri.parse("foo"), null, true);
     contentResolver.notifyChange(Uri.parse("bar"), null);
@@ -1182,7 +1292,7 @@
       } catch (IOException e) {
         throw new RuntimeException("error creating new file", e);
       }
-      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
     }
   }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java
index 366eeaa..b93dc58 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java
@@ -2,6 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.P;
 import static com.google.common.truth.Truth.assertThat;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -292,4 +293,10 @@
     assertThat(typedArray.getString(0)).isEqualTo("^q");
     assertThat(typedArray.getInt(1, -1234)).isEqualTo(1 /* ungulate */);
   }
+
+  @Test
+  @Config(minSdk = P)
+  public void getWifiRttService() {
+    assertThat(context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE)).isNotNull();
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
index 02b149d..03d9d73 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
@@ -4,6 +4,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.KITKAT;
 import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.P;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotSame;
@@ -46,6 +47,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.ConfigTestReceiver;
+import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithEmptyActionReceiver;
+import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithOneActionReceiver;
 import org.robolectric.R;
 import org.robolectric.Robolectric;
 import org.robolectric.RuntimeEnvironment;
@@ -95,6 +98,20 @@
   }
 
   @Test
+  @Config(manifest = "TestAndroidManifestWithAppComponentFactory.xml", minSdk = P)
+  public void registerReceiver_shouldGetReceiverWithCustomConstructorEmptyAction() {
+    BroadcastReceiver receiver = getReceiverOfClass(CustomConstructorWithEmptyActionReceiver.class);
+    assertThat(receiver).isInstanceOf(CustomConstructorWithEmptyActionReceiver.class);
+  }
+
+  @Test
+  @Config(manifest = "TestAndroidManifestWithAppComponentFactory.xml", minSdk = P)
+  public void registerReceiver_shouldGetReceiverWithCustomConstructorAndOneAction() {
+    BroadcastReceiver receiver = getReceiverOfClass(CustomConstructorWithOneActionReceiver.class);
+    assertThat(receiver).isInstanceOf(CustomConstructorWithOneActionReceiver.class);
+  }
+
+  @Test
   public void registerReceiver_shouldRegisterForAllIntentFilterActions() throws Exception {
     BroadcastReceiver receiver = broadcastReceiver("Larry");
     contextWrapper.registerReceiver(receiver, intentFilter("foo", "baz"));
@@ -658,16 +675,25 @@
   }
 
   @Test
-  public void checkPermissionUidPid() {
-    assertThat(contextWrapper.checkPermission("MY_PERMISSON", 1, 1))
+  public void checkPermission_denied() {
+    assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ 1, /* uid= */ 1))
         .isEqualTo(PackageManager.PERMISSION_DENIED);
 
+    assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ -1, /* uid= */ 1))
+        .isEqualTo(PackageManager.PERMISSION_DENIED);
+  }
+
+  @Test
+  public void checkPermission_granted() {
     shadowContextWrapper.grantPermissions(1, 1, "MY_PERMISSON");
 
-    assertThat(contextWrapper.checkPermission("MY_PERMISSON", 2, 1))
+    assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ 1, /* uid= */ 1))
+        .isEqualTo(PackageManager.PERMISSION_GRANTED);
+
+    assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ 2, /* uid= */ 1))
         .isEqualTo(PackageManager.PERMISSION_DENIED);
 
-    assertThat(contextWrapper.checkPermission("MY_PERMISSON", 1, 1))
+    assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ -1, /* uid= */ 1))
         .isEqualTo(PackageManager.PERMISSION_GRANTED);
   }
 
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateIntervalFormatTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateIntervalFormatTest.java
index bde0e72..f9721e4 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateIntervalFormatTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateIntervalFormatTest.java
@@ -1,6 +1,6 @@
 package org.robolectric.shadows;
 
-import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
 import static com.google.common.truth.Truth.assertThat;
 
 import android.icu.text.DateFormat;
@@ -12,13 +12,13 @@
 import java.text.ParseException;
 import java.util.Calendar;
 import java.util.Date;
-import libcore.icu.DateIntervalFormat;
+import android.text.format.DateIntervalFormat;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.annotation.Config;
 
 @RunWith(AndroidJUnit4.class)
-@Config(minSdk = M)
+@Config(minSdk = UPSIDE_DOWN_CAKE)
 public class ShadowDateIntervalFormatTest {
   @Test
   public void testDateInterval_FormatDateRange() throws ParseException {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
index 7d36f35..03d8247 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
@@ -818,6 +818,43 @@
   }
 
   @Test
+  @Config(minSdk = R)
+  public void getAutoTimeEnabledShouldWorkAsIntendedForDeviceOwner() {
+    // GIVEN the caller is the device owner
+    shadowOf(devicePolicyManager).setDeviceOwner(testComponent);
+
+    // WHEN setAutoTimeEnabled is called with true
+    devicePolicyManager.setAutoTimeEnabled(testComponent, true);
+
+    // THEN getAutoTimeEnabled should return true
+    assertThat(devicePolicyManager.getAutoTimeEnabled(testComponent)).isTrue();
+  }
+
+  @Test
+  @Config(minSdk = R)
+  public void getAutoTimeEnabledShouldWorkAsIntendedForProfileOwner() {
+    // GIVEN the caller is the profile owner
+    shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+
+    // WHEN setAutoTimeEnabled is called with false
+    devicePolicyManager.setAutoTimeEnabled(testComponent, false);
+
+    // THEN getAutoTimeEnabled should return false
+    assertThat(devicePolicyManager.getAutoTimeEnabled(testComponent)).isFalse();
+  }
+
+  @Test
+  @Config(minSdk = R)
+  public void getAutoTimeEnabledShouldReturnFalseIfNotSet() {
+    // GIVEN the caller is the device owner
+    shadowOf(devicePolicyManager).setDeviceOwner(testComponent);
+
+    // WHEN setAutoTimeEnabled has not been called
+    // THEN getAutoTimeEnabled should return false
+    assertThat(devicePolicyManager.getAutoTimeEnabled(testComponent)).isFalse();
+  }
+
+  @Test
   @Config(minSdk = LOLLIPOP)
   public void getAutoTimeRequiredShouldWorkAsIntendedForDeviceOwner() {
     // GIVEN the caller is the device owner
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java
index 5ab3cbd..3e11cb8 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java
@@ -167,10 +167,8 @@
 
     ShadowDisplayManager.removeDisplay(displayId);
 
-    assertThat(events).containsExactly(
-        "Added " + displayId,
-        "Changed " + displayId,
-        "Removed " + displayId);
+    assertThat(events)
+        .containsExactly("Added " + displayId, "Changed " + displayId, "Removed " + displayId);
   }
 
   @Test
@@ -187,9 +185,7 @@
     assertThat(display.getHeight()).isEqualTo(100);
     assertThat(display.getOrientation()).isEqualTo(Surface.ROTATION_90);
 
-    assertThat(events).containsExactly(
-        "Added " + displayId,
-        "Changed " + displayId);
+    assertThat(events).containsExactly("Added " + displayId, "Changed " + displayId);
   }
 
   @Test
@@ -312,36 +308,44 @@
     assertThat(shadowOf(instance).getSaturationLevel()).isEqualTo(1.0f);
   }
 
-  @Test @Config(minSdk = P)
+  @Test
+  @Config(minSdk = P)
   public void setSaturationLevel_setToValueGreaterThanOne_shouldThrow() {
     try {
       instance.setSaturationLevel(1.1f);
       fail("Expected IllegalArgumentException thrown");
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
-  @Test @Config(minSdk = P)
+  @Test
+  @Config(minSdk = P)
   public void setSaturationLevel_setToNegativeValue_shouldThrow() {
     try {
       instance.setSaturationLevel(-0.1f);
       fail("Expected IllegalArgumentException thrown");
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
-  @Test @Config(minSdk = P)
+  @Test
+  @Config(minSdk = P)
   public void setSaturationLevel_setToValueGreaterThanOneViaShadow_shouldThrow() {
     try {
       shadowOf(instance).setSaturationLevel(1.1f);
       fail("Expected IllegalArgumentException thrown");
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
-  @Test @Config(minSdk = P)
+  @Test
+  @Config(minSdk = P)
   public void setSaturationLevel_setToNegativevalueViaShadow_shouldThrow() {
     try {
       shadowOf(instance).setSaturationLevel(-0.1f);
       fail("Expected IllegalArgumentException thrown");
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
   @Test
@@ -450,6 +454,56 @@
     assertThat(instance.getBrightnessEvents()).containsExactlyElementsIn(events);
   }
 
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void setNaturallyPortrait_setPortrait_isRotatedWhenLandscape() {
+    ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, true);
+
+    ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land");
+
+    assertThat(ShadowDisplay.getDefaultDisplay().getRotation()).isEqualTo(Surface.ROTATION_90);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void setNaturallyPortrait_setPortraitWhenLandscape_isRotated() {
+    ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land");
+
+    ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, true);
+
+    assertThat(ShadowDisplay.getDefaultDisplay().getRotation()).isEqualTo(Surface.ROTATION_90);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void setNaturallyPortrait_setLandscape_isNotRotatedWhenLandscape() {
+    ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, false);
+
+    ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land");
+
+    assertThat(ShadowDisplay.getDefaultDisplay().getRotation()).isEqualTo(Surface.ROTATION_0);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void setNaturallyPortrait_setLandscape_isRotatedWhenPortrait() {
+    ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, false);
+
+    ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "port");
+
+    assertThat(ShadowDisplay.getDefaultDisplay().getRotation()).isEqualTo(Surface.ROTATION_90);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void setNaturallyPortrait_setLandscapeWhenLandscape_isNotRotated() {
+    ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land");
+
+    ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, false);
+
+    assertThat(ShadowDisplay.getDefaultDisplay().getRotation()).isEqualTo(Surface.ROTATION_0);
+  }
+
   // because DisplayManagerGlobal don't exist in Jelly Bean,
   // and we don't want them resolved as part of the test class.
   static class HideFromJB {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java
index e5d5c9d..4da4963 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java
@@ -17,6 +17,11 @@
 @Config(minSdk = VERSION_CODES.LOLLIPOP)
 public final class ShadowEGL14Test {
   @Test
+  public void eglGetCurrentContext() {
+    assertThat(EGL14.eglGetCurrentContext()).isNotNull();
+  }
+
+  @Test
   public void eglGetDisplay() {
     assertThat(EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)).isNotNull();
   }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowFingerprintManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowFingerprintManagerTest.java
index 964c04a..f0f827f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowFingerprintManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowFingerprintManagerTest.java
@@ -11,6 +11,7 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.hardware.fingerprint.FingerprintManager.CryptoObject;
+import android.os.Build.VERSION_CODES;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.security.Signature;
@@ -112,4 +113,10 @@
 
     assertThat(manager.isHardwareDetected()).isTrue();
   }
+
+  @Test
+  @Config(sdk = VERSION_CODES.S)
+  public void getSensorPropertiesInternal_notNull() {
+    assertThat(manager.getSensorPropertiesInternal()).isNotNull();
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLauncherAppsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLauncherAppsTest.java
index edff632..47c16b4 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLauncherAppsTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLauncherAppsTest.java
@@ -5,8 +5,9 @@
 import static android.os.Build.VERSION_CODES.N;
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.os.Build.VERSION_CODES.P;
 import static android.os.Build.VERSION_CODES.R;
-import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
@@ -18,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IncrementalStatesInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherActivityInfoInternal;
 import android.content.pm.LauncherApps;
@@ -25,6 +27,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -39,6 +42,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.ReflectionHelpers;
@@ -100,12 +104,12 @@
   }
 
   @Test
-  @Config(minSdk = O, maxSdk = R)
+  @Config(minSdk = O)
   public void getShortcutConfigActivityList_getsShortcutsForPackageName() {
     LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE);
     LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME_2, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME_2, USER_HANDLE);
     shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1);
     shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo2);
 
@@ -114,26 +118,12 @@
   }
 
   @Test
-  @Config(minSdk = S)
-  public void getShortcutConfigActivityListS_getsShortcutsForPackageName() {
-    LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME, USER_HANDLE);
-    LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME_2, USER_HANDLE);
-    shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1);
-    shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo2);
-
-    assertThat(launcherApps.getShortcutConfigActivityList(TEST_PACKAGE_NAME, USER_HANDLE))
-        .contains(launcherActivityInfo1);
-  }
-
-  @Test
-  @Config(minSdk = O, maxSdk = R)
+  @Config(minSdk = O)
   public void getShortcutConfigActivityList_getsShortcutsForUserHandle() {
     LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE);
     LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME, UserHandle.of(10));
+        createLauncherActivityInfo(TEST_PACKAGE_NAME, UserHandle.of(10));
     shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1);
     shadowOf(launcherApps).addShortcutConfigActivity(UserHandle.of(10), launcherActivityInfo2);
 
@@ -142,45 +132,14 @@
   }
 
   @Test
-  @Config(minSdk = S)
-  public void getShortcutConfigActivityListS_getsShortcutsForUserHandle() {
-    LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME, USER_HANDLE);
-    LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME, UserHandle.of(10));
-    shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1);
-    shadowOf(launcherApps).addShortcutConfigActivity(UserHandle.of(10), launcherActivityInfo2);
-
-    assertThat(launcherApps.getShortcutConfigActivityList(TEST_PACKAGE_NAME, UserHandle.of(10)))
-        .contains(launcherActivityInfo2);
-  }
-
-  @Test
-  @Config(minSdk = O, maxSdk = R)
+  @Config(minSdk = O)
   public void getShortcutConfigActivityList_packageNull_getsShortcutFromAllPackagesForUser() {
     LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE);
     LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME_2, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME_2, USER_HANDLE);
     LauncherActivityInfo launcherActivityInfo3 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME_3, UserHandle.of(10));
-    shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1);
-    shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo2);
-    shadowOf(launcherApps).addShortcutConfigActivity(UserHandle.of(10), launcherActivityInfo3);
-
-    assertThat(launcherApps.getShortcutConfigActivityList(null, USER_HANDLE))
-        .containsExactly(launcherActivityInfo1, launcherActivityInfo2);
-  }
-
-  @Test
-  @Config(minSdk = S)
-  public void getShortcutConfigActivityListS_packageNull_getsShortcutFromAllPackagesForUser() {
-    LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME, USER_HANDLE);
-    LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME_2, USER_HANDLE);
-    LauncherActivityInfo launcherActivityInfo3 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME_3, UserHandle.of(10));
+        createLauncherActivityInfo(TEST_PACKAGE_NAME_3, UserHandle.of(10));
     shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo1);
     shadowOf(launcherApps).addShortcutConfigActivity(USER_HANDLE, launcherActivityInfo2);
     shadowOf(launcherApps).addShortcutConfigActivity(UserHandle.of(10), launcherActivityInfo3);
@@ -209,12 +168,12 @@
   }
 
   @Test
-  @Config(minSdk = N, maxSdk = R)
+  @Config(minSdk = N)
   public void testGetActivityList() {
     assertThat(launcherApps.getActivityList(TEST_PACKAGE_NAME, USER_HANDLE)).isEmpty();
 
     LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE);
 
     shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo1);
     assertThat(launcherApps.getActivityList(TEST_PACKAGE_NAME, USER_HANDLE))
@@ -222,25 +181,14 @@
   }
 
   @Test
-  @Config(minSdk = S)
-  public void testGetActivityListS() {
-    LauncherActivityInfo launcherActivityInfo =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME, USER_HANDLE);
-    shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo);
-
-    assertThat(launcherApps.getActivityList(TEST_PACKAGE_NAME, USER_HANDLE))
-        .contains(launcherActivityInfo);
-  }
-
-  @Test
-  @Config(minSdk = N, maxSdk = R)
+  @Config(minSdk = N)
   public void testGetActivityList_packageNull_getsActivitiesFromAllPackagesForUser() {
     LauncherActivityInfo launcherActivityInfo1 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME, USER_HANDLE);
     LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME_2, USER_HANDLE);
+        createLauncherActivityInfo(TEST_PACKAGE_NAME_2, USER_HANDLE);
     LauncherActivityInfo launcherActivityInfo3 =
-        createLauncherActivityInfoPostN(TEST_PACKAGE_NAME_3, UserHandle.of(10));
+        createLauncherActivityInfo(TEST_PACKAGE_NAME_3, UserHandle.of(10));
     shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo1);
     shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo2);
     shadowOf(launcherApps).addActivity(UserHandle.of(10), launcherActivityInfo3);
@@ -250,17 +198,17 @@
   }
 
   @Test
-  @Config(minSdk = S)
-  public void testGetActivityListS_getsActivitiesFromAllPackagesForUser() {
-    LauncherActivityInfo launcherActivityInfo =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME, USER_HANDLE);
-    LauncherActivityInfo launcherActivityInfo2 =
-        createLauncherActivityInfoS(TEST_PACKAGE_NAME_2, USER_HANDLE);
-    shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo);
-    shadowOf(launcherApps).addActivity(USER_HANDLE, launcherActivityInfo2);
+  @Config(minSdk = L)
+  public void testIsActivityEnabled() {
+    ComponentName c1 = new ComponentName(ApplicationProvider.getApplicationContext(), "Activity1");
+    ComponentName c2 = new ComponentName(ApplicationProvider.getApplicationContext(), "Activity2");
+    ComponentName c3 = new ComponentName("other", "Activity1");
+    assertThat(launcherApps.isActivityEnabled(c1, USER_HANDLE)).isFalse();
 
-    assertThat(launcherApps.getActivityList(null, USER_HANDLE))
-        .containsExactly(launcherActivityInfo, launcherActivityInfo2);
+    shadowOf(launcherApps).setActivityEnabled(USER_HANDLE, c1);
+    assertThat(launcherApps.isActivityEnabled(c1, USER_HANDLE)).isTrue();
+    assertThat(launcherApps.isActivityEnabled(c2, USER_HANDLE)).isFalse();
+    assertThat(launcherApps.isActivityEnabled(c3, USER_HANDLE)).isFalse();
   }
 
   @Test
@@ -402,6 +350,35 @@
     assertThat(launcherApps.hasShortcutHostPermission()).isFalse();
   }
 
+  @Test
+  @Config(minSdk = P)
+  public void getSuspendedPackageLauncherExtras_returnsBundle() {
+    Bundle bundle = new Bundle();
+    bundle.putInt("suspended_app", 5);
+    shadowOf(launcherApps)
+        .addSuspendedPackageLauncherExtras(USER_HANDLE, TEST_PACKAGE_NAME_2, bundle);
+
+    assertThat(launcherApps.getSuspendedPackageLauncherExtras(TEST_PACKAGE_NAME_2, USER_HANDLE))
+        .isEqualTo(bundle);
+  }
+
+  @Test
+  @Config(minSdk = P)
+  public void getSuspendedPackageLauncherExtras_returnsEmptyBundle() {
+    Throwable throwable =
+        assertThrows(
+            NameNotFoundException.class,
+            () -> launcherApps.getSuspendedPackageLauncherExtras(TEST_PACKAGE_NAME, USER_HANDLE));
+
+    assertThat(throwable)
+        .hasMessageThat()
+        .isEqualTo(
+            "Suspended package extras for  "
+                + TEST_PACKAGE_NAME
+                + " not found for user "
+                + USER_HANDLE.getIdentifier());
+  }
+
   private List<ShortcutInfo> getPinnedShortcuts(String packageName, ComponentName activity) {
     ShortcutQuery query = new ShortcutQuery();
     query.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_PINNED);
@@ -410,31 +387,40 @@
     return launcherApps.getShortcuts(query, Process.myUserHandle());
   }
 
-  private LauncherActivityInfo createLauncherActivityInfoS(String packageName, UserHandle user) {
-    ActivityInfo info = new ActivityInfo();
-    info.packageName = packageName;
-    info.name = packageName;
-    info.nonLocalizedLabel = packageName;
-    LauncherActivityInfoInternal launcherActivityInfoInternal =
-        new LauncherActivityInfoInternal(info, null);
-
-    return ReflectionHelpers.callConstructor(
-        LauncherActivityInfo.class,
-        ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()),
-        ClassParameter.from(UserHandle.class, user),
-        ClassParameter.from(LauncherActivityInfoInternal.class, launcherActivityInfoInternal));
-  }
-
-  private LauncherActivityInfo createLauncherActivityInfoPostN(
+  private LauncherActivityInfo createLauncherActivityInfo(
       String packageName, UserHandle userHandle) {
     ActivityInfo info = new ActivityInfo();
     info.packageName = packageName;
     info.name = packageName;
     info.nonLocalizedLabel = packageName;
-    return ReflectionHelpers.callConstructor(
-        LauncherActivityInfo.class,
-        ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()),
-        ClassParameter.from(ActivityInfo.class, info),
-        ClassParameter.from(UserHandle.class, userHandle));
+    if (RuntimeEnvironment.getApiLevel() <= R) {
+      return ReflectionHelpers.callConstructor(
+          LauncherActivityInfo.class,
+          ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()),
+          ClassParameter.from(ActivityInfo.class, info),
+          ClassParameter.from(UserHandle.class, userHandle));
+    } else if (RuntimeEnvironment.getApiLevel() <= TIRAMISU) {
+      LauncherActivityInfoInternal launcherActivityInfoInternal =
+          new LauncherActivityInfoInternal(info, null, userHandle);
+
+      return ReflectionHelpers.callConstructor(
+          LauncherActivityInfo.class,
+          ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()),
+          ClassParameter.from(UserHandle.class, userHandle),
+          ClassParameter.from(LauncherActivityInfoInternal.class, launcherActivityInfoInternal));
+    } else {
+
+      LauncherActivityInfoInternal launcherActivityInfoInternal =
+          ReflectionHelpers.callConstructor(
+              LauncherActivityInfoInternal.class,
+              ClassParameter.from(ActivityInfo.class, info),
+              ClassParameter.from(IncrementalStatesInfo.class, null),
+              ClassParameter.from(UserHandle.class, userHandle));
+
+      return ReflectionHelpers.callConstructor(
+          LauncherActivityInfo.class,
+          ClassParameter.from(Context.class, ApplicationProvider.getApplicationContext()),
+          ClassParameter.from(LauncherActivityInfoInternal.class, launcherActivityInfoInternal));
+    }
   }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java
index 2299246..772d89a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java
@@ -30,6 +30,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.ReflectionHelpers;
@@ -37,7 +38,6 @@
 
 /** Tests for {@link ShadowMediaController}. */
 @RunWith(AndroidJUnit4.class)
-@Config(maxSdk = Q)
 public final class ShadowMediaControllerTest {
 
   private MediaController mediaController;
@@ -48,9 +48,19 @@
   public void setUp() {
     Context context = ApplicationProvider.getApplicationContext();
     ISessionController binder = mock(ISessionController.class);
-    MediaSession.Token token =
-        ReflectionHelpers.callConstructor(
-            MediaSession.Token.class, ClassParameter.from(ISessionController.class, binder));
+
+    MediaSession.Token token = null;
+    if (RuntimeEnvironment.getApiLevel() <= Q) {
+      token =
+          ReflectionHelpers.callConstructor(
+              MediaSession.Token.class, ClassParameter.from(ISessionController.class, binder));
+    } else {
+      token =
+          ReflectionHelpers.callConstructor(
+              MediaSession.Token.class,
+              ClassParameter.from(int.class, 0),
+              ClassParameter.from(ISessionController.class, binder));
+    }
     mediaController = new MediaController(context, token);
     shadowMediaController = Shadow.extract(mediaController);
   }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaExtractorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaExtractorTest.java
index 7a9f61a..f6b58a8 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaExtractorTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaExtractorTest.java
@@ -1,10 +1,13 @@
 package org.robolectric.shadows;
 
+import static android.os.Build.VERSION_CODES.O;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertThrows;
 
 import android.media.MediaExtractor;
+import android.media.MediaExtractor.MetricsConstants;
 import android.media.MediaFormat;
+import android.os.PersistableBundle;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -12,6 +15,7 @@
 import java.util.Random;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadows.util.DataSource;
 
 /** Tests for ShadowMediaExtractor */
@@ -174,4 +178,19 @@
 
     assertThat(mediaExtractor.getTrackCount()).isEqualTo(0);
   }
+
+  @Test
+  @Config(minSdk = O)
+  public void getMetrics_returnsMetrics() throws IOException {
+    PersistableBundle metrics = new PersistableBundle();
+    metrics.putString(MetricsConstants.MIME_TYPE, "audio/mp4");
+    ShadowMediaExtractor.setMetrics(dataSource, metrics);
+
+    MediaExtractor mediaExtractor = new MediaExtractor();
+    mediaExtractor.setDataSource(path);
+
+    assertThat(mediaExtractor.getMetrics().size()).isEqualTo(1);
+    assertThat(mediaExtractor.getMetrics().getString(MetricsConstants.MIME_TYPE))
+        .isEqualTo("audio/mp4");
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMetadataRetrieverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMetadataRetrieverTest.java
index b727f9c..9cbd7df 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMetadataRetrieverTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMetadataRetrieverTest.java
@@ -65,6 +65,14 @@
   }
 
   @Test
+  public void getFrameAtTime_withoutTime() {
+    addFrame(path, 1, bitmap);
+    addFrame(path, 2, bitmap2);
+    retriever.setDataSource(path);
+    assertThat(retriever.getFrameAtTime()).isEqualTo(bitmap);
+  }
+
+  @Test
   @Config(minSdk = O_MR1)
   public void getScaledFrameAtTime_shouldDependOnDataSource() {
     addScaledFrame(toDataSource(path), 1, 1024, 768, bitmap);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java
index 894b518..53e3836 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaMuxerTest.java
@@ -1,6 +1,7 @@
 package org.robolectric.shadows;
 
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.O;
 import static com.google.common.truth.Truth.assertThat;
 
 import android.media.MediaCodec;
@@ -9,6 +10,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
@@ -40,32 +42,59 @@
   @Test
   @Config(minSdk = LOLLIPOP)
   public void basicMuxingFlow_sameZeroOffset() throws IOException {
-    basicMuxingFlow(0, 0, INPUT_SIZE);
+    String tempFilePath =
+        tempDirectory.create("dir").resolve(UUID.randomUUID().toString()).toString();
+    MediaMuxer muxer = new MediaMuxer(tempFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+    basicMuxingFlow(muxer, tempFilePath, 0, 0, INPUT_SIZE);
   }
 
   @Test
   @Config(minSdk = LOLLIPOP)
   public void basicMuxingFlow_sameNonZeroOffset() throws IOException {
-    basicMuxingFlow(10, 10, INPUT_SIZE);
+    String tempFilePath =
+        tempDirectory.create("dir").resolve(UUID.randomUUID().toString()).toString();
+    MediaMuxer muxer = new MediaMuxer(tempFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+    basicMuxingFlow(muxer, tempFilePath, 10, 10, INPUT_SIZE);
   }
 
   @Test
   @Config(minSdk = LOLLIPOP)
   public void basicMuxingFlow_nonSameButSmallerOffset() throws IOException {
-    basicMuxingFlow(0, 10, INPUT_SIZE);
+    String tempFilePath =
+        tempDirectory.create("dir").resolve(UUID.randomUUID().toString()).toString();
+    MediaMuxer muxer = new MediaMuxer(tempFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+    basicMuxingFlow(muxer, tempFilePath, 0, 10, INPUT_SIZE);
   }
 
   @Test
   @Config(minSdk = LOLLIPOP)
   public void basicMuxingFlow_nonSameButLargerOffset() throws IOException {
-    basicMuxingFlow(10, 0, INPUT_SIZE);
-  }
-
-  private void basicMuxingFlow(int bufInfoOffset, int bufOffset, int inputSize) throws IOException {
     String tempFilePath =
         tempDirectory.create("dir").resolve(UUID.randomUUID().toString()).toString();
     MediaMuxer muxer = new MediaMuxer(tempFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
 
+    basicMuxingFlow(muxer, tempFilePath, 10, 0, INPUT_SIZE);
+  }
+
+  @Test
+  @Config(minSdk = O)
+  public void basicMuxingFlow_fileDescriptorConstructor_sameZeroOffset() throws IOException {
+    String tempFilePath =
+        tempDirectory.create("dir").resolve(UUID.randomUUID().toString()).toString();
+    try (FileOutputStream outputStream = new FileOutputStream(tempFilePath)) {
+      MediaMuxer muxer =
+          new MediaMuxer(outputStream.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+      basicMuxingFlow(muxer, tempFilePath, 0, 0, INPUT_SIZE);
+    }
+  }
+
+  private void basicMuxingFlow(
+      MediaMuxer muxer, String tempFilePath, int bufInfoOffset, int bufOffset, int inputSize)
+      throws IOException {
     MediaFormat format = new MediaFormat();
     format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
 
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java
index a5cf073..d913c99 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java
@@ -212,6 +212,6 @@
   }
 
   private static Tag createMockTag() {
-    return Tag.createMockTag(new byte[0], new int[0], new Bundle[0]);
+    return Tag.createMockTag(new byte[0], new int[0], new Bundle[0], 0L);
   }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
index 1295733..81506f3 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
@@ -16,6 +16,7 @@
 import static android.content.pm.ApplicationInfo.FLAG_VM_SAFE_MODE;
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
@@ -30,6 +31,7 @@
 import static android.content.pm.PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
 import static android.content.pm.PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
 import static android.content.pm.PackageManager.VERIFICATION_ALLOW;
+import static android.content.pm.PackageManager.VERIFICATION_REJECT;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
@@ -48,6 +50,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -62,6 +65,7 @@
 import android.app.Activity;
 import android.app.Application;
 import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -78,6 +82,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.ComponentEnabledSetting;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager.OnPermissionsChangedListener;
 import android.content.pm.PackageManager.PackageInfoFlags;
@@ -127,6 +132,7 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 import org.robolectric.R;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
@@ -153,6 +159,7 @@
   private static final String REAL_TEST_APP_PACKAGE_NAME = "org.robolectric.exampleapp";
   private static final String TEST_PACKAGE3_NAME = "com.a.third.package";
   private static final int TEST_PACKAGE_VERSION_CODE = 10000;
+  public static final int INSTALL_VERIFICATION_ID = 1234;
 
   @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
   private Context context;
@@ -2693,10 +2700,76 @@
 
   @Test
   @Config(minSdk = JELLY_BEAN_MR1)
-  public void extendPendingInstallTimeout() {
-    packageManager.extendVerificationTimeout(1234, 0, 1000);
+  public void
+      extendPendingInstallTimeout_verificationRejectAtTimeout_extendsPendingInstallTimeoutAndsetsCodeAtTimeoutToReject() {
+    packageManager.extendVerificationTimeout(
+        INSTALL_VERIFICATION_ID,
+        /* verificationCodeAtTimeout= */ VERIFICATION_REJECT,
+        /* millisecondsToDelay= */ 1000);
 
-    assertThat(shadowOf(packageManager).getVerificationExtendedTimeout(1234)).isEqualTo(1000);
+    assertThat(shadowOf(packageManager).getVerificationExtendedTimeout(INSTALL_VERIFICATION_ID))
+        .isEqualTo(1000);
+    assertThat(
+            shadowOf(packageManager).getVerificationCodeAtTimeoutExtension(INSTALL_VERIFICATION_ID))
+        .isEqualTo(VERIFICATION_REJECT);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void
+      extendPendingInstallTimeout_verificationAllowAtTimeout_extendsPendingInstallTimeoutAndsetsCodeAtTimeoutToAllow() {
+    packageManager.extendVerificationTimeout(
+        INSTALL_VERIFICATION_ID,
+        /* verificationCodeAtTimeout= */ VERIFICATION_ALLOW,
+        /* millisecondsToDelay= */ 1000);
+
+    assertThat(shadowOf(packageManager).getVerificationExtendedTimeout(INSTALL_VERIFICATION_ID))
+        .isEqualTo(1000);
+    assertThat(
+            shadowOf(packageManager).getVerificationCodeAtTimeoutExtension(INSTALL_VERIFICATION_ID))
+        .isEqualTo(VERIFICATION_ALLOW);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void whenVerificationTimeOutNotExtended_verificationCodeAtTimeoutIsAllow() {
+    assertThat(shadowOf(packageManager).getVerificationExtendedTimeout(INSTALL_VERIFICATION_ID))
+        .isEqualTo(0);
+    // Default verdict is to allow installation.
+    assertThat(
+            shadowOf(packageManager).getVerificationCodeAtTimeoutExtension(INSTALL_VERIFICATION_ID))
+        .isEqualTo(VERIFICATION_ALLOW);
+  }
+
+  @Test
+  @Config(minSdk = JELLY_BEAN_MR1)
+  public void triggerInstallVerificationTimeout_broadcastsPackageVerifiedIntent() {
+    ShadowPackageManager shadowPackageManagerMock =
+        mock(ShadowPackageManager.class, Mockito.CALLS_REAL_METHODS);
+
+    doReturn(VERIFICATION_REJECT)
+        .when(shadowPackageManagerMock)
+        .getVerificationCodeAtTimeoutExtension(INSTALL_VERIFICATION_ID);
+
+    List<Integer> verificationIdList = new ArrayList<>();
+    List<Integer> verificationResultList = new ArrayList<>();
+    BroadcastReceiver receiver =
+        new BroadcastReceiver() {
+          @Override
+          public void onReceive(Context context, Intent intent) {
+            verificationIdList.add(intent.getIntExtra(EXTRA_VERIFICATION_ID, 0));
+            verificationResultList.add(
+                intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_RESULT, 0));
+          }
+        };
+    IntentFilter intentFilter = new IntentFilter(Intent.ACTION_PACKAGE_VERIFIED);
+    context.registerReceiver(receiver, intentFilter);
+
+    shadowPackageManagerMock.triggerInstallVerificationTimeout(
+        (Application) context, INSTALL_VERIFICATION_ID);
+
+    assertThat(verificationIdList).containsExactly(INSTALL_VERIFICATION_ID);
+    assertThat(verificationResultList).containsExactly(VERIFICATION_REJECT);
   }
 
   @Test
@@ -4402,6 +4475,93 @@
     assertThat(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)).isFalse();
   }
 
+  @Test
+  @Config(minSdk = S)
+  public void getProperty() throws NameNotFoundException {
+    assertThrows(
+        NameNotFoundException.class,
+        () ->
+            packageManager.getProperty(
+                "myproperty", RuntimeEnvironment.getApplication().getPackageName()));
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void getProperty_component() throws NameNotFoundException {
+    final ComponentName componentName =
+        new ComponentName(RuntimeEnvironment.getApplication().getPackageName(), "mycomponentname");
+    assertThrows(
+        NameNotFoundException.class, () -> packageManager.getProperty("myproperty", componentName));
+  }
+
+  @Test
+  @Config(minSdk = TIRAMISU)
+  public void setComponentEnabledSettingsSameSettings_getComponentEnabledSettingsConsistent() {
+    ShadowApplicationPackageManager pm = (ShadowApplicationPackageManager) shadowOf(packageManager);
+    ComponentName componentName0 = new ComponentName(context, "mycomponentname0");
+    ComponentName componentName1 = new ComponentName(context, "mycomponentname1");
+    ComponentEnabledSetting componentEnabledSetting0 =
+        new ComponentEnabledSetting(
+            componentName0,
+            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+            PackageManager.DONT_KILL_APP);
+    ComponentEnabledSetting componentEnabledSetting1 =
+        new ComponentEnabledSetting(
+            componentName1,
+            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+            PackageManager.DONT_KILL_APP);
+    ImmutableList<ComponentEnabledSetting> componentEnabledSettings =
+        ImmutableList.of(componentEnabledSetting0, componentEnabledSetting1);
+
+    pm.setComponentEnabledSettings(componentEnabledSettings);
+
+    for (ComponentEnabledSetting setting : componentEnabledSettings) {
+      assertThat(pm.getComponentEnabledSetting(setting.getComponentName()))
+          .isEqualTo(setting.getEnabledState());
+    }
+  }
+
+  @Test
+  @Config(minSdk = TIRAMISU)
+  public void setComponentEnabledSettingsDifferentSettings_getComponentEnabledSettingsConsistent() {
+    ShadowApplicationPackageManager pm = (ShadowApplicationPackageManager) shadowOf(packageManager);
+    ComponentName componentName0 = new ComponentName(context, "mycomponentname0");
+    ComponentName componentName1 = new ComponentName(context, "mycomponentname1");
+    ComponentEnabledSetting componentEnabledSetting0 =
+        new ComponentEnabledSetting(
+            componentName0,
+            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+            PackageManager.DONT_KILL_APP);
+    ComponentEnabledSetting componentEnabledSetting1 =
+        new ComponentEnabledSetting(
+            componentName1,
+            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+            PackageManager.DONT_KILL_APP);
+    ImmutableList<ComponentEnabledSetting> componentEnabledSettings =
+        ImmutableList.of(componentEnabledSetting0, componentEnabledSetting1);
+
+    pm.setComponentEnabledSettings(componentEnabledSettings);
+
+    for (ComponentEnabledSetting setting : componentEnabledSettings) {
+      assertThat(pm.getComponentEnabledSetting(setting.getComponentName()))
+          .isEqualTo(setting.getEnabledState());
+    }
+  }
+
+  @Test
+  @Config(minSdk = Q)
+  public void setSyntheticAppDetailsActivityEnabled_getConsistent() {
+    ShadowApplicationPackageManager pm = (ShadowApplicationPackageManager) shadowOf(packageManager);
+
+    pm.setSyntheticAppDetailsActivityEnabled(TEST_PACKAGE_NAME, true);
+    boolean before = pm.getSyntheticAppDetailsActivityEnabled(TEST_PACKAGE_NAME);
+    pm.setSyntheticAppDetailsActivityEnabled(TEST_PACKAGE_NAME, false);
+    boolean after = pm.getSyntheticAppDetailsActivityEnabled(TEST_PACKAGE_NAME);
+
+    assertThat(before).isTrue();
+    assertThat(after).isFalse();
+  }
+
   public String[] setPackagesSuspended(
       String[] packageNames,
       boolean suspended,
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintTest.java
index 6ae57f9..7744b95 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintTest.java
@@ -71,6 +71,15 @@
   }
 
   @Test
+  public void shouldSetStrikeThruText() {
+    Paint paint = new Paint();
+    paint.setStrikeThruText(true);
+    assertThat(paint.isStrikeThruText()).isTrue();
+    paint.setStrikeThruText(false);
+    assertThat(paint.isStrikeThruText()).isFalse();
+  }
+
+  @Test
   public void measureTextActuallyMeasuresLength() {
     Paint paint = new Paint();
     assertThat(paint.measureText("Hello")).isEqualTo(5.0f);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java
index 8801f1e..71e85c9 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java
@@ -14,6 +14,7 @@
 import static org.robolectric.Shadows.shadowOf;
 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
 
+import android.os.Build.VERSION_CODES;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -33,6 +34,7 @@
 import org.junit.Test;
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.res.android.Ref;
 import org.robolectric.shadow.api.Shadow;
@@ -621,6 +623,43 @@
     assertThat(foregroundThreadReceived.get()).isTrue();
   }
 
+  @Test
+  @Config(minSdk = VERSION_CODES.M)
+  public void runOneTask_ignoreSyncBarrier() {
+    int barrier = Looper.getMainLooper().getQueue().postSyncBarrier();
+
+    final AtomicBoolean wasRun = new AtomicBoolean(false);
+    new Handler(Looper.getMainLooper()).post(() -> wasRun.set(true));
+
+    ShadowPausedLooper shadowPausedLooper = Shadow.extract(Looper.getMainLooper());
+    shadowPausedLooper.runOneTask();
+
+    // tasks should not be executed when blocked by a sync barrier
+    assertThat(wasRun.get()).isFalse();
+    // sync barrier will throw if the barrier was not found.
+    Looper.getMainLooper().getQueue().removeSyncBarrier(barrier);
+
+    shadowPausedLooper.runOneTask();
+    assertThat(wasRun.get()).isTrue();
+  }
+
+  @Test
+  @Config(minSdk = VERSION_CODES.P)
+  public void runOneTask_ignoreSyncBarrier_with_async() {
+    int barrier = Looper.getMainLooper().getQueue().postSyncBarrier();
+
+    final AtomicBoolean wasRun = new AtomicBoolean(false);
+    Handler.createAsync(Looper.getMainLooper()).post(() -> wasRun.set(true));
+
+    ShadowPausedLooper shadowPausedLooper = Shadow.extract(Looper.getMainLooper());
+    shadowPausedLooper.runOneTask();
+
+    // tasks should be executed as the handler is async
+    assertThat(wasRun.get()).isTrue();
+    // sync barrier will throw if the barrier was not found.
+    Looper.getMainLooper().getQueue().removeSyncBarrier(barrier);
+  }
+
   private static class BlockingRunnable implements Runnable {
     CountDownLatch latch = new CountDownLatch(1);
 
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java
index 7d50e6f..02219fe 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java
@@ -934,4 +934,30 @@
                 .toString())
         .startsWith("PendingIntent");
   }
+
+  @Test
+  public void isTargetedToPackage_returnsFalse() {
+    Intent embedded = new Intent().setComponent(new ComponentName("pkg", "cls")).setPackage("pkg");
+    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, embedded, 0);
+
+    assertThat(pendingIntent.isTargetedToPackage()).isFalse();
+  }
+
+  @Test
+  public void isTargetedToPackage_returnsTrue_whenOnlyComponentSet() {
+    Intent embedded = new Intent().setComponent(new ComponentName("pkg", "cls"));
+    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, embedded, 0);
+
+    // This is weird but the implementation checks both component and package and this method should
+    // really be named `isNotExplicitIntentWithPackageSet`.
+    assertThat(pendingIntent.isTargetedToPackage()).isTrue();
+  }
+
+  @Test
+  public void isTargetedToPackage_returnsTrue() {
+    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent("ACTION!"), 0);
+
+    // This is weird as well. `isTargetedToPackage` is really `isNotExplicitIntent`.
+    assertThat(pendingIntent.isTargetedToPackage()).isTrue();
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java
index f21c21f..f70bcd2 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java
@@ -1,5 +1,6 @@
 package org.robolectric.shadows;
 
+import static android.os.Build.VERSION_CODES.KITKAT;
 import static com.google.common.truth.Truth.assertThat;
 import static org.robolectric.Robolectric.buildActivity;
 import static org.robolectric.Shadows.shadowOf;
@@ -15,6 +16,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
 
 @RunWith(AndroidJUnit4.class)
 public class ShadowPreferenceGroupTest {
@@ -32,7 +37,7 @@
 
     group = new TestPreferenceGroup(activity, attrs);
     shadow = shadowOf(group);
-    shadow.callOnAttachedToHierarchy(new PreferenceManager(activity, 0));
+    shadow.callOnAttachedToHierarchy(newPreferenceManager(activity, 0));
 
     pref1 = new Preference(activity);
     pref1.setKey("pref1");
@@ -41,6 +46,18 @@
     pref2.setKey("pref2");
   }
 
+  private static PreferenceManager newPreferenceManager(Activity activity, int firstRequestCode) {
+    if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
+      return new PreferenceManager(activity, 0);
+    } else {
+      // Constructor was not public before KITKAT.
+      return ReflectionHelpers.callConstructor(
+          PreferenceManager.class,
+          ClassParameter.from(Activity.class, activity),
+          ClassParameter.from(int.class, 0));
+    }
+  }
+
   @Test
   public void shouldInheritFromPreference() {
     assertThat(shadow).isInstanceOf(ShadowPreference.class);
@@ -136,6 +153,7 @@
     assertThat(group.findPreference(pref2.getKey())).isSameInstanceAs(pref2);
   }
 
+  @Config(minSdk = KITKAT)
   @Test
   public void shouldFindPreferenceRecursively() {
     TestPreferenceGroup group2 = new TestPreferenceGroup(activity, attrs);
@@ -148,6 +166,7 @@
     assertThat(group.findPreference(pref2.getKey())).isSameInstanceAs(pref2);
   }
 
+  @Config(minSdk = KITKAT)
   @Test
   public void shouldSetEnabledRecursively() {
     boolean[] values = {false, true};
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
index da34401..a4732b9 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
@@ -2,8 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.N_MR1;
 import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.robolectric.Shadows.shadowOf;
 import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
 
@@ -85,7 +84,7 @@
 
   @Test
   public void openRawResourceFd_shouldReturnsNullForLegacyResource() throws Exception {
-    assumeTrue(useLegacy());
+    assume().that(useLegacy()).isTrue();
     try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) {
         assertThat(afd).isNull();
     }
@@ -93,7 +92,7 @@
 
   @Test
   public void openRawResourceFd_shouldReturnsValidFdForUnCompressFile() throws Exception {
-    assumeFalse(useLegacy());
+    assume().that(useLegacy()).isFalse();
     try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) {
         assertThat(afd).isNotNull();
     }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSQLiteConnectionTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSQLiteConnectionTest.java
index 323511f..878777c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSQLiteConnectionTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSQLiteConnectionTest.java
@@ -3,8 +3,8 @@
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 import static org.robolectric.annotation.SQLiteMode.Mode.LEGACY;
 import static org.robolectric.shadows.ShadowLegacySQLiteConnection.convertSQLWithLocalizedUnicodeCollator;
 
@@ -64,7 +64,7 @@
 
   @Test
   public void testSqlConversion() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     assertThat(convertSQLWithLocalizedUnicodeCollator("select * from `routine`"))
         .isEqualTo("select * from `routine`");
 
@@ -88,7 +88,7 @@
 
   @Test
   public void testSQLWithLocalizedOrUnicodeCollatorShouldBeSortedAsNoCase() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     database.execSQL("insert into routine(name) values ('الصحافة اليدوية')");
     database.execSQL("insert into routine(name) values ('Hand press 1')");
     database.execSQL("insert into routine(name) values ('hand press 2')");
@@ -116,28 +116,28 @@
 
   @Test
   public void nativeOpen_addsConnectionToPool() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     assertThat(conn).isNotNull();
     assertWithMessage("open").that(conn.isOpen()).isTrue();
   }
 
   @Test
   public void nativeClose_closesConnection() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     ShadowLegacySQLiteConnection.nativeClose(ptr);
     assertWithMessage("open").that(conn.isOpen()).isFalse();
   }
 
   @Test
   public void reset_closesConnection() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     ShadowLegacySQLiteConnection.reset();
     assertWithMessage("open").that(conn.isOpen()).isFalse();
   }
 
   @Test
   public void reset_clearsConnectionCache() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     final Map<Long, SQLiteConnection> connectionsMap =
         ReflectionHelpers.getField(connections, "connectionsMap");
 
@@ -149,7 +149,7 @@
 
   @Test
   public void reset_clearsStatementCache() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     final Map<Long, SQLiteStatement> statementsMap =
         ReflectionHelpers.getField(connections, "statementsMap");
 
@@ -161,7 +161,7 @@
 
   @Test
   public void error_resultsInSpecificExceptionWithCause() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     try {
       database.execSQL("insert into routine(name) values ('Hand press 1')");
       ContentValues values = new ContentValues(1);
@@ -178,7 +178,7 @@
 
   @Test
   public void interruption_doesNotConcurrentlyModifyDatabase() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     Thread.currentThread().interrupt();
     try {
       database.execSQL("insert into routine(name) values ('الصحافة اليدوية')");
@@ -190,7 +190,7 @@
 
   @Test
   public void test_setUseInMemoryDatabase() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     assertThat(conn.isMemoryDatabase()).isFalse();
     ShadowSQLiteConnection.setUseInMemoryDatabase(true);
     SQLiteDatabase inMemoryDb = createDatabase("in_memory.db");
@@ -201,7 +201,7 @@
 
   @Test
   public void cancel_shouldCancelAllStatements() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     SQLiteStatement statement1 =
         database.compileStatement("insert into routine(name) values ('Hand press 1')");
     SQLiteStatement statement2 =
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java
index 1b0970b..536e6dc 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java
@@ -36,7 +36,7 @@
   @Test
   public void realCode_shouldSmoothScrollTo() {
     try {
-      System.setProperty("robolectric.nativeruntime.enableGraphics", "true");
+      System.setProperty("robolectric.useRealScrolling", "true");
       Activity activity = Robolectric.setupActivity(Activity.class);
       ScrollView scrollView = new ScrollView(activity);
       View view = new View(activity);
@@ -51,14 +51,14 @@
       assertEquals(7, scrollView.getScrollX());
       assertEquals(6, scrollView.getScrollY());
     } finally {
-      System.clearProperty("robolectric.nativeruntime.enableGraphics");
+      System.clearProperty("robolectric.useRealScrolling");
     }
   }
 
   @Test
   public void realCode_shouldSmoothScrollBy() {
     try {
-      System.setProperty("robolectric.nativeruntime.enableGraphics", "true");
+      System.setProperty("robolectric.useRealScrolling", "true");
       Activity activity = Robolectric.setupActivity(Activity.class);
       ScrollView scrollView = new ScrollView(activity);
       View view = new View(activity);
@@ -74,7 +74,7 @@
       assertEquals(17, scrollView.getScrollX());
       assertEquals(26, scrollView.getScrollY());
     } finally {
-      System.clearProperty("robolectric.nativeruntime.enableGraphics");
+      System.clearProperty("robolectric.useRealScrolling");
     }
   }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java
index cf1bf5b..68778c5 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java
@@ -4,6 +4,7 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import android.content.Context;
+import android.os.Build.VERSION_CODES;
 import android.os.IBinder;
 import android.os.ServiceManager;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -13,17 +14,24 @@
 import java.util.concurrent.atomic.AtomicReference;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
 
 /** Tests for {@link ShadowServiceManager}. */
 @RunWith(AndroidJUnit4.class)
 public final class ShadowServiceManagerTest {
-
+  
   @Test
   public void getService_available_shouldReturnNonNull() {
     assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNotNull();
   }
 
   @Test
+  @Config(sdk = VERSION_CODES.S)
+  public void getSensorPrivacyService_notNull() {
+    assertThat(ServiceManager.getService(Context.SENSOR_PRIVACY_SERVICE)).isNotNull();
+  }
+
+  @Test
   public void getService_unavailableService_shouldReturnNull() {
     ShadowServiceManager.setServiceAvailability(Context.INPUT_METHOD_SERVICE, false);
     assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNull();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundTriggerManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundTriggerManagerTest.java
new file mode 100644
index 0000000..a5c8a61
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundTriggerManagerTest.java
@@ -0,0 +1,80 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger.SoundTriggerManager;
+import android.os.Build.VERSION_CODES;
+import androidx.test.core.app.ApplicationProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = VERSION_CODES.N)
+public final class ShadowSoundTriggerManagerTest {
+  private ShadowSoundTriggerManager instance;
+
+  @Before
+  public void setUp() {
+    instance =
+        extract(
+            ApplicationProvider.getApplicationContext()
+                .getSystemService(SoundTriggerManager.class));
+  }
+
+  @Config(sdk = VERSION_CODES.S)
+  @Test
+  public void getModuleProperties_nullModuleProperties() throws Exception {
+    SoundTrigger.ModuleProperties moduleProperties = instance.getModuleProperties();
+    assertThat(moduleProperties).isNull();
+  }
+
+  @Config(sdk = VERSION_CODES.R)
+  @Test
+  public void getModuleProperties_nullPointExceptionAtAndroidR() throws Exception {
+    try {
+      SoundTrigger.ModuleProperties unused = instance.getModuleProperties();
+      fail("Expect NullPointException");
+    } catch (NullPointerException e) {
+      assertThat(e).isNotNull();
+    }
+  }
+
+  @Config(sdk = VERSION_CODES.R)
+  @Test
+  public void getModuleProperties_nonNullProperties() throws Exception {
+    instance.setModuleProperties(
+        (SoundTrigger.ModuleProperties) getModuleProperties("supportedModelArch", 1234));
+    SoundTrigger.ModuleProperties moduleProperties = instance.getModuleProperties();
+    assertThat(moduleProperties).isNotNull();
+    assertThat(moduleProperties.getSupportedModelArch()).isEqualTo("supportedModelArch");
+    assertThat(moduleProperties.getVersion()).isEqualTo(1234);
+  }
+
+  // Construct a dummuy {@code SoundTrigger.ModuleProperties}. Return Object because {@code
+  // SoundTrigger.ModuleProperties} is not exist in public Android SDK.
+  private Object getModuleProperties(String supportedModelArch, int version) {
+    return new SoundTrigger.ModuleProperties(
+        /* id= */ 0,
+        /* implementor= */ "implementor",
+        /* description= */ "description",
+        /* uuid= */ "11111111-1111-1111-1111-111111111111",
+        version,
+        supportedModelArch,
+        /* maxSoundModels= */ 0,
+        /* maxKeyphrases= */ 0,
+        /* maxUsers= */ 0,
+        /* recognitionModes= */ 0,
+        /* supportsCaptureTransition= */ false,
+        /* maxBufferMs= */ 0,
+        /* supportsConcurrentCapture= */ false,
+        /* powerConsumptionMw= */ 0,
+        /* returnsTriggerInEvent= */ false,
+        /* audioCapabilities= */ 0);
+  }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
index e3b48f0..a527ec9 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
@@ -10,6 +10,8 @@
 import static org.junit.Assert.assertThrows;
 import static org.robolectric.Shadows.shadowOf;
 
+import android.os.Handler;
+import android.os.Looper;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -90,6 +92,29 @@
   }
 
   @Test
+  public void
+      addOnSubscriptionsChangedListener_whenHasExecutorParameter_shouldCallbackImmediately() {
+    DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener();
+    shadowOf(subscriptionManager)
+        .addOnSubscriptionsChangedListener(new Handler(Looper.getMainLooper())::post, listener);
+
+    assertThat(listener.subscriptionChangedCount).isEqualTo(1);
+  }
+
+  @Test
+  public void addOnSubscriptionsChangedListener_whenHasExecutorParameter_shouldAddListener() {
+    DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener();
+    shadowOf(subscriptionManager)
+        .addOnSubscriptionsChangedListener(new Handler(Looper.getMainLooper())::post, listener);
+
+    shadowOf(subscriptionManager)
+        .setActiveSubscriptionInfos(
+            SubscriptionInfoBuilder.newBuilder().setId(123).buildSubscriptionInfo());
+
+    assertThat(listener.subscriptionChangedCount).isEqualTo(2);
+  }
+
+  @Test
   public void removeOnSubscriptionsChangedListener_shouldRemoveListener() {
     DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener();
     DummySubscriptionsChangedListener listener2 = new DummySubscriptionsChangedListener();
@@ -106,6 +131,21 @@
   }
 
   @Test
+  public void hasOnSubscriptionsChangedListener_whenListenerNotExist_shouldReturnFalse() {
+    DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener();
+
+    assertThat(shadowOf(subscriptionManager).hasOnSubscriptionsChangedListener(listener)).isFalse();
+  }
+
+  @Test
+  public void hasOnSubscriptionsChangedListener_whenListenerExist_shouldReturnTrue() {
+    DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener();
+    shadowOf(subscriptionManager).addOnSubscriptionsChangedListener(listener);
+
+    assertThat(shadowOf(subscriptionManager).hasOnSubscriptionsChangedListener(listener)).isTrue();
+  }
+
+  @Test
   public void getActiveSubscriptionInfo_shouldReturnInfoWithSubId() {
     SubscriptionInfo expectedSubscriptionInfo =
         SubscriptionInfoBuilder.newBuilder().setId(123).buildSubscriptionInfo();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
index f6b0995..ad3adeb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
@@ -27,6 +27,8 @@
 import static android.telephony.TelephonyManager.CALL_STATE_RINGING;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_EVDO_0;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE;
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 import static org.junit.Assert.assertEquals;
@@ -74,10 +76,12 @@
 import android.telephony.TelephonyManager.CellInfoCallback;
 import android.telephony.UiccSlotInfo;
 import android.telephony.VisualVoicemailSmsFilterSettings;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.gba.UaSecurityProtocolIdentifier;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -1101,4 +1105,36 @@
   public void getEmergencyCallback_notSet_returnsFalse() {
     assertThat(telephonyManager.getEmergencyCallbackMode()).isFalse();
   }
+
+  @Test
+  @Config(minSdk = R)
+  public void getEmergencyNumbersList_notSet_returnsEmptyList() {
+    assertThat(telephonyManager.getEmergencyNumberList()).isEmpty();
+  }
+
+  @Test
+  @Config(minSdk = R)
+  public void getEmergencyNumbersList_wasSet_returnsCorrectList() throws Exception {
+    EmergencyNumber emergencyNumber =
+        EmergencyNumber.class
+            .getConstructor(
+                String.class,
+                String.class,
+                String.class,
+                int.class,
+                List.class,
+                int.class,
+                int.class)
+            .newInstance(
+                "911",
+                "us",
+                "30",
+                EMERGENCY_NUMBER_SOURCE_DATABASE,
+                ImmutableList.of(),
+                EMERGENCY_SERVICE_CATEGORY_POLICE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+    ShadowTelephonyManager.setEmergencyNumberList(
+        ImmutableMap.of(0, ImmutableList.of(emergencyNumber)));
+    assertThat(telephonyManager.getEmergencyNumberList().get(0)).containsExactly(emergencyNumber);
+  }
 }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
index 7c9bfaf..9845a59 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
@@ -9,6 +9,7 @@
 import static android.os.Build.VERSION_CODES.N_MR1;
 import static android.os.Build.VERSION_CODES.Q;
 import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.S;
 import static android.os.Build.VERSION_CODES.TIRAMISU;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
@@ -196,13 +197,9 @@
     shadowOf(userManager)
         .addProfile(
             0, PROFILE_USER_HANDLE, PROFILE_USER_NAME, ShadowUserManager.FLAG_MANAGED_PROFILE);
-
     assertThat(userManager.isManagedProfile()).isFalse();
 
-    Application application = ApplicationProvider.getApplicationContext();
-    ShadowContextImpl shadowContext = Shadow.extract(application.getBaseContext());
-    shadowContext.setUserId(PROFILE_USER_HANDLE);
-
+    setUserIdInContext(PROFILE_USER_HANDLE);
     assertThat(userManager.isManagedProfile()).isTrue();
   }
 
@@ -216,6 +213,35 @@
   }
 
   @Test
+  @Config(minSdk = S)
+  public void isCloneProfile_withSetter() {
+    shadowOf(userManager).setCloneProfile(false);
+    assertThat(userManager.isCloneProfile()).isFalse();
+
+    shadowOf(userManager).setCloneProfile(true);
+    assertThat(userManager.isCloneProfile()).isTrue();
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void isCloneProfile_usesContextUser() {
+    assertThat(userManager.isCloneProfile()).isFalse();
+
+    UserInfo profileUserInfo =
+        new UserInfo(
+            PROFILE_USER_HANDLE,
+            PROFILE_USER_NAME,
+            /* iconPath= */ null,
+            /* profileFlags= */ 0,
+            UserManager.USER_TYPE_PROFILE_CLONE);
+    shadowOf(userManager).addProfile(0, PROFILE_USER_HANDLE, profileUserInfo);
+    assertThat(userManager.isCloneProfile()).isFalse();
+
+    setUserIdInContext(PROFILE_USER_HANDLE);
+    assertThat(userManager.isCloneProfile()).isTrue();
+  }
+
+  @Test
   @Config(minSdk = R)
   public void isProfile_fullUser_returnsFalse() {
     assertThat(userManager.isProfile()).isFalse();
@@ -230,10 +256,7 @@
         userManager.createProfile(PROFILE_USER_NAME, UserManager.USER_TYPE_PROFILE_MANAGED, null);
     assertThat(userManager.isProfile()).isFalse();
 
-    Application application = ApplicationProvider.getApplicationContext();
-    ShadowContextImpl shadowContext = Shadow.extract(application.getBaseContext());
-    shadowContext.setUserId(profileHandle.getIdentifier());
-
+    setUserIdInContext(profileHandle.getIdentifier());
     assertThat(userManager.isProfile()).isTrue();
   }
 
@@ -750,9 +773,7 @@
     shadowOf(userManager).setMaxSupportedUsers(2);
     userManager.createProfile(PROFILE_USER_NAME, UserManager.USER_TYPE_PROFILE_MANAGED, null);
 
-    Application application = ApplicationProvider.getApplicationContext();
-    ShadowContextImpl shadowContext = Shadow.extract(application.getBaseContext());
-    shadowContext.setUserId(ShadowUserManager.DEFAULT_SECONDARY_USER_ID);
+    setUserIdInContext(ShadowUserManager.DEFAULT_SECONDARY_USER_ID);
     assertThat(userManager.getUserName()).isEqualTo(PROFILE_USER_NAME);
   }
 
@@ -851,13 +872,11 @@
 
     userManager.setUserName("new user name");
 
-    Application application = ApplicationProvider.getApplicationContext();
-    ShadowContextImpl shadowContext = Shadow.extract(application.getBaseContext());
-    shadowContext.setUserId(PROFILE_USER_HANDLE);
+    setUserIdInContext(PROFILE_USER_HANDLE);
     userManager.setUserName("new profile name");
     assertThat(userManager.getUserName()).isEqualTo("new profile name");
 
-    shadowContext.setUserId(TEST_USER_HANDLE);
+    setUserIdInContext(TEST_USER_HANDLE);
     assertThat(userManager.getUserName()).isEqualTo("new user name");
   }
 
@@ -870,10 +889,7 @@
         userManager.createProfile(PROFILE_USER_NAME, UserManager.USER_TYPE_PROFILE_MANAGED, null);
     assertThat(userManager.isUserOfType(UserManager.USER_TYPE_PROFILE_MANAGED)).isFalse();
 
-    Application application = ApplicationProvider.getApplicationContext();
-    ShadowContextImpl shadowContext = Shadow.extract(application.getBaseContext());
-    shadowContext.setUserId(newUser.getIdentifier());
-
+    setUserIdInContext(newUser.getIdentifier());
     assertThat(userManager.isUserOfType(UserManager.USER_TYPE_PROFILE_MANAGED)).isTrue();
   }
 
@@ -1098,6 +1114,18 @@
     assertThat(receivedHandle.get()).isNull();
   }
 
+  @Test
+  @Config(minSdk = TIRAMISU)
+  public void someUserHasAccount() {
+    assertThat(userManager.someUserHasAccount(SEED_ACCOUNT_NAME, SEED_ACCOUNT_TYPE)).isFalse();
+
+    shadowOf(userManager).setSomeUserHasAccount(SEED_ACCOUNT_NAME, SEED_ACCOUNT_TYPE);
+    assertThat(userManager.someUserHasAccount(SEED_ACCOUNT_NAME, SEED_ACCOUNT_TYPE)).isTrue();
+
+    shadowOf(userManager).removeSomeUserHasAccount(SEED_ACCOUNT_NAME, SEED_ACCOUNT_TYPE);
+    assertThat(userManager.someUserHasAccount(SEED_ACCOUNT_NAME, SEED_ACCOUNT_TYPE)).isFalse();
+  }
+
   // Create user handle from parcel since UserHandle.of() was only added in later APIs.
   private static UserHandle newUserHandle(int uid) {
     Parcel userParcel = Parcel.obtain();
@@ -1106,6 +1134,12 @@
     return new UserHandle(userParcel);
   }
 
+  private static void setUserIdInContext(int userId) {
+    Application application = ApplicationProvider.getApplicationContext();
+    ShadowContextImpl shadowContext = Shadow.extract(application.getBaseContext());
+    shadowContext.setUserId(userId);
+  }
+
   private static void setPermissions(String... permissions) {
     Application context = ApplicationProvider.getApplicationContext();
     PackageInfo packageInfo =
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java
index b8b5785..ef5527a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java
@@ -16,7 +16,6 @@
 import android.media.AudioAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.os.vibrator.PrimitiveSegment;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import com.google.common.collect.ImmutableList;
@@ -132,7 +131,7 @@
 
   @Config(minSdk = S)
   @Test
-  public void getVibrationEffectSegments_composeOnce_shouldReturnSameFragment() {
+  public void getPrimitiveSegmentsInPrimitiveEffects_composeOnce_shouldReturnSameFragment() {
     vibrator.vibrate(
         VibrationEffect.startComposition()
             .addPrimitive(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20)
@@ -140,17 +139,17 @@
             .addPrimitive(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150)
             .compose());
 
-    assertThat(shadowOf(vibrator).getVibrationEffectSegments())
+    assertThat(shadowOf(vibrator).getPrimitiveSegmentsInPrimitiveEffects())
         .isEqualTo(
             ImmutableList.of(
-                new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20),
-                new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50),
-                new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150)));
+                new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20),
+                new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50),
+                new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150)));
   }
 
   @Config(minSdk = S)
   @Test
-  public void getVibrationEffectSegments_composeTwice_shouldReturnTheLastComposition() {
+  public void getPrimitiveSegmentsInPrimitiveEffects_composeTwice_shouldReturnTheLastComposition() {
     vibrator.vibrate(
         VibrationEffect.startComposition()
             .addPrimitive(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20)
@@ -164,12 +163,12 @@
             .addPrimitive(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150)
             .compose());
 
-    assertThat(shadowOf(vibrator).getVibrationEffectSegments())
+    assertThat(shadowOf(vibrator).getPrimitiveSegmentsInPrimitiveEffects())
         .isEqualTo(
             ImmutableList.of(
-                new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.4f, /* delay= */ 120),
-                new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150),
-                new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150)));
+                new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.4f, /* delay= */ 120),
+                new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150),
+                new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150)));
   }
 
   @Config(minSdk = R)
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
index 15dd9ec..fc5a5e5 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
@@ -33,6 +33,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.AddNetworkResult;
 import android.net.wifi.WifiManager.MulticastLock;
 import android.net.wifi.WifiManager.PnoScanResultsCallback;
 import android.net.wifi.WifiSsid;
@@ -51,6 +52,7 @@
 import org.mockito.ArgumentCaptor;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
 
 @RunWith(AndroidJUnit4.class)
 public class ShadowWifiManagerTest {
@@ -68,11 +70,19 @@
 
   @Test
   public void setWifiInfo_shouldUpdateWifiInfo() {
-    WifiInfo wifiInfo = new WifiInfo();
+    WifiInfo wifiInfo = newWifiInfo();
     shadowOf(wifiManager).setConnectionInfo(wifiInfo);
     assertThat(wifiManager.getConnectionInfo()).isSameInstanceAs(wifiInfo);
   }
 
+  private static WifiInfo newWifiInfo() {
+    if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) {
+      return new WifiInfo();
+    } else {
+      return ReflectionHelpers.callConstructor(WifiInfo.class);
+    }
+  }
+
   @Test
   public void setWifiEnabled_shouldThrowSecurityExceptionWhenAccessWifiStatePermissionNotGranted() {
     shadowOf(wifiManager).setAccessWifiStatePermission(false);
@@ -263,6 +273,87 @@
   }
 
   @Test
+  @Config(minSdk = S)
+  public void addNetworkPrivileged_nullConfig_shouldThrowIllegalArgumentException() {
+    assertThrows(IllegalArgumentException.class, () -> wifiManager.addNetworkPrivileged(null));
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void addNetworkPrivileged_nonNullConfig_shouldAddNetworkSuccessfully() {
+    WifiConfiguration wifiConfiguration = new WifiConfiguration();
+
+    AddNetworkResult addNetworkResult = wifiManager.addNetworkPrivileged(wifiConfiguration);
+
+    assertThat(addNetworkResult).isNotNull();
+    assertThat(addNetworkResult.statusCode).isEqualTo(AddNetworkResult.STATUS_SUCCESS);
+    assertThat(wifiManager.getConfiguredNetworks()).hasSize(1);
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      getCallerConfiguredNetworks_noAccessWifiStatePermission_shouldThrowSecurityException() {
+    shadowOf(wifiManager).setAccessWifiStatePermission(false);
+
+    assertThrows(SecurityException.class, () -> wifiManager.getCallerConfiguredNetworks());
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void getCallerConfiguredNetworks_noNetworksConfigured_returnsEmptyList() {
+    assertThat(wifiManager.getCallerConfiguredNetworks()).isEmpty();
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void getCallerConfiguredNetworks_networksAddedAndRemoved_returnsConfiguredNetworks() {
+    WifiConfiguration wifiConfiguration = new WifiConfiguration();
+    wifiManager.addNetwork(wifiConfiguration);
+
+    assertThat(wifiManager.getCallerConfiguredNetworks()).hasSize(1);
+
+    wifiManager.removeNetwork(0);
+
+    assertThat(wifiManager.getCallerConfiguredNetworks()).isEmpty();
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void
+      removeNonCallerConfiguredNetworks_noChangeWifiStatePermission_shouldThrowSecurityException() {
+    setDeviceOwner();
+    shadowOf(wifiManager).setChangeWifiStatePermission(false);
+
+    assertThrows(SecurityException.class, () -> wifiManager.removeNonCallerConfiguredNetworks());
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void removeNonCallerConfiguredNetworks_notDeviceOwner_shouldThrowSecurityException() {
+    assertThrows(SecurityException.class, () -> wifiManager.removeNonCallerConfiguredNetworks());
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void removeNonCallerConfiguredNetworks_noConfiguredNetworks_returnsFalse() {
+    setDeviceOwner();
+
+    assertThat(wifiManager.removeNonCallerConfiguredNetworks()).isFalse();
+  }
+
+  @Test
+  @Config(minSdk = S)
+  public void removeNonCallerConfiguredNetworks_hasConfiguredNetworks_removesConfiguredNetworks() {
+    setDeviceOwner();
+    wifiManager.addNetwork(new WifiConfiguration());
+    wifiManager.addNetwork(new WifiConfiguration());
+
+    assertThat(wifiManager.removeNonCallerConfiguredNetworks()).isTrue();
+    assertThat(wifiManager.getConfiguredNetworks()).isEmpty();
+  }
+
+  @Test
   @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
   public void getPrivilegedConfiguredNetworks_shouldReturnConfiguredNetworks() {
     WifiConfiguration wifiConfiguration = new WifiConfiguration();
diff --git a/robolectric/src/test/java/org/robolectric/util/SQLiteLibraryLoaderTest.java b/robolectric/src/test/java/org/robolectric/util/SQLiteLibraryLoaderTest.java
index 3b06a4e..612fd3b 100644
--- a/robolectric/src/test/java/org/robolectric/util/SQLiteLibraryLoaderTest.java
+++ b/robolectric/src/test/java/org/robolectric/util/SQLiteLibraryLoaderTest.java
@@ -1,8 +1,8 @@
 package org.robolectric.util;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeTrue;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import org.junit.After;
@@ -56,7 +56,7 @@
 
   @Test
   public void shouldExtractNativeLibrary() {
-    assumeTrue(SQLiteLibraryLoader.isOsSupported());
+    assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue();
     assertThat(loader.isLoaded()).isFalse();
     loader.doLoad();
     assertThat(loader.isLoaded()).isTrue();
diff --git a/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml b/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml
new file mode 100644
index 0000000..cbda17e
--- /dev/null
+++ b/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.robolectric">
+  <uses-sdk android:targetSdkVersion="18"/>
+
+  <application
+      android:appComponentFactory="org.robolectric.CustomAppComponentFactory">
+    <receiver
+        android:name=".CustomConstructorReceiverWrapper$CustomConstructorWithOneActionReceiver">
+      <intent-filter>
+        <action android:name="org.robolectric.ACTION_CUSTOM_CONSTRUCTOR"/>
+      </intent-filter>
+    </receiver>
+    <receiver
+      android:name=".CustomConstructorReceiverWrapper$CustomConstructorWithEmptyActionReceiver" />
+  </application>
+</manifest>
diff --git a/sandbox/build.gradle b/sandbox/build.gradle
index 64accd7..358b027 100644
--- a/sandbox/build.gradle
+++ b/sandbox/build.gradle
@@ -5,24 +5,24 @@
 apply plugin: DeployedRoboJavaModulePlugin
 
 dependencies {
-    annotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
-    annotationProcessor "com.google.errorprone:error_prone_core:$errorproneVersion"
+    annotationProcessor libs.auto.service
+    annotationProcessor libs.error.prone.core
 
     api project(":annotations")
     api project(":utils")
     api project(":shadowapi")
     api project(":utils:reflector")
-    compileOnly "com.google.auto.service:auto-service-annotations:$autoServiceVersion"
-    api "javax.annotation:javax.annotation-api:1.3.2"
-    api "javax.inject:javax.inject:1"
+    compileOnly libs.auto.service.annotations
+    api libs.javax.annotation.api
+    api libs.javax.inject
 
-    api "org.ow2.asm:asm:${asmVersion}"
-    api "org.ow2.asm:asm-commons:${asmVersion}"
-    api "com.google.guava:guava:$guavaJREVersion"
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+    api libs.asm
+    api libs.asm.commons
+    api libs.guava
+    compileOnly libs.findbugs.jsr305
 
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockito
     testImplementation project(":junit")
 }
diff --git a/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java b/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java
index ce81f2a..c03b882 100644
--- a/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java
+++ b/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java
@@ -98,6 +98,11 @@
       builder.addClassNameTranslation("sun.misc.Cleaner", "java.lang.ref.Cleaner$Cleanable");
     }
 
+    // Don't acquire legacy support packages.
+    builder
+        .doNotInstrumentPackage("android.support.constraint.")
+        .doNotInstrumentPackage("android.support.v7.view.");
+
     // Instrumenting these classes causes a weird failure.
     builder.doNotInstrumentClass("android.R").doNotInstrumentClass("android.R$styleable");
 
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
index e1463a1..53d4e57 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
@@ -379,6 +379,7 @@
    * @param method the constructor to instrument
    */
   protected void instrumentConstructor(MutableClass mutableClass, MethodNode method) {
+    int methodAccess = method.access;
     makeMethodPrivate(method);
 
     InsnList callSuper = extractCallToSuperConstructor(mutableClass, method);
@@ -388,8 +389,7 @@
 
     String[] exceptions = exceptionArray(method);
     MethodNode initMethodNode =
-        new MethodNode(method.access, "<init>", method.desc, method.signature, exceptions);
-    makeMethodPublic(initMethodNode);
+        new MethodNode(methodAccess, "<init>", method.desc, method.signature, exceptions);
     RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(initMethodNode);
     initMethodNode.instructions.add(callSuper);
     generator.loadThis();
@@ -682,12 +682,6 @@
         (clazz.access | Opcodes.ACC_PUBLIC) & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
   }
 
-  /** Replaces protected and private method modifiers with public. */
-  protected void makeMethodPublic(MethodNode method) {
-    method.access =
-        (method.access | Opcodes.ACC_PUBLIC) & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
-  }
-
   /** Replaces protected and public class modifiers with private. */
   protected void makeMethodPrivate(MethodNode method) {
     method.access =
diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java
index 016e883..5c9411c 100644
--- a/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java
+++ b/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java
@@ -103,7 +103,7 @@
   public void shouldDelegateToHandlerForConstructors() throws Exception {
     Class<?> clazz = loadClass(AClassWithNoDefaultConstructor.class);
     Constructor<?> ctor = clazz.getDeclaredConstructor(String.class);
-    assertTrue(Modifier.isPublic(ctor.getModifiers()));
+    assertThat(Modifier.isPublic(ctor.getModifiers())).isFalse();
     ctor.setAccessible(true);
     Object instance = ctor.newInstance("new one");
     assertThat(transcript)
diff --git a/settings.gradle b/settings.gradle
index 20f8fae..1894b8c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -16,6 +16,7 @@
 include ":shadows:httpclient"
 include ":shadows:multidex"
 include ":shadows:playservices"
+include ":shadows:versioning"
 include ":shadowapi"
 include ":errorprone"
 include ":nativeruntime"
diff --git a/shadowapi/build.gradle b/shadowapi/build.gradle
index f63d048..3f0064f 100644
--- a/shadowapi/build.gradle
+++ b/shadowapi/build.gradle
@@ -5,11 +5,11 @@
 apply plugin: DeployedRoboJavaModulePlugin
 
 dependencies {
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+    compileOnly libs.findbugs.jsr305
 
     api project(":annotations")
     api project(":utils")
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
-}
\ No newline at end of file
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockito
+}
diff --git a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java
index eaaee1a..8ae6399 100644
--- a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java
+++ b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java
@@ -10,7 +10,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import javax.annotation.Nullable;
 
 /** Collection of helper methods for calling methods and accessing fields reflectively. */
 @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals", "NewApi"})
@@ -45,9 +44,10 @@
    * <p>The returned object will be an instance of the given class, but all methods will return
    * either the "default" value for primitives, or another deep proxy for non-primitive types.
    *
-   * <p>This should be used rarely, for cases where we need to create deep proxies in order not
-   * to crash. The inner proxies are impossible to configure, so there is no way to create
-   * meaningful behavior from a deep proxy. It serves mainly to prevent Null Pointer Exceptions.
+   * <p>This should be used rarely, for cases where we need to create deep proxies in order not to
+   * crash. The inner proxies are impossible to configure, so there is no way to create meaningful
+   * behavior from a deep proxy. It serves mainly to prevent Null Pointer Exceptions.
+   *
    * @param clazz the class to provide a proxy instance of.
    * @return a new "Deep Proxy" instance of the given class.
    */
@@ -127,7 +127,8 @@
    * @param fieldName The field name.
    * @param fieldNewValue New value.
    */
-  public static void setField(final Object object, final String fieldName, final Object fieldNewValue) {
+  public static void setField(
+      final Object object, final String fieldName, final Object fieldNewValue) {
     try {
       traverseClassHierarchy(
           object.getClass(),
@@ -152,7 +153,8 @@
    * @param fieldName The field name.
    * @param fieldNewValue New value.
    */
-  public static void setField(Class<?> type, final Object object, final String fieldName, final Object fieldNewValue) {
+  public static void setField(
+      Class<?> type, final Object object, final String fieldName, final Object fieldNewValue) {
     try {
       Field field = type.getDeclaredField(fieldName);
       field.setAccessible(true);
@@ -163,6 +165,22 @@
   }
 
   /**
+   * Reflectively check if a class has a given field (static or non static).
+   *
+   * @param clazz Target class.
+   * @param fieldName The field name.
+   * @return boolean to indicate whether the field exists or not in clazz.
+   */
+  public static boolean hasField(Class<?> clazz, String fieldName) {
+    try {
+      Field field = clazz.getDeclaredField(fieldName);
+      return (field != null);
+    } catch (NoSuchFieldException e) {
+      return false;
+    }
+  }
+
+  /**
    * Reflectively get the value of a static field.
    *
    * @param field Field object.
@@ -392,7 +410,9 @@
   public static <T> T newInstance(Class<T> cl) {
     try {
       return cl.getDeclaredConstructor().newInstance();
-    } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
+    } catch (InstantiationException
+        | IllegalAccessException
+        | NoSuchMethodException
         | InvocationTargetException e) {
       throw new RuntimeException(e);
     }
@@ -465,15 +485,15 @@
    */
   public static class ClassParameter<V> {
     public final Class<? extends V> clazz;
-    public final V val;
+    public final V value;
 
-    public ClassParameter(Class<? extends V> clazz, V val) {
+    public ClassParameter(Class<? extends V> clazz, V value) {
       this.clazz = clazz;
-      this.val = val;
+      this.value = value;
     }
 
-    public static <V> ClassParameter<V> from(Class<? extends V> clazz, V val) {
-      return new ClassParameter<>(clazz, val);
+    public static <V> ClassParameter<V> from(Class<? extends V> clazz, V value) {
+      return new ClassParameter<>(clazz, value);
     }
 
     public static ClassParameter<?>[] fromComponentLists(Class<?>[] classes, Object[] values) {
@@ -496,7 +516,7 @@
     public static Object[] getValues(ClassParameter<?>... classParameters) {
       Object[] values = new Object[classParameters.length];
       for (int i = 0; i < classParameters.length; i++) {
-        Object paramValue = classParameters[i].val;
+        Object paramValue = classParameters[i].value;
         values[i] = paramValue;
       }
       return values;
@@ -510,15 +530,15 @@
    */
   public static class StringParameter<V> {
     public final String className;
-    public final V val;
+    public final V value;
 
-    public StringParameter(String className, V val) {
+    public StringParameter(String className, V value) {
       this.className = className;
-      this.val = val;
+      this.value = value;
     }
 
-    public static <V> StringParameter<V> from(String className, V val) {
-      return new StringParameter<>(className, val);
+    public static <V> StringParameter<V> from(String className, V value) {
+      return new StringParameter<>(className, value);
     }
   }
 }
diff --git a/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java b/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java
index 56c489d..e5f281b 100644
--- a/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java
+++ b/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java
@@ -141,7 +141,8 @@
   }
 
   @Test
-  public void callInstanceMethodReflectively_whenMultipleSignaturesExistForAMethodName_callsMethodWithCorrectSignature() {
+  public void
+      callInstanceMethodReflectively_whenMultipleSignaturesExistForAMethodName_callsMethodWithCorrectSignature() {
     ExampleDescendant example = new ExampleDescendant();
     int returnNumber =
         ReflectionHelpers.callInstanceMethod(
@@ -282,23 +283,35 @@
   }
 
   @Test
-  public void callConstructorReflectively_whenMultipleSignaturesExistForTheConstructor_callsConstructorWithCorrectSignature() {
-    ExampleClass ec = ReflectionHelpers.callConstructor(ExampleClass.class, ClassParameter.from(int.class, 16));
+  public void
+      callConstructorReflectively_whenMultipleSignaturesExistForTheConstructor_callsConstructorWithCorrectSignature() {
+    ExampleClass ec =
+        ReflectionHelpers.callConstructor(ExampleClass.class, ClassParameter.from(int.class, 16));
     assertWithMessage("index").that(ec.index).isEqualTo(16);
     assertWithMessage("name").that(ec.name).isNull();
   }
 
-  @SuppressWarnings("serial")
-  private static class TestError extends Error {
+  @Test
+  public void callHasField_withstaticandregularmember() {
+    assertWithMessage("has field failed for member: unusedName")
+        .that(ReflectionHelpers.hasField(FieldTestClass.class, "unusedName"))
+        .isTrue();
+    assertWithMessage("has field failed for member: unusedStaticName")
+        .that(ReflectionHelpers.hasField(FieldTestClass.class, "unusedStaticName"))
+        .isTrue();
+    assertWithMessage("has field failed for non existant member: noname")
+        .that(ReflectionHelpers.hasField(FieldTestClass.class, "noname"))
+        .isFalse();
   }
 
   @SuppressWarnings("serial")
-  private static class TestException extends Exception {
-  }
+  private static class TestError extends Error {}
 
   @SuppressWarnings("serial")
-  private static class TestRuntimeException extends RuntimeException {
-  }
+  private static class TestException extends Exception {}
+
+  @SuppressWarnings("serial")
+  private static class TestRuntimeException extends RuntimeException {}
 
   @SuppressWarnings("unused")
   private static class ExampleBase {
@@ -406,4 +419,11 @@
       this.index = index;
     }
   }
+
+  private static class FieldTestClass {
+    public String unusedName;
+    public static String unusedStaticName = "unusedStaticNameValue";
+
+    private FieldTestClass() {}
+  }
 }
diff --git a/shadows/framework/build.gradle b/shadows/framework/build.gradle
index a273d5a..a2230b0 100644
--- a/shadows/framework/build.gradle
+++ b/shadows/framework/build.gradle
@@ -15,6 +15,8 @@
     sqlite4java
 }
 
+def sqlite4javaVersion = libs.versions.sqlite4java.get()
+
 task copySqliteNatives(type: Copy) {
     from project.configurations.sqlite4java {
         include '**/*.dll'
@@ -45,22 +47,20 @@
     api project(":pluginapi")
     api project(":sandbox")
     api project(":shadowapi")
+    api project(":shadows:versioning")
     api project(":utils")
     api project(":utils:reflector")
+
     api "androidx.test:monitor:$axtMonitorVersion@aar"
 
-    implementation "com.google.errorprone:error_prone_annotations:$errorproneVersion"
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
-    api "com.almworks.sqlite4java:sqlite4java:$sqlite4javaVersion"
-    compileOnly(AndroidSdk.MAX_SDK.coordinates) { force = true }
-    api "com.ibm.icu:icu4j:72.1"
-    api "androidx.annotation:annotation:1.1.0"
-    api "com.google.auto.value:auto-value-annotations:1.10.1"
-    annotationProcessor "com.google.auto.value:auto-value:1.10.1"
+    implementation libs.error.prone.annotations
+    compileOnly libs.findbugs.jsr305
+    api libs.sqlite4java
+    compileOnly(AndroidSdk.MAX_SDK.coordinates)
+    api libs.icu4j
+    api libs.androidx.annotation
+    api libs.auto.value.annotations
+    annotationProcessor libs.auto.value
 
-    sqlite4java "com.almworks.sqlite4java:libsqlite4java-osx:$sqlite4javaVersion"
-    sqlite4java "com.almworks.sqlite4java:libsqlite4java-linux-amd64:$sqlite4javaVersion"
-    sqlite4java "com.almworks.sqlite4java:sqlite4java-win32-x64:$sqlite4javaVersion"
-    sqlite4java "com.almworks.sqlite4java:libsqlite4java-linux-i386:$sqlite4javaVersion"
-    sqlite4java "com.almworks.sqlite4java:sqlite4java-win32-x86:$sqlite4javaVersion"
+    sqlite4java libs.bundles.sqlite4java.native
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java
old mode 100755
new mode 100644
index 33276d9..0385636
--- a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java
+++ b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java
@@ -1,5 +1,6 @@
 package org.robolectric;
 
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.KITKAT;
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
 import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
@@ -13,11 +14,14 @@
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.util.DisplayMetrics;
+import android.view.Display;
 import com.google.common.base.Supplier;
 import java.nio.file.Path;
 import org.robolectric.android.Bootstrap;
 import org.robolectric.android.ConfigurationV25;
 import org.robolectric.res.ResourceTable;
+import org.robolectric.shadows.ShadowDisplayManager;
+import org.robolectric.shadows.ShadowView;
 import org.robolectric.util.Scheduler;
 import org.robolectric.util.TempDirectory;
 
@@ -193,6 +197,10 @@
    * @param newQualifiers the qualifiers to apply
    */
   public static void setQualifiers(String newQualifiers) {
+    if (getApiLevel() >= JELLY_BEAN_MR1) {
+      ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, newQualifiers);
+    }
+
     Configuration configuration;
     DisplayMetrics displayMetrics = new DisplayMetrics();
 
@@ -203,10 +211,31 @@
       configuration = new Configuration();
     }
     Bootstrap.applyQualifiers(newQualifiers, getApiLevel(), configuration, displayMetrics);
-    if (Boolean.getBoolean("robolectric.nativeruntime.enableGraphics")) {
+    if (ShadowView.useRealGraphics()) {
       Bitmap.setDefaultDensity(displayMetrics.densityDpi);
     }
 
+    updateConfiguration(configuration, displayMetrics);
+  }
+
+  public static void setFontScale(float fontScale) {
+    Resources systemResources = getApplication().getResources();
+    DisplayMetrics displayMetrics = systemResources.getDisplayMetrics();
+    Configuration configuration = systemResources.getConfiguration();
+
+    displayMetrics.scaledDensity = displayMetrics.density * fontScale;
+    configuration.fontScale = fontScale;
+
+    updateConfiguration(configuration, displayMetrics);
+  }
+
+  public static float getFontScale() {
+    Resources systemResources = getApplication().getResources();
+    return systemResources.getConfiguration().fontScale;
+  }
+
+  private static void updateConfiguration(
+      Configuration configuration, DisplayMetrics displayMetrics) {
     // Update the resources last so that listeners will have a consistent environment.
     // TODO(paulsowden): Can we call ResourcesManager.getInstance().applyConfigurationToResources()?
     if (Build.VERSION.SDK_INT >= KITKAT
@@ -223,7 +252,6 @@
     }
   }
 
-
   public static int getApiLevel() {
     return apiLevel;
   }
diff --git a/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java b/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java
index b349ed5..1ed8d21 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java
@@ -8,6 +8,7 @@
 import android.util.DisplayMetrics;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.res.Qualifiers;
+import org.robolectric.shadows.ShadowDateUtils;
 import org.robolectric.shadows.ShadowDisplayManager;
 import org.robolectric.shadows.ShadowWindowManagerImpl;
 
@@ -101,6 +102,9 @@
     DeviceConfig.applyRules(configuration, displayMetrics, apiLevel);
 
     fixJellyBean(configuration, displayMetrics);
+
+    // DateUtils has a static cache of the last Configuration, so it may need to be reset.
+    ShadowDateUtils.resetLastConfig();
   }
 
   private static void fixJellyBean(Configuration configuration, DisplayMetrics displayMetrics) {
diff --git a/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java b/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java
index 74d5b3f..56b0f4c 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java
@@ -67,6 +67,9 @@
     DESTROYED
   }
 
+  // ActivityInfo constant.
+  private static final int CONFIG_WINDOW_CONFIGURATION = 0x20000000;
+
   private _Activity_ _component_;
   private LifecycleState currentState = LifecycleState.INITIAL;
 
@@ -428,11 +431,12 @@
       Configuration newConfiguration, DisplayMetrics newMetrics, @Config int changedConfig) {
     component.getResources().updateConfiguration(newConfiguration, newMetrics);
 
+    int filteredChanges = filterConfigChanges(changedConfig);
     // TODO: throw on changedConfig == 0 since it non-intuitively calls onConfigurationChanged
 
     // Can the activity handle itself ALL configuration changes?
-    if ((getActivityInfo(component.getApplication()).configChanges & changedConfig)
-        == changedConfig) {
+    if ((getActivityInfo(component.getApplication()).configChanges & filteredChanges)
+        == filteredChanges) {
       shadowMainLooper.runPaused(
           () -> {
             component.onConfigurationChanged(newConfiguration);
@@ -460,7 +464,7 @@
           () -> {
             // Set flags
             _component_.setChangingConfigurations(true);
-            _component_.setConfigChangeFlags(changedConfig);
+            _component_.setConfigChangeFlags(filteredChanges);
 
             // Perform activity destruction
             final Bundle outState = new Bundle();
@@ -690,6 +694,16 @@
     destroy();
   }
 
+  // See ActivityRecord#getConfigurationChanges for the config changes that are considered for
+  // activity recreation by the window manager.
+  private static int filterConfigChanges(int changedConfig) {
+    // We don't want window configuration to cause relaunches.
+    if ((changedConfig & CONFIG_WINDOW_CONFIGURATION) != 0) {
+      changedConfig &= ~CONFIG_WINDOW_CONFIGURATION;
+    }
+    return changedConfig;
+  }
+
   /** Accessor interface for android.app.Activity.NonConfigurationInstances's internals. */
   @ForType(className = "android.app.Activity$NonConfigurationInstances")
   interface _NonConfigurationInstances_ {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java
index 73c36a5..e2b8f0d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java
@@ -80,19 +80,40 @@
   public AssociationInfo build() {
     try {
       if (RuntimeEnvironment.getApiLevel() <= TIRAMISU) {
-        return ReflectionHelpers.callConstructor(
-            AssociationInfo.class,
-            ClassParameter.from(int.class, id),
-            ClassParameter.from(int.class, userId),
-            ClassParameter.from(String.class, packageName),
-            ClassParameter.from(MacAddress.class, MacAddress.fromString(deviceMacAddress)),
-            ClassParameter.from(CharSequence.class, displayName),
-            ClassParameter.from(String.class, deviceProfile),
-            ClassParameter.from(boolean.class, selfManaged),
-            ClassParameter.from(boolean.class, notifyOnDeviceNearby),
-            ClassParameter.from(boolean.class, false /*revoked*/),
-            ClassParameter.from(long.class, approvedMs),
-            ClassParameter.from(long.class, lastTimeConnectedMs));
+        // We have two different constructors for AssociationInfo across
+        // T branches. aosp has the constructor that takes a new "revoked" parameter.
+        // Since there is not deterministic way to know which branch we are running in,
+        // we will reflect on the class to see if it has the mRevoked member.
+        // Based on the result we will either invoke the constructor with "revoked" or the
+        // one without this parameter.
+        if (ReflectionHelpers.hasField(AssociationInfo.class, "mRevoked")) {
+          return ReflectionHelpers.callConstructor(
+              AssociationInfo.class,
+              ClassParameter.from(int.class, id),
+              ClassParameter.from(int.class, userId),
+              ClassParameter.from(String.class, packageName),
+              ClassParameter.from(MacAddress.class, MacAddress.fromString(deviceMacAddress)),
+              ClassParameter.from(CharSequence.class, displayName),
+              ClassParameter.from(String.class, deviceProfile),
+              ClassParameter.from(boolean.class, selfManaged),
+              ClassParameter.from(boolean.class, notifyOnDeviceNearby),
+              ClassParameter.from(boolean.class, false /*revoked only supported in aosp*/),
+              ClassParameter.from(long.class, approvedMs),
+              ClassParameter.from(long.class, lastTimeConnectedMs));
+        } else {
+          return ReflectionHelpers.callConstructor(
+              AssociationInfo.class,
+              ClassParameter.from(int.class, id),
+              ClassParameter.from(int.class, userId),
+              ClassParameter.from(String.class, packageName),
+              ClassParameter.from(MacAddress.class, MacAddress.fromString(deviceMacAddress)),
+              ClassParameter.from(CharSequence.class, displayName),
+              ClassParameter.from(String.class, deviceProfile),
+              ClassParameter.from(boolean.class, selfManaged),
+              ClassParameter.from(boolean.class, notifyOnDeviceNearby),
+              ClassParameter.from(long.class, approvedMs),
+              ClassParameter.from(long.class, lastTimeConnectedMs));
+        }
       } else {
         return ReflectionHelpers.callConstructor(
             AssociationInfo.class,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/BluetoothConnectionManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/BluetoothConnectionManager.java
index 70e54b1..d445460 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/BluetoothConnectionManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/BluetoothConnectionManager.java
@@ -136,4 +136,4 @@
   void resetConnections() {
     this.remoteAddressConnectionMap.clear();
   }
-}
\ No newline at end of file
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java
new file mode 100644
index 0000000..22a0e75
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java
@@ -0,0 +1,135 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.os.Build;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfo;
+import androidx.annotation.RequiresApi;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.reflector.Constructor;
+import org.robolectric.util.reflector.ForType;
+
+/** Builder for {@link android.telephony.CellIdentityNr}. */
+@RequiresApi(Build.VERSION_CODES.Q)
+public class CellIdentityNrBuilder {
+
+  private int pci = CellInfo.UNAVAILABLE;
+  private int tac = CellInfo.UNAVAILABLE;
+  private int nrarfcn = CellInfo.UNAVAILABLE;
+  private int[] bands = new int[0];
+  @Nullable private String mcc = null;
+  @Nullable private String mnc = null;
+  private long nci = CellInfo.UNAVAILABLE;
+  @Nullable private String alphal = null;
+  @Nullable private String alphas = null;
+  private List<String> additionalPlmns = new ArrayList<>();
+
+  private CellIdentityNrBuilder() {}
+
+  public static CellIdentityNrBuilder newBuilder() {
+    return new CellIdentityNrBuilder();
+  }
+
+  // An empty constructor is not available on Q.
+  @RequiresApi(Build.VERSION_CODES.R)
+  protected static CellIdentityNr getDefaultInstance() {
+    return reflector(CellIdentityNrReflector.class).newCellIdentityNr();
+  }
+
+  public CellIdentityNrBuilder setNci(long nci) {
+    this.nci = nci;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setPci(int pci) {
+    this.pci = pci;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setTac(int tac) {
+    this.tac = tac;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setNrarfcn(int nrarfcn) {
+    this.nrarfcn = nrarfcn;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setMcc(String mcc) {
+    this.mcc = mcc;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setMnc(String mnc) {
+    this.mnc = mnc;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setBands(int[] bands) {
+    this.bands = bands;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setLongOperatorName(String longOperatorName) {
+    this.alphal = longOperatorName;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setShortOperatorName(String shortOperatorName) {
+    this.alphas = shortOperatorName;
+    return this;
+  }
+
+  public CellIdentityNrBuilder setAdditionalPlmns(List<String> additionalPlmns) {
+    this.additionalPlmns = additionalPlmns;
+    return this;
+  }
+
+  public CellIdentityNr build() {
+    CellIdentityNrReflector cellIdentityReflector = reflector(CellIdentityNrReflector.class);
+    if (RuntimeEnvironment.getApiLevel() < Build.VERSION_CODES.R) {
+      return cellIdentityReflector.newCellIdentityNr(
+          pci, tac, nrarfcn, mcc, mnc, nci, alphal, alphas);
+    } else {
+      return cellIdentityReflector.newCellIdentityNr(
+          pci, tac, nrarfcn, bands, mcc, mnc, nci, alphal, alphas, additionalPlmns);
+    }
+  }
+
+  @ForType(CellIdentityNr.class)
+  private interface CellIdentityNrReflector {
+
+    @Constructor
+    CellIdentityNr newCellIdentityNr();
+
+    @Constructor
+    CellIdentityNr newCellIdentityNr(
+        int pci,
+        int tac,
+        int nrarfcn,
+        String mcc,
+        String mnc,
+        long nci,
+        String alphal,
+        String alphas);
+
+    @Constructor
+    CellIdentityNr newCellIdentityNr(
+        int pci,
+        int tac,
+        int nrarfcn,
+        int[] bands,
+        String mcc,
+        String mnc,
+        long nci,
+        String alphal,
+        String alphas,
+        Collection<String> additionalPlmns);
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java
index 412d8c7..6f3f934 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java
@@ -57,13 +57,17 @@
   }
 
   public CellInfoLte build() {
+    int apiLevel = RuntimeEnvironment.getApiLevel();
     if (cellIdentity == null) {
-      cellIdentity = CellIdentityLteBuilder.getDefaultInstance();
+      if (apiLevel > Build.VERSION_CODES.Q) {
+        cellIdentity = CellIdentityLteBuilder.getDefaultInstance();
+      } else {
+        cellIdentity = CellIdentityLteBuilder.newBuilder().build();
+      }
     }
     if (cellSignalStrength == null) {
       cellSignalStrength = CellSignalStrengthLteBuilder.getDefaultInstance();
     }
-    int apiLevel = RuntimeEnvironment.getApiLevel();
     CellInfoLteReflector cellInfoLteReflector = reflector(CellInfoLteReflector.class);
     if (apiLevel < Build.VERSION_CODES.TIRAMISU) {
       CellInfoLte cellInfo = cellInfoLteReflector.newCellInfoLte();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java
new file mode 100644
index 0000000..7819524
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java
@@ -0,0 +1,93 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.os.Build;
+import android.os.Parcel;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfoNr;
+import android.telephony.CellSignalStrengthNr;
+import androidx.annotation.RequiresApi;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.reflector.Constructor;
+import org.robolectric.util.reflector.ForType;
+
+/** Builder for {@link android.telephony.CellInfoNr}. */
+@RequiresApi(Build.VERSION_CODES.Q)
+public class CellInfoNrBuilder {
+
+  private boolean isRegistered = false;
+  private long timeStamp = 0L;
+  private int cellConnectionStatus = 0;
+  private CellIdentityNr cellIdentity;
+  private CellSignalStrengthNr cellSignalStrength;
+
+  private CellInfoNrBuilder() {}
+
+  public static CellInfoNrBuilder newBuilder() {
+    return new CellInfoNrBuilder();
+  }
+
+  public CellInfoNrBuilder setRegistered(boolean isRegistered) {
+    this.isRegistered = isRegistered;
+    return this;
+  }
+
+  public CellInfoNrBuilder setTimeStampNanos(long timeStamp) {
+    this.timeStamp = timeStamp;
+    return this;
+  }
+
+  public CellInfoNrBuilder setCellConnectionStatus(int cellConnectionStatus) {
+    this.cellConnectionStatus = cellConnectionStatus;
+    return this;
+  }
+
+  public CellInfoNrBuilder setCellIdentity(CellIdentityNr cellIdentity) {
+    this.cellIdentity = cellIdentity;
+    return this;
+  }
+
+  public CellInfoNrBuilder setCellSignalStrength(CellSignalStrengthNr cellSignalStrength) {
+    this.cellSignalStrength = cellSignalStrength;
+    return this;
+  }
+
+  public CellInfoNr build() {
+    if (cellIdentity == null) {
+      cellIdentity = CellIdentityNrBuilder.getDefaultInstance();
+    }
+    if (cellSignalStrength == null) {
+      cellSignalStrength = CellSignalStrengthNrBuilder.getDefaultInstance();
+    }
+    // CellInfoNr has no default constructor below T so we write it to a Parcel.
+    if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.TIRAMISU) {
+      Parcel p = Parcel.obtain();
+      p.writeInt(/* CellInfo#TYPE_NR */ 6);
+      p.writeInt(isRegistered ? 1 : 0);
+      p.writeLong(timeStamp);
+      p.writeInt(cellConnectionStatus);
+      cellIdentity.writeToParcel(p, 0);
+      cellSignalStrength.writeToParcel(p, 0);
+      p.setDataPosition(0);
+      CellInfoNr cellInfoNr = CellInfoNr.CREATOR.createFromParcel(p);
+      p.recycle();
+      return cellInfoNr;
+    } else {
+      return reflector(CellInfoNrReflector.class)
+          .newCellInfoNr(
+              cellConnectionStatus, isRegistered, timeStamp, cellIdentity, cellSignalStrength);
+    }
+  }
+
+  @ForType(CellInfoNr.class)
+  private interface CellInfoNrReflector {
+    @Constructor
+    CellInfoNr newCellInfoNr(
+        int cellConnectionStatus,
+        boolean isRegistered,
+        long timeStamp,
+        CellIdentityNr cellIdentity,
+        CellSignalStrengthNr cellSignalStrength);
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java
new file mode 100644
index 0000000..4f3f859
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java
@@ -0,0 +1,140 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.os.Build;
+import android.telephony.CellInfo;
+import android.telephony.CellSignalStrengthNr;
+import androidx.annotation.RequiresApi;
+import java.util.ArrayList;
+import java.util.List;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.reflector.Constructor;
+import org.robolectric.util.reflector.ForType;
+
+/** Builder for {@link android.telephony.CellSignalStrengthNr} */
+@RequiresApi(Build.VERSION_CODES.Q)
+public class CellSignalStrengthNrBuilder {
+
+  private int csiRrsp = CellInfo.UNAVAILABLE;
+  private int csiRsrq = CellInfo.UNAVAILABLE;
+  private int csiSinr = CellInfo.UNAVAILABLE;
+  private int csiCqiTableIndex = CellInfo.UNAVAILABLE;
+  private List<Byte> csiCqiReport = new ArrayList<>();
+  private int ssRsrp = CellInfo.UNAVAILABLE;
+  private int ssRsrq = CellInfo.UNAVAILABLE;
+  private int ssSinr = CellInfo.UNAVAILABLE;
+  private int timingAdvance = CellInfo.UNAVAILABLE;
+
+  private CellSignalStrengthNrBuilder() {}
+
+  public static CellSignalStrengthNrBuilder newBuilder() {
+    return new CellSignalStrengthNrBuilder();
+  }
+
+  protected static CellSignalStrengthNr getDefaultInstance() {
+    return reflector(CellSignalStrengthNrReflector.class).newCellSignalStrengthNr();
+  }
+
+  public CellSignalStrengthNrBuilder setCsiRsrp(int csiRrsp) {
+    this.csiRrsp = csiRrsp;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setCsiRsrq(int csiRsrq) {
+    this.csiRsrq = csiRsrq;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setCsiSinr(int csiSinr) {
+    this.csiSinr = csiSinr;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setCsiCqiTableIndex(int csiCqiTableIndex) {
+    this.csiCqiTableIndex = csiCqiTableIndex;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setCsiCqiReport(List<Byte> csiCqiReport) {
+    this.csiCqiReport = csiCqiReport;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setSsRsrp(int ssRsrp) {
+    this.ssRsrp = ssRsrp;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setSsRsrq(int ssRsrq) {
+    this.ssRsrq = ssRsrq;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setSsSinr(int ssSinr) {
+    this.ssSinr = ssSinr;
+    return this;
+  }
+
+  public CellSignalStrengthNrBuilder setTimingAdvance(int timingAdvance) {
+    this.timingAdvance = timingAdvance;
+    return this;
+  }
+
+  public CellSignalStrengthNr build() {
+    CellSignalStrengthNrReflector cellSignalStrengthReflector =
+        reflector(CellSignalStrengthNrReflector.class);
+    if (RuntimeEnvironment.getApiLevel() < Build.VERSION_CODES.TIRAMISU) {
+      return cellSignalStrengthReflector.newCellSignalStrengthNr(
+          csiRrsp, csiRsrq, csiSinr, ssRsrp, ssRsrq, ssSinr);
+    } else if (RuntimeEnvironment.getApiLevel() == Build.VERSION_CODES.TIRAMISU) {
+      return cellSignalStrengthReflector.newCellSignalStrengthNr(
+          csiRrsp, csiRsrq, csiSinr, csiCqiTableIndex, csiCqiReport, ssRsrp, ssRsrq, ssSinr);
+    } else {
+      return cellSignalStrengthReflector.newCellSignalStrengthNr(
+          csiRrsp,
+          csiRsrq,
+          csiSinr,
+          csiCqiTableIndex,
+          csiCqiReport,
+          ssRsrp,
+          ssRsrq,
+          ssSinr,
+          timingAdvance);
+    }
+  }
+
+  @ForType(CellSignalStrengthNr.class)
+  private interface CellSignalStrengthNrReflector {
+
+    @Constructor
+    CellSignalStrengthNr newCellSignalStrengthNr();
+
+    @Constructor
+    CellSignalStrengthNr newCellSignalStrengthNr(
+        int csRsrp, int csiRsrq, int csiSinr, int ssRsrp, int ssRsrq, int ssSinr);
+
+    @Constructor
+    CellSignalStrengthNr newCellSignalStrengthNr(
+        int csRsrp,
+        int csiRsrq,
+        int csiSinr,
+        int csiCqiTableIndex,
+        List<Byte> csiCqiReport,
+        int ssRsrp,
+        int ssRsrq,
+        int ssSinr);
+
+    @Constructor
+    CellSignalStrengthNr newCellSignalStrengthNr(
+        int csRsrp,
+        int csiRsrq,
+        int csiSinr,
+        int csiCqiTableIndex,
+        List<Byte> csiCqiReport,
+        int ssRsrp,
+        int ssRsrq,
+        int ssSinr,
+        int timingAdvance);
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java
index d23045b..4af12cd 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java
@@ -23,11 +23,12 @@
     this.binary14ShadowClass = binary9ShadowClass;
   }
 
-  public ResourceModeShadowPicker(Class<? extends T> legacyShadowClass,
-          Class<? extends T> binaryShadowClass,
-          Class<? extends T> binary9ShadowClass,
-          Class<? extends T> binary10ShadowClass,
-          Class<? extends T> binary14ShadowClass) {
+  public ResourceModeShadowPicker(
+      Class<? extends T> legacyShadowClass,
+      Class<? extends T> binaryShadowClass,
+      Class<? extends T> binary9ShadowClass,
+      Class<? extends T> binary10ShadowClass,
+      Class<? extends T> binary14ShadowClass) {
     this.legacyShadowClass = legacyShadowClass;
     this.binaryShadowClass = binaryShadowClass;
     this.binary9ShadowClass = binary9ShadowClass;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbsSpinner.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbsSpinner.java
index f0c5b6f..e1cc9f9 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbsSpinner.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbsSpinner.java
@@ -25,12 +25,38 @@
   @Implementation
   protected void setSelection(int position) {
     reflector(AbsSpinnerReflector.class, realAbsSpinner).setSelection(position);
-    SpinnerAdapter adapter = realAbsSpinner.getAdapter();
-    if (getItemSelectedListener() != null && adapter != null) {
-      getItemSelectedListener().onItemSelected(realAbsSpinner, null, position, adapter.getItemId(position));
+    if (!useRealSpinnerSelection()) {
+      // Use the broken legacy spinner selection callback invocation logic.
+      SpinnerAdapter adapter = realAbsSpinner.getAdapter();
+      if (getItemSelectedListener() != null && adapter != null) {
+        getItemSelectedListener()
+            .onItemSelected(realAbsSpinner, null, position, adapter.getItemId(position));
+      }
     }
   }
 
+  /**
+   * Currently the shadow method {@link #setSelection(int)} broken and low-fidelity. In real
+   * Android, the item selected callback is invoked during layout passes. However, in Robolectric,
+   * the callback is always invoked explicitly, which leads to many issues:
+   *
+   * <ol>
+   *   <li>Item selection callbacks are invoked even if the selection hasn't changed, which can lead
+   *       to infinite loops.
+   *   <li>Item selection callbacks are still invoked if spinners are not attached to an Activity.
+   *   <li>Item selection callbacks are invoked even if spinners are non-visible.
+   *   <li>Item selection callbacks are invoked twice if spinners are attached to an Activity and
+   *       visible.
+   * </ol>
+   *
+   * <p>Long-term we want to eliminate this broken shadow method, but in the mean time the real
+   * scrolling behavior is only enabled if a system property is set, due to tests depending on the
+   * broken behavior.
+   */
+  private static boolean useRealSpinnerSelection() {
+    return Boolean.getBoolean("robolectric.useRealSpinnerSelection");
+  }
+
   // Non-implementation helper method
   public boolean isAnimatedTransition() {
     return animatedTransition;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityService.java
index e5620df..9e51896 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityService.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityService.java
@@ -16,11 +16,15 @@
 import android.hardware.HardwareBuffer;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.util.SparseArray;
+import android.view.Display;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
+import com.google.common.collect.ArrayListMultimap;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
+import javax.annotation.Nullable;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.util.ReflectionHelpers;
@@ -35,7 +39,8 @@
 
   private final List<Integer> globalActionsPerformed = new ArrayList<>();
   private List<AccessibilityNodeInfo.AccessibilityAction> systemActions;
-  private final List<AccessibilityWindowInfo> windows = new ArrayList<>();
+  private final ArrayListMultimap<Integer, AccessibilityWindowInfo> windows =
+      ArrayListMultimap.create();
   private final List<GestureDispatch> gesturesDispatched = new ArrayList<>();
 
   private boolean canDispatchGestures = true;
@@ -66,13 +71,41 @@
   }
 
   /**
-   * Returns a representation of interactive windows shown on the device screen. Mirrors the values
-   * provided to {@link #setWindows(List<AccessibilityWindowInfo>)}. Returns an empty List if not
-   * set.
+   * Returns a representation of interactive windows shown on the device's default display. Mirrors
+   * the values provided to {@link #setWindows(List<AccessibilityWindowInfo>)}. Returns an empty
+   * list if not set.
    */
   @Implementation(minSdk = LOLLIPOP)
   protected List<AccessibilityWindowInfo> getWindows() {
-    return new ArrayList<>(windows);
+    List<AccessibilityWindowInfo> windowInfos = windows.get(Display.DEFAULT_DISPLAY);
+    if (windowInfos != null) {
+      return new ArrayList<>(windowInfos);
+    } else {
+      return new ArrayList<>();
+    }
+  }
+
+  /**
+   * Returns a representation of interactive windows shown on the device's all displays. An empty
+   * list will be returned for default display and {@code null} will be return for other displays if
+   * they are not set by {@link #setWindowsOnDisplay(int, List)}.
+   */
+  @Implementation(minSdk = R)
+  protected SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
+    return cloneWindowOnAllDisplays();
+  }
+
+  private SparseArray<List<AccessibilityWindowInfo>> cloneWindowOnAllDisplays() {
+    SparseArray<List<AccessibilityWindowInfo>> anotherArray = new SparseArray<>();
+    for (int displayId : windows.keySet()) {
+      anotherArray.put(displayId, windows.get(displayId));
+    }
+
+    if (anotherArray.get(Display.DEFAULT_DISPLAY) == null) {
+      anotherArray.put(Display.DEFAULT_DISPLAY, new ArrayList<>());
+    }
+
+    return anotherArray;
   }
 
   @Implementation(minSdk = N)
@@ -130,13 +163,23 @@
   }
 
   /**
-   * Sets the list of interactive windows shown on the device screen as reported by {@link
-   * #getWindows()}
+   * Sets the list of interactive windows shown on the device's default display as reported by
+   * {@link #getWindows()}
    */
   public void setWindows(List<AccessibilityWindowInfo> windowList) {
-    windows.clear();
+    setWindowsOnDisplay(Display.DEFAULT_DISPLAY, windowList);
+  }
+
+  /**
+   * Sets the list of interactive windows shown on the device's {@code displayId} display. If the
+   * {@code windowList} is null, we will remove the list with given {@code displayId} display as
+   * reported by {@link #getWindowsOnAllDisplays()}.
+   */
+  public void setWindowsOnDisplay(
+      int displayId, @Nullable List<AccessibilityWindowInfo> windowList) {
+    windows.removeAll(displayId);
     if (windowList != null) {
-      windows.addAll(windowList);
+      windows.putAll(displayId, windowList);
     }
   }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityWindowInfo.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityWindowInfo.java
index 77f1a30..c20012e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityWindowInfo.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityWindowInfo.java
@@ -2,6 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
 import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.O;
 import static android.os.Build.VERSION_CODES.Q;
 import static org.robolectric.util.reflector.Reflector.reflector;
 
@@ -52,6 +53,8 @@
 
   private boolean isFocused = false;
 
+  private boolean isPictureInPicture = false;
+
   @RealObject private AccessibilityWindowInfo mRealAccessibilityWindowInfo;
 
   @Implementation
@@ -256,6 +259,11 @@
     return isAccessibilityFocused;
   }
 
+  @Implementation(minSdk = O)
+  protected boolean isInPictureInPictureMode() {
+    return isPictureInPicture;
+  }
+
   @Implementation
   protected void recycle() {
     // This shadow does not track recycling of windows.
@@ -314,6 +322,11 @@
     isFocused = focused;
   }
 
+  @Implementation(minSdk = O)
+  public void setPictureInPicture(boolean pictureInPicture) {
+    isPictureInPicture = pictureInPicture;
+  }
+
   public void addChild(AccessibilityWindowInfo child) {
     if (children == null) {
       children = new ArrayList<>();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java
index 70464bf..852919a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityThread.java
@@ -2,6 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.N;
 import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.os.Build.VERSION_CODES.P;
 import static android.os.Build.VERSION_CODES.R;
 import static android.os.Build.VERSION_CODES.S;
 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
@@ -157,7 +158,12 @@
   /** Update's ActivityThread's list of active Activities */
   void registerActivityLaunch(
       Intent intent, ActivityInfo activityInfo, Activity activity, IBinder token) {
-    ActivityClientRecord record = new ActivityClientRecord();
+    ActivityClientRecord record;
+    if (RuntimeEnvironment.getApiLevel() >= P) {
+      record = new ActivityClientRecord();
+    } else {
+      record = ReflectionHelpers.callConstructor(ActivityClientRecord.class);
+    }
     ActivityClientRecordReflector recordReflector =
         reflector(ActivityClientRecordReflector.class, record);
     recordReflector.setToken(token);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
index 4084825..290d785 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
@@ -5,7 +5,9 @@
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
 import static android.content.pm.PackageManager.GET_ACTIVITIES;
 import static android.content.pm.PackageManager.GET_META_DATA;
 import static android.content.pm.PackageManager.GET_PROVIDERS;
@@ -67,7 +69,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager.ComponentEnabledSetting;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager.OnPermissionsChangedListener;
@@ -336,6 +337,33 @@
     componentList.put(componentName, new ComponentState(newState, flags));
   }
 
+  @Implementation(minSdk = TIRAMISU)
+  protected void setComponentEnabledSettings(List<ComponentEnabledSetting> settings) {
+    for (ComponentEnabledSetting setting : settings) {
+      componentList.put(
+          setting.getComponentName(),
+          new ComponentState(setting.getEnabledState(), setting.getEnabledFlags()));
+    }
+  }
+
+  @Implementation(minSdk = Q)
+  protected void setSyntheticAppDetailsActivityEnabled(String packageName, boolean enabled) {
+    ComponentName componentName =
+        new ComponentName(packageName, PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME);
+    setComponentEnabledSetting(
+        componentName,
+        enabled ? COMPONENT_ENABLED_STATE_DEFAULT : COMPONENT_ENABLED_STATE_DISABLED,
+        DONT_KILL_APP);
+  }
+
+  @Implementation(minSdk = Q)
+  protected boolean getSyntheticAppDetailsActivityEnabled(String packageName) {
+    ComponentName componentName =
+        new ComponentName(packageName, PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME);
+    int state = getComponentEnabledSetting(componentName);
+    return state == COMPONENT_ENABLED_STATE_ENABLED || state == COMPONENT_ENABLED_STATE_DEFAULT;
+  }
+
   @Implementation
   protected void setApplicationEnabledSetting(String packageName, int newState, int flags) {
     applicationEnabledSettingMap.put(packageName, newState);
@@ -1096,7 +1124,10 @@
   @Implementation(minSdk = JELLY_BEAN_MR1)
   protected void extendVerificationTimeout(
       int id, int verificationCodeAtTimeout, long millisecondsToDelay) {
-    verificationTimeoutExtension.put(id, millisecondsToDelay);
+    synchronized (lock) {
+      verificationTimeoutExtension.put(id, millisecondsToDelay);
+      verificationCodeAtTimeoutExtension.put(id, verificationCodeAtTimeout);
+    }
   }
 
   @Override
@@ -1109,7 +1140,9 @@
 
   @Implementation
   protected void setInstallerPackageName(String targetPackage, String installerPackageName) {
-    packageInstallerMap.put(targetPackage, installerPackageName);
+    synchronized (lock) {
+      packageInstallerMap.put(targetPackage, installerPackageName);
+    }
   }
 
   @Implementation(minSdk = KITKAT)
@@ -2246,6 +2279,22 @@
     return reflector(ReflectorApplicationPackageManager.class, realObject).getContext();
   }
 
+  /** Stub that will always throw. */
+  @Implementation(minSdk = S)
+  protected Object /* PackageManager.Property */ getProperty(
+      String propertyName, String packageName) throws NameNotFoundException {
+    // TODO: in future read this value from parsed manifest
+    throw new NameNotFoundException("unsupported");
+  }
+
+  /** Stub that will always throw. */
+  @Implementation(minSdk = S)
+  protected Object /* PackageManager.Property */ getProperty(
+      String propertyName, ComponentName name) throws NameNotFoundException {
+    // TODO: in future read this value from parsed manifest
+    throw new NameNotFoundException("unsupported");
+  }
+
   /** Reflector interface for {@link ApplicationPackageManager}'s internals. */
   @ForType(ApplicationPackageManager.class)
   private interface ReflectorApplicationPackageManager {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java
old mode 100755
new mode 100644
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
old mode 100755
new mode 100644
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
index 2234351..5f2d4fd 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
@@ -93,6 +93,7 @@
   private ImmutableList<Object> defaultDevicesForAttributes = ImmutableList.of();
   private List<AudioDeviceInfo> inputDevices = new ArrayList<>();
   private List<AudioDeviceInfo> outputDevices = new ArrayList<>();
+  private List<AudioDeviceInfo> availableCommunicationDevices = new ArrayList<>();
   private AudioDeviceInfo communicationDevice = null;
 
   public ShadowAudioManager() {
@@ -451,6 +452,23 @@
   }
 
   /**
+   * Sets the list of available communication devices represented by {@link AudioDeviceInfo}.
+   *
+   * <p>The previous list of communication devices is replaced and no notifications of the list of
+   * {@link AudioDeviceCallback} is done.
+   *
+   * <p>To add/remove devices one by one and trigger notifications for the list of {@link
+   * AudioDeviceCallback} please use one of the following methods {@link
+   * #addOutputDevice(AudioDeviceInfo, boolean)}, {@link #removeOutputDevice(AudioDeviceInfo,
+   * boolean)}.
+   */
+  @TargetApi(VERSION_CODES.S)
+  public void setAvailableCommunicationDevices(
+      List<AudioDeviceInfo> availableCommunicationDevices) {
+    this.availableCommunicationDevices = new ArrayList<>(availableCommunicationDevices);
+  }
+
+  /**
    * Adds an input {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback} if
    * the device was not present before and indicated by {@code notifyAudioDeviceCallbacks}.
    */
@@ -497,6 +515,36 @@
   }
 
   /**
+   * Adds an available communication {@link AudioDeviceInfo} and notifies the list of {@link
+   * AudioDeviceCallback} if the device was not present before and indicated by {@code
+   * notifyAudioDeviceCallbacks}.
+   */
+  @TargetApi(VERSION_CODES.S)
+  public void addAvailableCommunicationDevice(
+      AudioDeviceInfo communicationDevice, boolean notifyAudioDeviceCallbacks) {
+    boolean changed =
+        !this.availableCommunicationDevices.contains(communicationDevice)
+            && this.availableCommunicationDevices.add(communicationDevice);
+    if (changed && notifyAudioDeviceCallbacks) {
+      notifyAudioDeviceCallbacks(ImmutableList.of(communicationDevice), /* added= */ true);
+    }
+  }
+
+  /**
+   * Removes an available communication {@link AudioDeviceInfo} and notifies the list of {@link
+   * AudioDeviceCallback} if the device was present before and indicated by {@code
+   * notifyAudioDeviceCallbacks}.
+   */
+  @TargetApi(VERSION_CODES.S)
+  public void removeAvailableCommunicationDevice(
+      AudioDeviceInfo communicationDevice, boolean notifyAudioDeviceCallbacks) {
+    boolean changed = this.availableCommunicationDevices.remove(communicationDevice);
+    if (changed && notifyAudioDeviceCallbacks) {
+      notifyAudioDeviceCallbacks(ImmutableList.of(communicationDevice), /* added= */ false);
+    }
+  }
+
+  /**
    * Registers an {@link AudioDeviceCallback} object to receive notifications of changes to the set
    * of connected audio devices.
    *
@@ -504,8 +552,10 @@
    *
    * @see #addInputDevice(AudioDeviceInfo, boolean)
    * @see #addOutputDevice(AudioDeviceInfo, boolean)
+   * @see #addAvailableCommunicationDevice(AudioDeviceInfo, boolean)
    * @see #removeInputDevice(AudioDeviceInfo, boolean)
    * @see #removeOutputDevice(AudioDeviceInfo, boolean)
+   * @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean)
    */
   @Implementation(minSdk = M)
   protected void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) {
@@ -520,8 +570,10 @@
    *
    * @see #addInputDevice(AudioDeviceInfo, boolean)
    * @see #addOutputDevice(AudioDeviceInfo, boolean)
+   * @see #addAvailableCommunicationDevice(AudioDeviceInfo, boolean)
    * @see #removeInputDevice(AudioDeviceInfo, boolean)
    * @see #removeOutputDevice(AudioDeviceInfo, boolean)
+   * @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean)
    */
   @Implementation(minSdk = M)
   protected void unregisterAudioDeviceCallback(AudioDeviceCallback callback) {
@@ -563,6 +615,11 @@
     this.communicationDevice = null;
   }
 
+  @Implementation(minSdk = S)
+  protected List<AudioDeviceInfo> getAvailableCommunicationDevices() {
+    return availableCommunicationDevices;
+  }
+
   @Implementation(minSdk = M)
   public AudioDeviceInfo[] getDevices(int flags) {
     List<AudioDeviceInfo> result = new ArrayList<>();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
index 7b6ff7e..188c60d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
@@ -35,7 +35,7 @@
 public class ShadowBluetoothGatt {
 
   private static final String NULL_CALLBACK_MSG = "BluetoothGattCallback can not be null.";
-
+  
   private BluetoothGattCallback bluetoothGattCallback;
   private int connectionPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED;
   private boolean isConnected = false;
@@ -143,7 +143,7 @@
   @Implementation(minSdk = JELLY_BEAN_MR2)
   protected void disconnect() {
     bluetoothGattReflector.disconnect();
-    if (this.getGattCallback() != null && this.isConnected) {
+    if (this.isCallbackAppropriate()) {
       this.getGattCallback()
           .onConnectionStateChange(
               this.realBluetoothGatt,
@@ -382,7 +382,6 @@
     return this.getGattCallback() != null && this.isConnected;
   }
 
-
   @ForType(BluetoothGatt.class)
   private interface BluetoothGattReflector {
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java
new file mode 100644
index 0000000..de2e76e
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java
@@ -0,0 +1,159 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.ReflectorObject;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+
+/** Shadow of {@link BluetoothGattServer}. */
+@Implements(value = BluetoothGattServer.class, minSdk = O)
+public class ShadowBluetoothGattServer {
+  private BluetoothGattServerCallback callback;
+  private final List<byte[]> responses = new ArrayList<>();
+  private final Set<BluetoothDevice> cancelledDevices = new HashSet<>();
+  private boolean isClosed;
+
+  @ReflectorObject protected BluetoothGattServerReflector bluetoothGattServerReflector;
+
+  /** Close this GATT server instance. */
+  @Implementation
+  protected void close() {
+    bluetoothGattServerReflector.close();
+    this.isClosed = true;
+  }
+
+  /**
+   * Disconnects an established connection, or cancels a connection attempt currently in progress.
+   *
+   * @param device Remote device
+   */
+  @Implementation
+  protected void cancelConnection(BluetoothDevice device) {
+    this.bluetoothGattServerReflector.cancelConnection(device);
+    this.cancelledDevices.add(device);
+  }
+
+  /**
+   * Send a response to a read or write request to a remote device.
+   *
+   * @param device The remote device to send this response to
+   * @param requestId The ID of the request that was received with the callback
+   * @param status The status of the request to be sent to the remote devices
+   * @param offset Value offset for partial read/write response
+   * @param value The value of the attribute that was read/written (optional)
+   */
+  @Implementation
+  protected boolean sendResponse(
+      BluetoothDevice device, int requestId, int status, int offset, byte[] value) {
+    this.responses.add(value);
+    return this.bluetoothGattServerReflector.sendResponse(device, requestId, status, offset, value);
+  }
+
+  /**
+   * Simulate a successful Gatt Server Connection with {@link BluetoothConnectionManager}. Performs
+   * a {@link BluetoothGattCallback#onConnectionStateChange} if available.
+   *
+   * @param device remote device
+   */
+  public void notifyConnection(BluetoothDevice device) {
+    BluetoothConnectionManager.getInstance().registerGattServerConnection(device.getAddress());
+    this.cancelledDevices.remove(device);
+
+    if (this.callback != null) {
+      this.callback.onConnectionStateChange(
+          device, BluetoothGatt.GATT_SUCCESS, BluetoothAdapter.STATE_CONNECTED);
+    }
+  }
+
+  /**
+   * Simulate a successful Gatt Server Disconnection with {@link BluetoothConnectionManager}.
+   * Performs a {@link BluetoothGattCallback#onConnectionStateChange} if available, even when device
+   * was not connected initially.
+   *
+   * @param device remote device
+   */
+  public void notifyDisconnection(BluetoothDevice device) {
+    BluetoothConnectionManager.getInstance().unregisterGattServerConnection(device.getAddress());
+    this.cancelledDevices.add(device);
+
+    if (this.callback != null) {
+      this.callback.onConnectionStateChange(
+          device, BluetoothGatt.GATT_SUCCESS, BluetoothAdapter.STATE_DISCONNECTED);
+    }
+  }
+
+  /**
+   * Get whether the device's connection has been cancelled.
+   *
+   * @param device remote device
+   */
+  public boolean isConnectionCancelled(BluetoothDevice device) {
+    return this.cancelledDevices.contains(device);
+  }
+
+  /**
+   * Returns true if the connection status of remote device is connected
+   *
+   * @param device remote device
+   */
+  public boolean isConnectedToDevice(BluetoothDevice device) {
+    return BluetoothConnectionManager.getInstance().hasGattServerConnection(device.getAddress());
+  }
+
+  /** Get a copy of the list of responses that have been sent. */
+  public List<byte[]> getResponses() {
+    List<byte[]> responsesCopy = new ArrayList<>();
+    for (byte[] response : this.responses) {
+      responsesCopy.add(response.clone());
+    }
+    return responsesCopy;
+  }
+
+  /** Clear the list of responses. */
+  public void clearResponses() {
+    this.responses.clear();
+  }
+
+  /** Get whether server has been closed. */
+  public boolean isClosed() {
+    return this.isClosed;
+  }
+
+  public void setGattServerCallback(BluetoothGattServerCallback callback) {
+    this.callback = callback;
+  }
+
+  public BluetoothGattServerCallback getGattServerCallback() {
+    return this.callback;
+  }
+
+  public BluetoothConnectionManager getBluetoothConnectionManager() {
+    return BluetoothConnectionManager.getInstance();
+  }
+
+  @ForType(BluetoothGattServer.class)
+  private interface BluetoothGattServerReflector {
+
+    @Direct
+    void close();
+
+    @Direct
+    void cancelConnection(BluetoothDevice device);
+
+    @Direct
+    boolean sendResponse(
+        BluetoothDevice device, int requestId, int status, int offset, byte[] value);
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java
index a5dd7bf..db9d1bd 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java
@@ -1,19 +1,31 @@
 package org.robolectric.shadows;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.S;
 import static com.google.common.base.Preconditions.checkArgument;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothGatt;
+import android.content.Context;
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.ImmutableIntArray;
 import java.util.ArrayList;
 import java.util.List;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.PerfStatsCollector;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
 
 /** Shadow of {@link BluetoothManager} that makes the testing possible. */
 @Implements(value = BluetoothManager.class, minSdk = JELLY_BEAN_MR2)
@@ -57,6 +69,11 @@
     }
   }
 
+  /**
+   * Get the {@link BluetoothAdapter} for this device.
+   *
+   * @return BluetoothAdapter instance
+   */
   @Implementation
   protected BluetoothAdapter getAdapter() {
     return BluetoothAdapter.getDefaultAdapter();
@@ -101,4 +118,45 @@
   private boolean isProfileValid(int profile) {
     return profile == BluetoothProfile.GATT || profile == BluetoothProfile.GATT_SERVER;
   }
+
+  @Implementation(minSdk = O, maxSdk = R)
+  protected BluetoothGattServer openGattServer(
+      Context context, BluetoothGattServerCallback callback, int transport) {
+    return createGattServer(context, callback, transport);
+  }
+
+  /**
+   * Overrides behavior of {@link openGattServer} and returns {@link ShadowBluetoothGattServer}
+   * after creating and using a nullProxy for {@link IBluetoothGatt}.
+   */
+  @Implementation(minSdk = S)
+  protected BluetoothGattServer openGattServer(
+      Context context, BluetoothGattServerCallback callback, int transport, boolean eattSupport) {
+    return createGattServer(context, callback, transport);
+  }
+
+  private BluetoothGattServer createGattServer(
+      Context unusedContext, BluetoothGattServerCallback callback, int transport) {
+    IBluetoothGatt iGatt = ReflectionHelpers.createNullProxy(IBluetoothGatt.class);
+    BluetoothGattServer gattServer;
+
+    if (RuntimeEnvironment.getApiLevel() <= R) {
+      gattServer =
+          ReflectionHelpers.callConstructor(
+              BluetoothGattServer.class,
+              ClassParameter.from(IBluetoothGatt.class, iGatt),
+              ClassParameter.from(int.class, transport));
+    } else {
+      gattServer =
+          ReflectionHelpers.callConstructor(
+              BluetoothGattServer.class,
+              ClassParameter.from(IBluetoothGatt.class, iGatt),
+              ClassParameter.from(int.class, transport),
+              ClassParameter.from(BluetoothAdapter.class, this.getAdapter()));
+    }
+    PerfStatsCollector.getInstance().incrementCount("constructShadowBluetoothGattServer");
+    ShadowBluetoothGattServer shadowBluetoothGattServer = Shadow.extract(gattServer);
+    shadowBluetoothGattServer.setGattServerCallback(callback);
+    return gattServer;
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java
index 22df7cf..7be474b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java
@@ -140,6 +140,12 @@
     return shareDescription;
   }
 
+  /** Returns the screenshot file descriptor if set with {@code startBugreport}, else null. */
+  @Nullable
+  public ParcelFileDescriptor getScreenshotFd() {
+    return screenshotFd;
+  }
+
   private void resetParams() {
     try {
       bugreportFd.close();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java
index da43162..ffe0097 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java
@@ -17,7 +17,6 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
 import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
 
 @Implements(Camera.class)
 public class ShadowCamera {
@@ -300,7 +299,7 @@
 
     /** Add custom preview sizes to supportedPreviewSizes. */
     public void addSupportedPreviewSize(int width, int height) {
-      Camera.Size newSize = ReflectionHelpers.newInstance(Camera.class).new Size(width, height);
+      Camera.Size newSize = newInstanceOf(Camera.class).new Size(width, height);
       supportedPreviewSizes.add(newSize);
     }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
index a4b1fb7..cff9382 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
@@ -7,6 +7,7 @@
 
 import android.view.Choreographer;
 import android.view.Choreographer.FrameCallback;
+import android.view.DisplayEventReceiver;
 import java.time.Duration;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
@@ -171,5 +172,8 @@
 
     @Direct
     void doFrame(long frameTimeNanos, int frame);
+
+    @Accessor("mDisplayEventReceiver")
+    DisplayEventReceiver getReceiver();
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java
index 7937487..6201869 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java
@@ -3,6 +3,9 @@
 import static java.util.stream.Collectors.toCollection;
 import static java.util.stream.Collectors.toList;
 
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -21,6 +24,10 @@
 import java.util.concurrent.Executor;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.Reflector;
+
 
 /** Shadow for CompanionDeviceManager. */
 @Implements(value = CompanionDeviceManager.class, minSdk = VERSION_CODES.O)
@@ -136,13 +143,17 @@
         MacAddress.fromString(info.deviceMacAddress()),
         info.displayName(),
         info.deviceProfile(),
+        info.associatedDevice(),
         info.selfManaged(),
         info.notifyOnDeviceNearby(),
+        info.revoked(),
         info.timeApprovedMs(),
-        info.lastTimeConnectedMs());
+        info.lastTimeConnectedMs(),
+        info.systemDataSyncFlags());
   }
 
   private RoboAssociationInfo createShadowAssociationInfo(AssociationInfo info) {
+    var ref_info = Reflector.reflector(_AssociationInfo_.class, info);
     return RoboAssociationInfo.create(
         info.getId(),
         info.getUserId(),
@@ -150,10 +161,20 @@
         info.getDeviceMacAddress().toString(),
         info.getDisplayName(),
         info.getDeviceProfile(),
+            ref_info.getFullAssociatedDevice(),
         info.isSelfManaged(),
         info.isNotifyOnDeviceNearby(),
+        info.isRevoked(),
         info.getTimeApprovedMs(),
-        info.getLastTimeConnectedMs());
+        info.getLastTimeConnectedMs(),
+        info.getSystemDataSyncFlags());
+  }
+
+  @ForType(AssociationInfo.class)
+  public interface _AssociationInfo_ {
+
+    @Accessor("mAssociatedDevice")
+    AssociatedDevice getFullAssociatedDevice();
   }
 
   /**
@@ -178,14 +199,20 @@
     @Nullable
     public abstract String deviceProfile();
 
+    public abstract AssociatedDevice associatedDevice();
+
     public abstract boolean selfManaged();
 
     public abstract boolean notifyOnDeviceNearby();
 
+    public abstract boolean revoked();
+
     public abstract long timeApprovedMs();
 
     public abstract long lastTimeConnectedMs();
 
+    public abstract int systemDataSyncFlags();
+
     public static Builder builder() {
       return new AutoValue_ShadowCompanionDeviceManager_RoboAssociationInfo.Builder()
           .setId(1)
@@ -193,7 +220,8 @@
           .setSelfManaged(false)
           .setNotifyOnDeviceNearby(false)
           .setTimeApprovedMs(0)
-          .setLastTimeConnectedMs(0);
+          .setLastTimeConnectedMs(0)
+          .setSystemDataSyncFlags(-1);
     }
 
     public static RoboAssociationInfo create(
@@ -203,10 +231,13 @@
         String deviceMacAddress,
         CharSequence displayName,
         String deviceProfile,
+        AssociatedDevice associatedDevice,
         boolean selfManaged,
         boolean notifyOnDeviceNearby,
+        boolean revoked,
         long timeApprovedMs,
-        long lastTimeConnectedMs) {
+        long lastTimeConnectedMs,
+        int systemDataSyncFlags) {
       return RoboAssociationInfo.builder()
           .setId(id)
           .setUserId(userId)
@@ -214,10 +245,13 @@
           .setDeviceMacAddress(deviceMacAddress)
           .setDisplayName(displayName)
           .setDeviceProfile(deviceProfile)
+              .setAssociatedDevice(associatedDevice)
           .setSelfManaged(selfManaged)
           .setNotifyOnDeviceNearby(notifyOnDeviceNearby)
           .setTimeApprovedMs(timeApprovedMs)
+              .setRevoked(revoked)
           .setLastTimeConnectedMs(lastTimeConnectedMs)
+          .setSystemDataSyncFlags(systemDataSyncFlags)
           .build();
     }
 
@@ -238,12 +272,18 @@
 
       public abstract Builder setSelfManaged(boolean selfManaged);
 
+      public abstract Builder setAssociatedDevice(AssociatedDevice device);
+
       public abstract Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby);
 
+      public abstract Builder setRevoked(boolean revoked);
+
       public abstract Builder setTimeApprovedMs(long timeApprovedMs);
 
       public abstract Builder setLastTimeConnectedMs(long lastTimeConnectedMs);
 
+      public abstract Builder setSystemDataSyncFlags(int flags);
+
       public abstract RoboAssociationInfo build();
     }
   }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
index 0b86844..bb2672c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
@@ -7,6 +7,7 @@
 import static android.content.ContentResolver.SCHEME_CONTENT;
 import static android.content.ContentResolver.SCHEME_FILE;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
 import static android.os.Build.VERSION_CODES.KITKAT;
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Build.VERSION_CODES.Q;
@@ -201,7 +202,25 @@
   }
 
   @Implementation
-  protected final OutputStream openOutputStream(final Uri uri) {
+  protected final OutputStream openOutputStream(final Uri uri) throws FileNotFoundException {
+    try {
+      return openOutputStream(uri, "w");
+    } catch (SecurityException | FileNotFoundException e) {
+      // This is legacy behavior is only supported because existing users require it.
+      return new OutputStream() {
+        @Override
+        public void write(int arg0) throws IOException {}
+
+        @Override
+        public String toString() {
+          return "outputstream for " + uri;
+        }
+      };
+    }
+  }
+
+  @Implementation
+  protected final OutputStream openOutputStream(Uri uri, String mode) throws FileNotFoundException {
     Supplier<OutputStream> supplier = outputStreamMap.get(uri);
     if (supplier != null) {
       OutputStream outputStream = supplier.get();
@@ -209,15 +228,8 @@
         return outputStream;
       }
     }
-    return new OutputStream() {
-      @Override
-      public void write(int arg0) throws IOException {}
-
-      @Override
-      public String toString() {
-        return "outputstream for " + uri;
-      }
-    };
+    return reflector(ContentResolverReflector.class, realContentResolver)
+        .openOutputStream(uri, mode);
   }
 
   /**
@@ -553,7 +565,7 @@
       }
       for (Map.Entry<Account, Status> mp : map.getValue().entrySet()) {
         if (isSyncActive(mp.getKey(), map.getKey())) {
-          SyncInfo si = new SyncInfo(0, mp.getKey(), map.getKey(), 0);
+          SyncInfo si = newSyncInfo(0, mp.getKey(), map.getKey(), 0);
           list.add(si);
         }
       }
@@ -561,6 +573,20 @@
     return list;
   }
 
+  private static SyncInfo newSyncInfo(
+      int authorityId, Account account, String authority, long startTime) {
+    if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
+      return new SyncInfo(authorityId, account, authority, startTime);
+    } else {
+      return ReflectionHelpers.callConstructor(
+          SyncInfo.class,
+          ClassParameter.from(int.class, authorityId),
+          ClassParameter.from(Account.class, account),
+          ClassParameter.from(String.class, authority),
+          ClassParameter.from(long.class, startTime));
+    }
+  }
+
   @Implementation
   protected static void setIsSyncable(Account account, String authority, int syncable) {
     getStatus(account, authority, true).state = syncable;
@@ -1160,6 +1186,6 @@
     InputStream openInputStream(Uri uri) throws FileNotFoundException;
 
     @Direct
-    OutputStream openOutputStream(Uri uri) throws FileNotFoundException;
+    OutputStream openOutputStream(Uri uri, String mode) throws FileNotFoundException;
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java
index abbed64..83d5235 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java
@@ -2,16 +2,17 @@
 
 import static android.os.Build.VERSION_CODES.KITKAT;
 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
 
 import java.text.FieldPosition;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
-import libcore.icu.DateIntervalFormat;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
-@Implements(value = DateIntervalFormat.class, isInAndroidSdk = false, minSdk = KITKAT)
+@Implements(className = "libcore.icu.DateIntervalFormat", isInAndroidSdk = false, minSdk = KITKAT,
+        maxSdk = TIRAMISU)
 public class ShadowDateIntervalFormat {
 
   private static long address;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormatU.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormatU.java
new file mode 100644
index 0000000..ac435c4
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormatU.java
@@ -0,0 +1,42 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import java.text.FieldPosition;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import android.text.format.DateIntervalFormat;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(value = DateIntervalFormat.class, isInAndroidSdk = false, minSdk = UPSIDE_DOWN_CAKE)
+public class ShadowDateIntervalFormatU {
+
+  private static long address;
+  private static Map<Long, com.ibm.icu.text.DateIntervalFormat> INTERVAL_CACHE = new HashMap<>();
+
+  @Implementation
+  public static long createDateIntervalFormat(String skeleton, String localeName, String tzName) {
+    address++;
+    INTERVAL_CACHE.put(address, com.ibm.icu.text.DateIntervalFormat.getInstance(skeleton, new Locale(localeName)));
+    return address;
+  }
+
+  @Implementation
+  public static void destroyDateIntervalFormat(long address) {
+    INTERVAL_CACHE.remove(address);
+  }
+
+  @Implementation
+  @SuppressWarnings("JdkObsolete")
+  public static String formatDateInterval(long address, long fromDate, long toDate) {
+    StringBuffer buffer = new StringBuffer();
+
+    FieldPosition pos = new FieldPosition(0);
+    INTERVAL_CACHE.get(address).format(new com.ibm.icu.util.DateInterval(fromDate, toDate), buffer, pos);
+
+    return buffer.toString();
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateUtils.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateUtils.java
new file mode 100644
index 0000000..1a5114a
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateUtils.java
@@ -0,0 +1,30 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.text.format.DateUtils;
+import org.robolectric.annotation.Implements;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
+
+/** Shadow for {@link DateUtils}. */
+@Implements(value = DateUtils.class, isInAndroidSdk = false)
+public class ShadowDateUtils {
+
+  /**
+   * internal only
+   *
+   * <p>Does not need to be a resetter method because Configuration at test startup.
+   */
+  public static void resetLastConfig() {
+    reflector(DateUtilsReflector.class).setLastConfig(null);
+  }
+
+  @ForType(DateUtils.class)
+  interface DateUtilsReflector {
+    @Static
+    @Accessor("sLastConfig")
+    void setLastConfig(String lastConfig);
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
index dbaa353..422f96f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
@@ -89,6 +89,7 @@
   private Map<String, Bundle> applicationRestrictionsMap = new HashMap<>();
   private CharSequence organizationName;
   private int organizationColor;
+  private boolean isAutoTimeEnabled;
   private boolean isAutoTimeRequired;
   private boolean isAutoTimeZoneEnabled;
   private String timeZone;
@@ -602,6 +603,18 @@
     return organizationColor;
   }
 
+  @Implementation(minSdk = R)
+  protected void setAutoTimeEnabled(ComponentName admin, boolean enabled) {
+    enforceDeviceOwnerOrProfileOwner(admin);
+    isAutoTimeEnabled = enabled;
+  }
+
+  @Implementation(minSdk = R)
+  protected boolean getAutoTimeEnabled(ComponentName admin) {
+    enforceDeviceOwnerOrProfileOwner(admin);
+    return isAutoTimeEnabled;
+  }
+
   @Implementation(minSdk = LOLLIPOP)
   protected void setAutoTimeRequired(ComponentName admin, boolean required) {
     enforceDeviceOwnerOrProfileOwner(admin);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java
index c4f5770..75d3579 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java
@@ -405,6 +405,11 @@
       throw new UnsupportedOperationException("HDR capabilities are not supported below Android N");
     }
 
+    if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) {
+      reflector(DisplayModeReflector.class, realObject.getMode())
+          .setSupportedHdrTypes(supportedHdrTypes);
+    }
+
     ShadowDisplayManager.changeDisplay(
         displayId,
         displayConfig -> {
@@ -474,4 +479,10 @@
     @Accessor("mFlags")
     void setFlags(int flags);
   }
+
+  @ForType(Display.Mode.class)
+  interface DisplayModeReflector {
+    @Accessor("mSupportedHdrTypes")
+    void setSupportedHdrTypes(int[] types);
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java
index 9959ca1..d15da6b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java
@@ -1,7 +1,10 @@
 package org.robolectric.shadows;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.P;
+import static java.util.Objects.requireNonNull;
 import static org.robolectric.shadow.api.Shadow.extract;
 import static org.robolectric.shadow.api.Shadow.invokeConstructor;
 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
@@ -17,8 +20,10 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import com.google.auto.value.AutoBuilder;
+import java.util.HashMap;
 import java.util.List;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.android.Bootstrap;
@@ -27,6 +32,7 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
+import org.robolectric.annotation.Resetter;
 import org.robolectric.res.Qualifiers;
 import org.robolectric.util.Consumer;
 import org.robolectric.util.ReflectionHelpers.ClassParameter;
@@ -44,6 +50,13 @@
 
   private Context context;
 
+  private static final HashMap<Integer, Boolean> displayIsNaturallyPortrait = new HashMap<>();
+
+  @Resetter
+  public static void reset() {
+    displayIsNaturallyPortrait.clear();
+  }
+
   @Implementation
   protected void __constructor__(Context context) {
     this.context = context;
@@ -74,11 +87,12 @@
       throw new IllegalStateException("this method should only be called by Robolectric");
     }
 
-    shadowDisplayManagerGlobal.addDisplay(createDisplayInfo(configuration, displayMetrics));
+    shadowDisplayManagerGlobal.addDisplay(
+        createDisplayInfo(configuration, displayMetrics, /* isNaturallyPortrait= */ true));
   }
 
   private static DisplayInfo createDisplayInfo(
-      Configuration configuration, DisplayMetrics displayMetrics) {
+      Configuration configuration, DisplayMetrics displayMetrics, boolean isNaturallyPortrait) {
     int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density);
     int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density);
 
@@ -93,9 +107,9 @@
     displayInfo.logicalWidth = widthPx;
     displayInfo.logicalHeight = heightPx;
     displayInfo.rotation =
-        configuration.orientation == Configuration.ORIENTATION_PORTRAIT
-            ? Surface.ROTATION_0
-            : Surface.ROTATION_90;
+        configuration.orientation == ORIENTATION_PORTRAIT
+            ? (isNaturallyPortrait ? Surface.ROTATION_0 : Surface.ROTATION_90)
+            : (isNaturallyPortrait ? Surface.ROTATION_90 : Surface.ROTATION_0);
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       displayInfo.modeId = 0;
       displayInfo.defaultModeId = 0;
@@ -115,16 +129,19 @@
     return displayInfo;
   }
 
-  private static DisplayInfo createDisplayInfo(String qualifiersStr, DisplayInfo baseDisplayInfo) {
+  private static DisplayInfo createDisplayInfo(String qualifiersStr, @Nullable Integer displayId) {
+    DisplayInfo baseDisplayInfo =
+        displayId != null ? DisplayManagerGlobal.getInstance().getDisplayInfo(displayId) : null;
     Configuration configuration = new Configuration();
     DisplayMetrics displayMetrics = new DisplayMetrics();
 
+    boolean isNaturallyPortrait =
+        requireNonNull(displayIsNaturallyPortrait.getOrDefault(displayId, true));
     if (qualifiersStr.startsWith("+") && baseDisplayInfo != null) {
       configuration.orientation =
-          (baseDisplayInfo.rotation == Surface.ROTATION_0
-                  || baseDisplayInfo.rotation == Surface.ROTATION_180)
-              ? Configuration.ORIENTATION_PORTRAIT
-              : Configuration.ORIENTATION_LANDSCAPE;
+          isRotated(baseDisplayInfo.rotation)
+              ? (isNaturallyPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT)
+              : (isNaturallyPortrait ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE);
       configuration.screenWidthDp =
           baseDisplayInfo.logicalWidth
               * DisplayMetrics.DENSITY_DEFAULT
@@ -142,7 +159,11 @@
     Bootstrap.applyQualifiers(
         qualifiersStr, RuntimeEnvironment.getApiLevel(), configuration, displayMetrics);
 
-    return createDisplayInfo(configuration, displayMetrics);
+    return createDisplayInfo(configuration, displayMetrics, isNaturallyPortrait);
+  }
+
+  private static boolean isRotated(int rotation) {
+    return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
   }
 
   private static void fixNominalDimens(DisplayInfo displayInfo) {
@@ -168,13 +189,33 @@
    *     display
    */
   public static void changeDisplay(int displayId, String qualifiersStr) {
-    DisplayInfo baseDisplayInfo = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
-    DisplayInfo displayInfo = createDisplayInfo(qualifiersStr, baseDisplayInfo);
+    DisplayInfo displayInfo = createDisplayInfo(qualifiersStr, displayId);
     getShadowDisplayManagerGlobal().changeDisplay(displayId, displayInfo);
     shadowMainLooper().idle();
   }
 
   /**
+   * Changes the display to be naturally portrait or landscape. This will ensure that the rotation
+   * is configured consistently with orientation when the orientation is configured by {@link
+   * #changeDisplay}, e.g. if the display is naturally portrait and the orientation is configured as
+   * landscape the rotation will be set to {@link Surface#ROTATION_90}.
+   */
+  public static void setNaturallyPortrait(int displayId, boolean isNaturallyPortrait) {
+    displayIsNaturallyPortrait.put(displayId, isNaturallyPortrait);
+    changeDisplay(
+        displayId,
+        config -> {
+          boolean isRotated = isRotated(config.rotation);
+          boolean isPortrait = config.logicalHeight > config.logicalWidth;
+          if ((isNaturallyPortrait ^ isPortrait) != isRotated) {
+            config.rotation =
+                (isNaturallyPortrait ^ isPortrait) ? Surface.ROTATION_90 : Surface.ROTATION_0;
+          }
+        });
+    shadowMainLooper().idle();
+  }
+
+  /**
    * Sets supported modes to the specified display with ID {@code displayId}.
    *
    * <p>Idles the main looper to ensure all listeners are notified.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java
index f7f5f5b..d0fdab0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java
@@ -18,16 +18,12 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import com.google.common.annotations.VisibleForTesting;
-
 import java.lang.reflect.Field;
-import java.sql.Array;
-import java.text.Format;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.TreeMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import javax.annotation.Nullable;
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.android.Bootstrap;
 import org.robolectric.annotation.HiddenApi;
 import org.robolectric.annotation.Implementation;
@@ -84,7 +80,7 @@
   private static DisplayManagerGlobal newDisplayManagerGlobal(IDisplayManager displayManager) {
     instance = Shadow.newInstanceOf(DisplayManagerGlobal.class);
     DisplayManagerGlobalReflector displayManagerGlobal =
-            reflector(DisplayManagerGlobalReflector.class, instance);
+        reflector(DisplayManagerGlobalReflector.class, instance);
     displayManagerGlobal.setDm(displayManager);
     displayManagerGlobal.setLock(new Object());
     List<Handler> displayListeners = createDisplayListeners();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java
index 475607a..4f488b5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java
@@ -84,6 +84,11 @@
   }
 
   @Implementation
+  protected static EGLContext eglGetCurrentContext() {
+    return createEglContext(3);
+  }
+
+  @Implementation
   protected static boolean eglMakeCurrent(
       EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
     return true;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java
index 42f7711..2972ed7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFingerprintManager.java
@@ -10,6 +10,8 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.hardware.fingerprint.FingerprintManager.CryptoObject;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Build.VERSION_CODES;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.util.Log;
@@ -183,4 +185,9 @@
   protected boolean isHardwareDetected() {
     return this.isHardwareDetected;
   }
+
+  @Implementation(minSdk = VERSION_CODES.S)
+  protected List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal() {
+    return new ArrayList<>();
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
index e76b797..da5070a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
@@ -1011,10 +1011,20 @@
   }
 
   int checkPermission(String permission, int pid, int uid) {
-    Set<String> grantedPermissionsForPidUid = grantedPermissionsMap.get(new Pair(pid, uid));
-    return grantedPermissionsForPidUid != null && grantedPermissionsForPidUid.contains(permission)
-        ? PERMISSION_GRANTED
-        : PERMISSION_DENIED;
+    if (pid == -1) {
+      for (Map.Entry<Pair<Integer, Integer>, Set<String>> entry :
+          grantedPermissionsMap.entrySet()) {
+        if (entry.getKey().second == uid && entry.getValue().contains(permission)) {
+          return PERMISSION_GRANTED;
+        }
+      }
+      return PERMISSION_DENIED;
+    } else {
+      Set<String> grantedPermissionsForPidUid = grantedPermissionsMap.get(new Pair(pid, uid));
+      return grantedPermissionsForPidUid != null && grantedPermissionsForPidUid.contains(permission)
+          ? PERMISSION_GRANTED
+          : PERMISSION_DENIED;
+    }
   }
 
   void grantPermissions(String... permissionNames) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java
index 4b28baf..0cbc9fb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java
@@ -49,10 +49,13 @@
 public class ShadowLauncherApps {
   private List<ShortcutInfo> shortcuts = new ArrayList<>();
   private final Multimap<UserHandle, String> enabledPackages = HashMultimap.create();
+  private final Multimap<UserHandle, ComponentName> enabledActivities = HashMultimap.create();
   private final Multimap<UserHandle, LauncherActivityInfo> shortcutActivityList =
       HashMultimap.create();
   private final Multimap<UserHandle, LauncherActivityInfo> activityList = HashMultimap.create();
   private final Map<UserHandle, Map<String, ApplicationInfo>> applicationInfoList = new HashMap<>();
+  private final Map<UserHandle, Map<String, Bundle>> suspendedPackageLauncherExtras =
+      new HashMap<>();
 
   private final List<Pair<LauncherApps.Callback, Handler>> callbacks = new ArrayList<>();
   private boolean hasShortcutHostPermission = false;
@@ -100,6 +103,17 @@
   }
 
   /**
+   * Sets an activity referenced by ComponentName as enabled, to be checked by {@link
+   * #isActivityEnabled(ComponentName, UserHandle)}.
+   *
+   * @param userHandle the user handle to be set.
+   * @param componentName the component name of the activity to be enabled.
+   */
+  public void setActivityEnabled(UserHandle userHandle, ComponentName componentName) {
+    enabledActivities.put(userHandle, componentName);
+  }
+
+  /**
    * Adds a {@link LauncherActivityInfo} to be retrieved by {@link
    * #getShortcutConfigActivityList(String, UserHandle)}.
    *
@@ -205,11 +219,36 @@
         "Package " + packageName + " not found for user " + user.getIdentifier());
   }
 
+  /**
+   * Adds a {@link Bundle} to be retrieved by {@link #getSuspendedPackageLauncherExtras(String,
+   * UserHandle)}.
+   *
+   * @param userHandle the user handle to be added.
+   * @param packageName the package name to be added.
+   * @param bundle the bundle for the extras.
+   */
+  public void addSuspendedPackageLauncherExtras(
+      UserHandle userHandle, String packageName, Bundle bundle) {
+    if (!suspendedPackageLauncherExtras.containsKey(userHandle)) {
+      suspendedPackageLauncherExtras.put(userHandle, new HashMap<>());
+    }
+    suspendedPackageLauncherExtras.get(userHandle).put(packageName, bundle);
+  }
+
   @Implementation(minSdk = P)
   @Nullable
-  protected Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) {
-    throw new UnsupportedOperationException(
-        "This method is not currently supported in Robolectric.");
+  protected Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user)
+      throws NameNotFoundException {
+    Map<String, Bundle> map = suspendedPackageLauncherExtras.get(user);
+    if (map != null && map.containsKey(packageName)) {
+      return map.get(packageName);
+    }
+
+    throw new NameNotFoundException(
+        "Suspended package extras for  "
+            + packageName
+            + " not found for user "
+            + user.getIdentifier());
   }
 
   @Implementation(minSdk = Q)
@@ -219,10 +258,9 @@
         "This method is not currently supported in Robolectric.");
   }
 
-  @Implementation
+  @Implementation(minSdk = L)
   protected boolean isActivityEnabled(ComponentName component, UserHandle user) {
-    throw new UnsupportedOperationException(
-        "This method is not currently supported in Robolectric.");
+    return enabledActivities.containsEntry(user, component);
   }
 
   /**
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLoadedApk.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLoadedApk.java
index 2a3a61b..f954ac2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLoadedApk.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLoadedApk.java
@@ -1,20 +1,38 @@
 package org.robolectric.shadows;
 
+import static org.robolectric.shadow.api.Shadow.newInstanceOf;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
 import android.app.Application;
 import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.os.Build.VERSION_CODES;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
 import org.robolectric.util.reflector.Accessor;
 import org.robolectric.util.reflector.ForType;
 
 @Implements(value = LoadedApk.class, isInAndroidSdk = false)
 public class ShadowLoadedApk {
+  @RealObject private LoadedApk realLoadedApk;
+  private boolean isClassLoaderInitialized = false;
+  private final Object classLoaderLock = new Object();
 
   @Implementation
   public ClassLoader getClassLoader() {
+    // The AppComponentFactory was introduced from SDK 28.
+    if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.P) {
+      synchronized (classLoaderLock) {
+        if (!isClassLoaderInitialized) {
+          isClassLoaderInitialized = true;
+          tryInitAppComponentFactory(realLoadedApk);
+        }
+      }
+    }
     return this.getClass().getClassLoader();
   }
 
@@ -23,6 +41,35 @@
     return this.getClass().getClassLoader();
   }
 
+  private void tryInitAppComponentFactory(LoadedApk realLoadedApk) {
+    if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.P) {
+      ApplicationInfo applicationInfo = realLoadedApk.getApplicationInfo();
+      if (applicationInfo == null || applicationInfo.appComponentFactory == null) {
+        return;
+      }
+      _LoadedApk_ loadedApkReflector = reflector(_LoadedApk_.class, realLoadedApk);
+      if (!loadedApkReflector.getIncludeCode()) {
+        return;
+      }
+      String fullQualifiedClassName =
+          calculateFullQualifiedClassName(
+              applicationInfo.appComponentFactory, applicationInfo.packageName);
+      android.app.AppComponentFactory factory =
+          (android.app.AppComponentFactory) newInstanceOf(fullQualifiedClassName);
+      if (factory == null) {
+        factory = new android.app.AppComponentFactory();
+      }
+      loadedApkReflector.setAppFactory(factory);
+    }
+  }
+
+  private String calculateFullQualifiedClassName(String className, String packageName) {
+    if (packageName == null) {
+      return className;
+    }
+    return className.startsWith(".") ? packageName + className : className;
+  }
+
   /** Accessor interface for {@link LoadedApk}'s internals. */
   @ForType(LoadedApk.class)
   public interface _LoadedApk_ {
@@ -32,5 +79,11 @@
 
     @Accessor("mResources")
     void setResources(Resources resources);
+
+    @Accessor("mIncludeCode")
+    boolean getIncludeCode();
+
+    @Accessor("mAppComponentFactory")
+    void setAppFactory(Object appFactory);
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java
index 9cbf5ee..57fc6e9 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java
@@ -390,16 +390,16 @@
         if (locationProviderConstructor == null) {
           if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) {
             locationProviderConstructor =
-                LocationProvider.class.getConstructor(
+                LocationProvider.class.getDeclaredConstructor(
                     String.class, android.location.provider.ProviderProperties.class);
           } else {
             locationProviderConstructor =
-                LocationProvider.class.getConstructor(
+                LocationProvider.class.getDeclaredConstructor(
                     String.class,
                     Class.forName("com.android.internal.location.ProviderProperties"));
           }
-          locationProviderConstructor.setAccessible(true);
         }
+        locationProviderConstructor.setAccessible(true);
 
         if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.S) {
           return locationProviderConstructor.newInstance(name, properties.getProviderProperties());
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaExtractor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaExtractor.java
index bd06be2..aa1763e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaExtractor.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaExtractor.java
@@ -2,6 +2,7 @@
 
 import static android.os.Build.VERSION_CODES.M;
 import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.O;
 import static java.lang.Math.min;
 import static org.robolectric.shadows.util.DataSource.toDataSource;
 
@@ -11,6 +12,7 @@
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.net.Uri;
+import android.os.PersistableBundle;
 import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -48,8 +50,10 @@
   }
 
   private static final Map<DataSource, List<TrackInfo>> tracksMap = new HashMap<>();
+  private static final Map<DataSource, PersistableBundle> metricsMap = new HashMap<>();
 
   private List<TrackInfo> tracks;
+  private PersistableBundle metrics;
   private int[] trackSampleReadPositions;
   private int[] trackLastReadSize;
   private int selectedTrackIndex = -1;
@@ -70,6 +74,15 @@
     tracks.add(trackInfo);
   }
 
+  /**
+   * Sets metrics for an associated {@link org.robolectric.shadows.util.DataSource}.
+   *
+   * @param metrics the data which will be returned by {@link MediaExtractor#getMetrics()}.
+   */
+  public static void setMetrics(DataSource dataSource, PersistableBundle metrics) {
+    metricsMap.put(dataSource, metrics);
+  }
+
   private void setDataSource(DataSource dataSource) {
     if (tracksMap.containsKey(dataSource)) {
       this.tracks = tracksMap.get(dataSource);
@@ -81,6 +94,8 @@
     Arrays.fill(trackSampleReadPositions, 0);
     this.trackLastReadSize = new int[tracks.size()];
     Arrays.fill(trackLastReadSize, 0);
+
+    this.metrics = metricsMap.get(dataSource);
   }
 
   @Implementation(minSdk = N)
@@ -203,9 +218,15 @@
     selectedTrackIndex = -1;
   }
 
+  @Implementation(minSdk = O)
+  protected PersistableBundle getMetrics() {
+    return metrics;
+  }
+
   @Resetter
   public static void reset() {
     tracksMap.clear();
+    metricsMap.clear();
     DataSource.reset();
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMetadataRetriever.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMetadataRetriever.java
index b38da28..14de44a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMetadataRetriever.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMetadataRetriever.java
@@ -72,6 +72,11 @@
     return (frames.containsKey(dataSource) ? frames.get(dataSource).get(timeUs) : null);
   }
 
+  @Implementation
+  protected Bitmap getFrameAtTime() {
+    return getFrameAtTime(/* timeUs= */ 1, /* option= */ 0);
+  }
+
   @Implementation(minSdk = O_MR1)
   protected Bitmap getScaledFrameAtTime(long timeUs, int option, int dstWidth, int dstHeight) {
     return (scaledFrames.containsKey(dataSource)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java
index 56e893c..ee0bdff 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java
@@ -93,6 +93,12 @@
   @Implementation
   protected static long nativeSetup(@NonNull FileDescriptor fd, int format) throws IOException {
     FileOutputStream outputStream = fdToStream.get(fd);
+    if (outputStream == null) {
+      // If this MediaMuxer was constructed with the un-shadowed MediaMuxer(FileDescriptor, int), no
+      // FileOutputStream will be associated with the FileDescriptor, so just create one.
+      outputStream = new FileOutputStream(fd);
+      fdToStream.put(fd, outputStream);
+    }
 
     long potentialKey;
     do {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
index cddbd44..0359bc3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
@@ -364,7 +364,12 @@
     p.writeInt(realBitmap.getDensity());
     p.writeBoolean(realBitmap.isMutable());
     p.writeSerializable(realBitmap.getConfig());
-    p.writeString(realBitmap.getColorSpace().getName());
+    ColorSpace colorSpace = realBitmap.getColorSpace();
+    boolean hasColorSpace = colorSpace != null;
+    p.writeBoolean(hasColorSpace);
+    if (hasColorSpace) {
+      p.writeString(colorSpace.getName());
+    }
     p.writeBoolean(realBitmap.hasAlpha());
     int[] pixels = new int[width * height];
     realBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
@@ -378,20 +383,28 @@
     int density = p.readInt();
     boolean mutable = p.readBoolean();
     Bitmap.Config parceledConfig = (Bitmap.Config) p.readSerializable();
-    String colorSpaceName = p.readString();
-    boolean hasAlpha = p.readBoolean();
     ColorSpace colorSpace = null;
-    ColorSpace[] namedColorSpaces = reflector(ColorSpaceReflector.class).getNamedColorSpaces();
-    for (ColorSpace named : namedColorSpaces) {
-      if (named.getName().equals(colorSpaceName)) {
-        colorSpace = named;
-        break;
+    boolean hasColorSpace = p.readBoolean();
+    if (hasColorSpace) {
+      String colorSpaceName = p.readString();
+      ColorSpace[] namedColorSpaces = reflector(ColorSpaceReflector.class).getNamedColorSpaces();
+      for (ColorSpace named : namedColorSpaces) {
+        if (named.getName().equals(colorSpaceName)) {
+          colorSpace = named;
+          break;
+        }
       }
     }
+    boolean hasAlpha = p.readBoolean();
     int[] parceledColors = new int[parceledHeight * parceledWidth];
     p.readIntArray(parceledColors);
-    Bitmap bitmap =
-        Bitmap.createBitmap(parceledWidth, parceledHeight, parceledConfig, hasAlpha, colorSpace);
+    Bitmap bitmap;
+    if (colorSpace != null) {
+      bitmap =
+          Bitmap.createBitmap(parceledWidth, parceledHeight, parceledConfig, hasAlpha, colorSpace);
+    } else {
+      bitmap = Bitmap.createBitmap(parceledWidth, parceledHeight, parceledConfig, hasAlpha);
+    }
     bitmap.setPixels(parceledColors, 0, parceledWidth, 0, 0, parceledWidth, parceledHeight);
     bitmap.setDensity(density);
     if (!mutable) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
index 7b045e8..b2da827 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
@@ -57,7 +57,7 @@
       FontFamilyBuilderNatives.nAddFont(builderPtr, fontPtr);
     }
 
-    @Implementation(maxSdk=TIRAMISU)
+    @Implementation(maxSdk = TIRAMISU)
     protected static long nBuild(
         long builderPtr, String langTags, int variant, boolean isCustomFallback) {
       return FontFamilyBuilderNatives.nBuild(builderPtr, langTags, variant, isCustomFallback);
@@ -65,8 +65,12 @@
 
     @Implementation(minSdk = ShadowBuild.UPSIDE_DOWN_CAKE)
     protected static long nBuild(
-        long builderPtr, String langTags, int variant, boolean isCustomFallback, boolean isDefaultFallback) {
-      return nBuild(builderPtr, langTags, variant, isCustomFallback);
+        long builderPtr,
+        String langTags,
+        int variant,
+        boolean isCustomFallback,
+        boolean isDefaultFallback) {
+      return FontFamilyBuilderNatives.nBuild(builderPtr, langTags, variant, isCustomFallback);
     }
 
     @Implementation
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java
index d9a8f7d..f5e9b8e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java
@@ -5,6 +5,7 @@
 import android.animation.PropertyValuesHolder;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
 import org.robolectric.nativeruntime.PropertyValuesHolderNatives;
 import org.robolectric.shadows.ShadowNativePropertyValuesHolder.Picker;
 
@@ -12,6 +13,10 @@
 @Implements(value = PropertyValuesHolder.class, minSdk = O, shadowPicker = Picker.class)
 public class ShadowNativePropertyValuesHolder {
 
+  static {
+    DefaultNativeRuntimeLoader.injectAndLoad();
+  }
+
   @Implementation
   protected static long nGetIntMethod(Class<?> targetClass, String methodName) {
     return PropertyValuesHolderNatives.nGetIntMethod(targetClass, methodName);
@@ -79,7 +84,7 @@
   /** Shadow picker for {@link PropertyValuesHolder}. */
   public static final class Picker extends GraphicsShadowPicker<Object> {
     public Picker() {
-      super(null, ShadowNativePropertyValuesHolder.class);
+      super(ShadowPropertyValuesHolder.class, ShadowNativePropertyValuesHolder.class);
     }
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
index 3c9f578..7977df1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
@@ -1,6 +1,7 @@
 package org.robolectric.shadows;
 
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.GET_ACTIVITIES;
 import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
 import static android.content.pm.PackageManager.GET_GIDS;
@@ -25,6 +26,7 @@
 import static android.content.pm.PackageManager.SIGNATURE_NEITHER_SIGNED;
 import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
 import static android.content.pm.PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
+import static android.content.pm.PackageManager.VERIFICATION_ALLOW;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.KITKAT;
 import static android.os.Build.VERSION_CODES.N;
@@ -34,6 +36,8 @@
 
 import android.Manifest;
 import android.annotation.UserIdInt;
+import android.app.Application;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -143,7 +147,13 @@
   static final Map<String, Integer> uidForPackage = new HashMap<>();
   static final Map<Integer, String> namesForUid = new HashMap<>();
   static final Map<Integer, Integer> verificationResults = new HashMap<>();
+
+  @GuardedBy("lock")
   static final Map<Integer, Long> verificationTimeoutExtension = new HashMap<>();
+
+  @GuardedBy("lock")
+  static final Map<Integer, Integer> verificationCodeAtTimeoutExtension = new HashMap<>();
+
   static final Map<String, String> currentToCanonicalNames = new HashMap<>();
   static final Map<String, String> canonicalToCurrentNames = new HashMap<>();
   static final Map<ComponentName, ComponentState> componentList = new LinkedHashMap<>();
@@ -545,7 +555,6 @@
     private static PersistableBundle deepCopyNullablePersistableBundle(PersistableBundle bundle) {
       return bundle == null ? null : bundle.deepCopy();
     }
-
   }
 
   static final Map<String, PackageSetting> packageSettings = new HashMap<>();
@@ -1008,11 +1017,31 @@
   }
 
   public long getVerificationExtendedTimeout(int id) {
-    Long result = verificationTimeoutExtension.get(id);
-    if (result == null) {
-      return 0;
+    synchronized (lock) {
+      return verificationTimeoutExtension.getOrDefault(id, 0L);
     }
-    return result;
+  }
+
+  public int getVerificationCodeAtTimeoutExtension(int id) {
+    synchronized (lock) {
+      return verificationCodeAtTimeoutExtension.getOrDefault(id, VERIFICATION_ALLOW);
+    }
+  }
+
+  public void triggerInstallVerificationTimeout(Application appContext, int id) {
+    Intent intent = new Intent(Intent.ACTION_PACKAGE_VERIFIED);
+    intent.putExtra(EXTRA_VERIFICATION_ID, id);
+    intent.putExtra(
+        PackageManager.EXTRA_VERIFICATION_RESULT, getVerificationCodeAtTimeoutExtension(id));
+
+    // Send PACKAGE_VERIFIED broadcast to trigger the verification timeout.
+    // Replacement api does not return the actual receiver objects.
+    @SuppressWarnings("deprecation")
+    List<BroadcastReceiver> receivers =
+        ShadowApplication.getShadowInstrumentation().getReceiversForIntent(intent);
+    for (BroadcastReceiver receiver : receivers) {
+      receiver.onReceive(appContext, intent);
+    }
   }
 
   public void setShouldShowRequestPermissionRationale(String permission, boolean show) {
@@ -1694,6 +1723,7 @@
       namesForUid.clear();
       verificationResults.clear();
       verificationTimeoutExtension.clear();
+      verificationCodeAtTimeoutExtension.clear();
       currentToCanonicalNames.clear();
       canonicalToCurrentNames.clear();
       componentList.clear();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
index 8fec646..16a7398 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
@@ -112,6 +112,15 @@
   }
 
   @Implementation
+  protected void setStrikeThruText(boolean strikeThruText) {
+    if (strikeThruText) {
+      setFlags(flags | Paint.STRIKE_THRU_TEXT_FLAG);
+    } else {
+      setFlags(flags & ~Paint.STRIKE_THRU_TEXT_FLAG);
+    }
+  }
+
+  @Implementation
   protected Shader setShader(Shader shader) {
     this.shader = shader;
     return shader;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java
index 3f27850..d898b43 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java
@@ -1,5 +1,6 @@
 package org.robolectric.shadows;
 
+import static android.os.Build.VERSION_CODES.JELLY_BEAN;
 import static android.os.Build.VERSION_CODES.KITKAT;
 import static org.robolectric.shadow.api.Shadow.invokeConstructor;
 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
@@ -13,7 +14,6 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.lang.reflect.Constructor;
 import java.util.UUID;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
@@ -21,6 +21,7 @@
 import org.robolectric.annotation.RealObject;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
 import org.robolectric.util.reflector.Direct;
 import org.robolectric.util.reflector.ForType;
 
@@ -51,13 +52,15 @@
 
   @Implementation
   protected static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException {
-    ParcelFileDescriptor pfd;
-    try {
-      Constructor<ParcelFileDescriptor> constructor =
-          ParcelFileDescriptor.class.getDeclaredConstructor(FileDescriptor.class);
-      pfd = constructor.newInstance(new FileDescriptor());
-    } catch (Exception e) {
-      throw new RuntimeException(e);
+    ParcelFileDescriptor pfd = null;
+    if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) {
+      pfd = new ParcelFileDescriptor(new FileDescriptor());
+    } else {
+      // In Jelly Bean, the ParcelFileDescriptor(FileDescriptor) constructor was non-public.
+      pfd =
+          ReflectionHelpers.callConstructor(
+              ParcelFileDescriptor.class,
+              ClassParameter.from(FileDescriptor.class, new FileDescriptor()));
     }
     ShadowParcelFileDescriptor shadowParcelFileDescriptor = Shadow.extract(pfd);
     shadowParcelFileDescriptor.file = new RandomAccessFile(file, getFileMode(mode));
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java
index 8a0a949..8d1f042 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java
@@ -3,9 +3,12 @@
 import static org.robolectric.util.reflector.Reflector.reflector;
 
 import android.view.Choreographer;
+import android.view.DisplayEventReceiver;
+import androidx.annotation.VisibleForTesting;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.Resetter;
+import org.robolectric.shadows.ShadowDisplayEventReceiver.DisplayEventReceiverReflector;
 
 /**
  * A {@link Choreographer} shadow for {@link LooperMode.Mode.PAUSED}.
@@ -25,4 +28,16 @@
   public static void reset() {
     reflector(ChoreographerReflector.class).getThreadInstance().remove();
   }
+
+  /**
+   * Returns true if choreographer has been initialized properly.
+   *
+   * @return
+   */
+  @VisibleForTesting
+  boolean isInitialized() {
+    DisplayEventReceiver receiver =
+        reflector(ChoreographerReflector.class, realObject).getReceiver();
+    return reflector(DisplayEventReceiverReflector.class, receiver).getReceiverPtr() != 0;
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java
index 416033b..5caf016 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java
@@ -59,7 +59,16 @@
     invokeConstructor(MessageQueue.class, realQueue, from(boolean.class, quitAllowed));
     int ptr = (int) nativeQueueRegistry.register(this);
     reflector(MessageQueueReflector.class, realQueue).setPtr(ptr);
-    clockListener = () -> nativeWake(ptr);
+    clockListener =
+        () -> {
+          synchronized (realQueue) {
+            // only wake up the Looper thread if queue is non empty to reduce contention if many
+            // Looper threads are active
+            if (getMessages() != null) {
+              nativeWake(ptr);
+            }
+          }
+        };
     ShadowPausedSystemClock.addStaticListener(clockListener);
   }
 
@@ -307,7 +316,9 @@
         return Duration.ZERO;
       }
       while (next != null) {
-        when = shadowOfMsg(next).getWhen();
+        if (next.getTarget() != null) {
+          when = shadowOfMsg(next).getWhen();
+        }
         next = shadowOfMsg(next).internalGetNext();
       }
     }
@@ -333,7 +344,9 @@
     synchronized (realQueue) {
       Message next = getMessages();
       while (next != null) {
-        count++;
+        if (next.getTarget() != null) {
+          count++;
+        }
         next = shadowOfMsg(next).internalGetNext();
       }
     }
@@ -347,12 +360,24 @@
    */
   Message getNextIgnoringWhen() {
     synchronized (realQueue) {
-      Message head = getMessages();
-      if (head != null) {
-        Message next = shadowOfMsg(head).internalGetNext();
-        reflector(MessageQueueReflector.class, realQueue).setMessages(next);
+      Message prev = null;
+      Message msg = getMessages();
+      // Head is blocked on synchronization barrier, find next asynchronous message.
+      if (msg != null && msg.getTarget() == null) {
+        do {
+          prev = msg;
+          msg = shadowOfMsg(msg).internalGetNext();
+        } while (msg != null && !msg.isAsynchronous());
       }
-      return head;
+      if (msg != null) {
+        Message next = shadowOfMsg(msg).internalGetNext();
+        if (prev == null) {
+          reflector(MessageQueueReflector.class, realQueue).setMessages(next);
+        } else {
+          ReflectionHelpers.setField(prev, "next", next);
+        }
+      }
+      return msg;
     }
   }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java
index 8f031d8..61f3c3c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java
@@ -333,6 +333,18 @@
     return (flags & FLAG_IMMUTABLE) > 0;
   }
 
+  @Implementation
+  protected boolean isTargetedToPackage() {
+    // This is weird and we know it. See:
+    // https://googleplex-android.googlesource.com/platform/frameworks/base/+/f24a737c89de326199eb6d9f5912eae24b5514e6/services/core/java/com/android/server/am/ActivityManagerService.java#5377
+    for (Intent intent : savedIntents) {
+      if (intent.getPackage() != null && intent.getComponent() != null) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   /**
    * @return {@code true} iff sending this PendingIntent will start an activity
    * @deprecated prefer {@link #isActivity} which was added to {@link PendingIntent} in API 31
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java
new file mode 100644
index 0000000..cbac333
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java
@@ -0,0 +1,141 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.P;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.PixelCopy;
+import android.view.PixelCopy.OnPixelCopyFinishedListener;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * Shadow for PixelCopy that uses View.draw to create screenshots. The real PixelCopy performs a
+ * full hardware capture of the screen at the given location, which is impossible in Robolectric.
+ *
+ * <p>If listenerThread is backed by a paused looper, make sure to call ShadowLooper.idle() to
+ * ensure the screenshot finishes.
+ */
+@Implements(value = PixelCopy.class, minSdk = P)
+public class ShadowPixelCopy {
+  @Implementation
+  protected static void request(
+      @NonNull SurfaceView source,
+      @Nullable Rect srcRect,
+      @NonNull Bitmap dest,
+      @NonNull OnPixelCopyFinishedListener listener,
+      @NonNull Handler listenerThread) {
+    Activity activity = getActivity(source);
+    if (srcRect != null && srcRect.isEmpty()) {
+      throw new IllegalArgumentException("sourceRect is empty");
+    }
+    if (activity == null) {
+      throw new IllegalArgumentException("SourceView was not attached to an activity");
+    }
+    takeScreenshot(activity.getWindow(), dest, srcRect);
+    alertFinished(listener, listenerThread, PixelCopy.SUCCESS);
+  }
+
+  @Implementation
+  protected static void request(
+      @NonNull Window source,
+      @NonNull Bitmap dest,
+      @NonNull OnPixelCopyFinishedListener listener,
+      @NonNull Handler listenerThread) {
+    takeScreenshot(source, dest, null);
+    alertFinished(listener, listenerThread, PixelCopy.SUCCESS);
+  }
+
+  @Implementation
+  protected static void request(
+      @NonNull Window source,
+      @Nullable Rect srcRect,
+      @NonNull Bitmap dest,
+      @NonNull OnPixelCopyFinishedListener listener,
+      @NonNull Handler listenerThread) {
+    if (srcRect != null && srcRect.isEmpty()) {
+      throw new IllegalArgumentException("sourceRect is empty");
+    }
+    takeScreenshot(source, dest, srcRect);
+    alertFinished(listener, listenerThread, PixelCopy.SUCCESS);
+  }
+
+  private static void takeScreenshot(Window window, Bitmap screenshot, @Nullable Rect srcRect) {
+    validateBitmap(screenshot);
+
+    // Draw the view to a bitmap in the canvas that is the size of the view itself.
+    View decorView = window.getDecorView();
+    Bitmap bitmap =
+        Bitmap.createBitmap(decorView.getWidth(), decorView.getHeight(), Bitmap.Config.ARGB_8888);
+    Canvas screenshotCanvas = new Canvas(bitmap);
+    decorView.draw(screenshotCanvas);
+
+    Rect dst = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
+
+    Canvas resizingCanvas = new Canvas(screenshot);
+    Paint paint = new Paint();
+    resizingCanvas.drawBitmap(bitmap, srcRect, dst, paint);
+  }
+
+  private static void alertFinished(
+      OnPixelCopyFinishedListener listener, Handler listenerThread, int result) {
+    if (listenerThread.getLooper() == Looper.getMainLooper()) {
+      listener.onPixelCopyFinished(result);
+      return;
+    }
+    listenerThread.post(() -> listener.onPixelCopyFinished(result));
+  }
+
+  private static void validateBitmap(Bitmap bitmap) {
+    if (bitmap == null) {
+      throw new IllegalArgumentException("Bitmap cannot be null");
+    }
+    if (bitmap.isRecycled()) {
+      throw new IllegalArgumentException("Bitmap is recycled");
+    }
+    if (!bitmap.isMutable()) {
+      throw new IllegalArgumentException("Bitmap is immutable");
+    }
+  }
+
+  private static Activity getActivity(Context context) {
+    if (context instanceof Activity) {
+      return (Activity) context;
+    } else if (context instanceof ContextWrapper) {
+      return getActivity(((ContextWrapper) context).getBaseContext());
+    } else {
+      return null;
+    }
+  }
+
+  private static Activity getActivity(View view) {
+    Activity activity = getActivity(view.getContext());
+    if (activity != null) {
+      return activity;
+    }
+
+    if (view instanceof ViewGroup) {
+      ViewGroup viewGroup = (ViewGroup) view;
+      if (viewGroup.getChildCount() > 0) {
+        // getActivity is known to fail if View is a DecorView such as specified via espresso's
+        // isRoot().
+        // Make another attempt to find the activity from its first child view
+        return getActivity(viewGroup.getChildAt(0).getContext());
+      }
+    }
+    return null;
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPropertyValuesHolder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPropertyValuesHolder.java
new file mode 100644
index 0000000..1e52981
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPropertyValuesHolder.java
@@ -0,0 +1,50 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.animation.PropertyValuesHolder;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+
+/**
+ * Shadow for {@link PropertyValuesHolder} that works around the ART/JVM differences of accessing
+ * methods.
+ */
+@Implements(value = PropertyValuesHolder.class, isInAndroidSdk = false)
+public class ShadowPropertyValuesHolder {
+
+  @RealObject PropertyValuesHolder realPropertyValuesHolder;
+
+  @Implementation
+  protected Method setupSetterOrGetter(
+      Class<?> targetClass,
+      HashMap<Class<?>, HashMap<String, Method>> propertyMapMap,
+      String prefix,
+      Class<?> valueType) {
+    Method result =
+        reflector(PropertyValuesHolderReflector.class, realPropertyValuesHolder)
+            .setupSetterOrGetter(targetClass, propertyMapMap, prefix, valueType);
+    if (result != null && Modifier.isPublic(result.getModifiers())) {
+      // ART allows calling public methods on private or package-private classes, but the
+      // JVM does not. Calling setAccessible(true) is required.
+      result.setAccessible(true);
+    }
+    return result;
+  }
+
+  @ForType(PropertyValuesHolder.class)
+  interface PropertyValuesHolderReflector {
+    @Direct
+    Method setupSetterOrGetter(
+        Class<?> targetClass,
+        HashMap<Class<?>, HashMap<String, Method>> propertyMapMap,
+        String prefix,
+        Class<?> valueType);
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java
index a96c00e..d7126d2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java
@@ -16,7 +16,7 @@
 
   @Implementation
   protected void smoothScrollTo(int x, int y) {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       reflector(ScrollViewReflector.class, realScrollView).smoothScrollTo(x, y);
     } else {
       scrollTo(x, y);
@@ -25,7 +25,7 @@
 
   @Implementation
   protected void smoothScrollBy(int x, int y) {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       reflector(ScrollViewReflector.class, realScrollView).smoothScrollBy(x, y);
     } else {
       scrollBy(x, y);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
index cbf52ae..2785e11 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
@@ -34,6 +34,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothManager;
+import android.companion.ICompanionDeviceManager;
 import android.content.Context;
 import android.content.IClipboard;
 import android.content.IRestrictionsManager;
@@ -88,6 +89,7 @@
 import android.view.translation.ITranslationManager;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.os.IDropBoxManagerService;
 import com.android.internal.statusbar.IStatusBar;
@@ -167,6 +169,7 @@
     }
     if (RuntimeEnvironment.getApiLevel() >= N) {
       addBinderService(Context.CONTEXTHUB_SERVICE, IContextHubService.class);
+      addBinderService(Context.SOUND_TRIGGER_SERVICE, ISoundTriggerService.class);
     }
     if (RuntimeEnvironment.getApiLevel() >= N_MR1) {
       addBinderService(Context.SHORTCUT_SERVICE, IShortcutService.class);
@@ -175,6 +178,7 @@
       addBinderService("mount", IStorageManager.class);
       addBinderService(Context.WIFI_AWARE_SERVICE, IWifiAwareManager.class);
       addBinderService(Context.STORAGE_STATS_SERVICE, IStorageStatsManager.class);
+      addBinderService(Context.COMPANION_DEVICE_SERVICE, ICompanionDeviceManager.class);
     } else {
       addBinderService("mount", "android.os.storage.IMountService");
     }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSoundTriggerManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSoundTriggerManager.java
new file mode 100644
index 0000000..8983213
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSoundTriggerManager.java
@@ -0,0 +1,36 @@
+package org.robolectric.shadows;
+
+import android.annotation.RequiresPermission;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Build.VERSION_CODES;
+import javax.annotation.Nullable;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** A Shadow SoundTriggerManager in Android O+. */
+@Implements(
+    className = "android.media.soundtrigger.SoundTriggerManager",
+    minSdk = VERSION_CODES.N,
+    isInAndroidSdk = false)
+public final class ShadowSoundTriggerManager {
+  private SoundTrigger.ModuleProperties moduleProperties = null;
+
+  /**
+   * Set {@code SoundTrigger.ModuleProperties}, value will returned for the following {@code
+   * getModuleProperties} call.
+   */
+  public void setModuleProperties(@Nullable SoundTrigger.ModuleProperties moduleProperties) {
+    this.moduleProperties = moduleProperties;
+  }
+
+  @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+  @Implementation(minSdk = VERSION_CODES.R)
+  protected SoundTrigger.ModuleProperties getModuleProperties() {
+    if (RuntimeEnvironment.getApiLevel() == VERSION_CODES.R && moduleProperties == null) {
+      throw new NullPointerException(
+          "Throw NullPointException in Android R when moduleProperties is null.");
+    }
+    return moduleProperties;
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
index fb5c1c2..1b239d1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
@@ -21,6 +21,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import org.robolectric.annotation.HiddenApi;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
@@ -282,6 +283,17 @@
   }
 
   /**
+   * Adds a listener to a local list of listeners. Will be triggered by {@link
+   * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated.
+   */
+  @Implementation(minSdk = R)
+  protected void addOnSubscriptionsChangedListener(
+      Executor executor, OnSubscriptionsChangedListener listener) {
+    listeners.add(listener);
+    listener.onSubscriptionsChanged();
+  }
+
+  /**
    * Removes a listener from a local list of listeners. Will be triggered by {@link
    * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated.
    */
@@ -290,6 +302,16 @@
     listeners.remove(listener);
   }
 
+  /**
+   * Check if a listener exists in the {@link ShadowSubscriptionManager.listeners}.
+   *
+   * @param listener The listener to check.
+   * @return boolean True if the listener already added, otherwise false.
+   */
+  public boolean hasOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
+    return listeners.contains(listener);
+  }
+
   /** Returns subscription Ids that were set via {@link #setActiveSubscriptionInfoList}. */
   @Implementation(minSdk = LOLLIPOP_MR1)
   @HiddenApi
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java
index 63d12e6..49a93bc 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java
@@ -84,11 +84,7 @@
   void initializeNativeObject() {
     surfaceControlReflector.setNativeObject(nativeObject.incrementAndGet());
     if (RuntimeEnvironment.getApiLevel() >= ShadowBuild.UPSIDE_DOWN_CAKE) {
-      try {
-        surfaceControlReflector.setFreeNativeResources(() -> {});
-      } catch(Exception e) {
-        // tm branches not yet have mFreeNativeResources added while in partial U state
-      }
+      surfaceControlReflector.setFreeNativeResources(() -> {});
     }
   }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemServiceRegistry.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemServiceRegistry.java
index f8e7bc6..9ab967e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemServiceRegistry.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemServiceRegistry.java
@@ -54,7 +54,7 @@
     static _ServiceFetcher_ get(String key, Object serviceFetcher) {
       String serviceFetcherClassName = getConcreteClassName(serviceFetcher);
       if (serviceFetcherClassName == null) {
-        throw new IllegalStateException("no idea what to do with " + key + " " + serviceFetcher);
+        throw new IllegalStateException("could not find class name for serviceFetcher " + key);
       }
 
       switch (serviceFetcherClassName) {
@@ -69,8 +69,15 @@
         default:
           if (key.equals(Context.INPUT_METHOD_SERVICE)) {
             return o -> {}; // handled by ShadowInputMethodManager.reset()
+          } else if (key.equals(Context.INPUT_SERVICE)) {
+            return o -> {}; // handled by ShadowInputManager.reset()
           }
-          throw new IllegalStateException("no idea what to do with " + key + " " + serviceFetcher);
+          throw new IllegalStateException(
+              "did not recognize serviceFetcher class name "
+                  + serviceFetcherClassName
+                  + " for key '"
+                  + key
+                  + "'");
       }
     }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
index cce1990..41fbf4e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
@@ -15,9 +15,9 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemVibrator;
-import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.vibrator.VibrationEffectSegment;
+import com.google.common.base.Preconditions;
 import java.util.List;
 import java.util.Optional;
 import org.robolectric.RuntimeEnvironment;
@@ -25,7 +25,8 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.util.ReflectionHelpers;
 
-@Implements(value = SystemVibrator.class, isInAndroidSdk = false)
+/** Shadow for {@link SystemVibrator}. */
+@Implements(value = SystemVibrator.class, isInAndroidSdk = false, looseSignatures = true)
 public class ShadowSystemVibrator extends ShadowVibrator {
 
   private final Handler handler = new Handler(Looper.getMainLooper());
@@ -133,11 +134,14 @@
 
   @Implementation(minSdk = S)
   protected void vibrate(
-      int uid,
-      String opPkg,
-      VibrationEffect effect,
-      String reason,
-      VibrationAttributes attributes) {
+      Object uid, Object opPkg, Object effect, Object reason, Object attributes) {
+    Preconditions.checkArgument(uid instanceof Integer);
+    Preconditions.checkArgument(opPkg == null || opPkg instanceof String);
+    // The SystemVibrator#vibrate needs effect NonNull.
+    Preconditions.checkArgument(effect instanceof VibrationEffect);
+    Preconditions.checkArgument(reason == null || reason instanceof String);
+    // The SystemVibrator#vibrate needs attributes NonNull.
+    Preconditions.checkArgument(attributes instanceof android.os.VibrationAttributes);
     if (effect instanceof VibrationEffect.Composed) {
       VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
       vibrationAttributesFromLastVibration = attributes;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
index 3fd351f..ef52021 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
@@ -6,6 +6,7 @@
 import static android.os.Build.VERSION_CODES.N;
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Build.VERSION_CODES.R;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Verify.verifyNotNull;
 
 import android.annotation.SystemApi;
@@ -615,6 +616,7 @@
   @Implementation(minSdk = M)
   @HiddenApi
   public void enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled) {
+    checkNotNull(getPhoneAccount(handle)).setIsEnabled(isEnabled);
   }
 
   /**
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
index 0ab3730..048abf0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
@@ -52,6 +52,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.CellInfoCallback;
 import android.telephony.VisualVoicemailSmsFilterSettings;
+import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -59,6 +60,7 @@
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -155,6 +157,7 @@
   private VisualVoicemailSmsParams lastVisualVoicemailSmsParams;
   private VisualVoicemailSmsFilterSettings visualVoicemailSmsFilterSettings;
   private boolean emergencyCallbackMode;
+  private static Map<Integer, List<EmergencyNumber>> emergencyNumbersList;
 
   /**
    * Should be {@link TelephonyManager.BootstrapAuthenticationCallback} but this object was
@@ -172,6 +175,7 @@
   @Resetter
   public static void reset() {
     callComposerStatus = 0;
+    emergencyNumbersList = null;
   }
 
   @Implementation(minSdk = S)
@@ -1411,4 +1415,25 @@
       return sentIntent;
     }
   }
+
+  /**
+   * Sets the emergency numbers list returned by {@link TelephonyManager#getEmergencyNumberList}.
+   */
+  public static void setEmergencyNumberList(
+      Map<Integer, List<EmergencyNumber>> emergencyNumbersList) {
+    ShadowTelephonyManager.emergencyNumbersList = emergencyNumbersList;
+  }
+
+  /**
+   * Implementation for {@link TelephonyManager#getEmergencyNumberList}.
+   *
+   * @return an immutable map by default, unless set with {@link #setEmergencyNumberList}.
+   */
+  @Implementation(minSdk = R)
+  protected Map<Integer, List<EmergencyNumber>> getEmergencyNumberList() {
+    if (ShadowTelephonyManager.emergencyNumbersList != null) {
+      return ShadowTelephonyManager.emergencyNumbersList;
+    }
+    return ImmutableMap.of();
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimeManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimeManager.java
index 7a77328..56b7eb0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimeManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimeManager.java
@@ -1,13 +1,19 @@
 package org.robolectric.shadows;
 
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+
 import android.annotation.SystemApi;
 import android.app.time.Capabilities;
 import android.app.time.Capabilities.CapabilityState;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
 import android.app.time.TimeManager;
 import android.app.time.TimeZoneCapabilities;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
 import android.os.Build.VERSION_CODES;
 import android.os.UserHandle;
 import java.util.Objects;
@@ -25,10 +31,19 @@
   private TimeZoneCapabilities timeZoneCapabilities =
       new TimeZoneCapabilities.Builder(UserHandle.CURRENT)
           .setConfigureAutoDetectionEnabledCapability(Capabilities.CAPABILITY_POSSESSED)
+          .setUseLocationEnabled(true)
           .setConfigureGeoDetectionEnabledCapability(Capabilities.CAPABILITY_POSSESSED)
-          .setSuggestManualTimeZoneCapability(Capabilities.CAPABILITY_POSSESSED)
+          .setSetManualTimeZoneCapability(Capabilities.CAPABILITY_POSSESSED)
           .build();
 
+  private TimeZoneDetectorStatus detectorStatus =
+          new TimeZoneDetectorStatus(
+                  DETECTOR_STATUS_RUNNING,
+                  new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING),
+                  new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+                          LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+                          LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null));
+
   private TimeZoneConfiguration timeZoneConfiguration;
 
   /**
@@ -54,7 +69,8 @@
   protected TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() {
     Objects.requireNonNull(timeZoneConfiguration, "timeZoneConfiguration was not set");
 
-    return new TimeZoneCapabilitiesAndConfig(timeZoneCapabilities, timeZoneConfiguration);
+    return new TimeZoneCapabilitiesAndConfig(
+            detectorStatus, timeZoneCapabilities, timeZoneConfiguration);
   }
 
   @Implementation
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
index 00ad658..4eb7f18 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
@@ -19,6 +19,7 @@
 import static org.robolectric.util.reflector.Reflector.reflector;
 
 import android.Manifest.permission;
+import android.accounts.Account;
 import android.annotation.UserIdInt;
 import android.app.Application;
 import android.content.Context;
@@ -39,6 +40,7 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -81,6 +83,7 @@
   @RealObject private UserManager realObject;
   private UserManagerState userManagerState;
   private Boolean managedProfile;
+  private Boolean cloneProfile;
   private boolean userUnlocked = true;
   private boolean isSystemUser = true;
 
@@ -97,6 +100,8 @@
   private boolean enforcePermissions;
   private int userSwitchability = UserManager.SWITCHABILITY_STATUS_OK;
 
+  private final Set<Account> userAccounts = new HashSet<>();
+
   /**
    * Global UserManager state. Shared across {@link UserManager}s created in different {@link
    * Context}s.
@@ -122,10 +127,13 @@
 
     private int nextUserId = DEFAULT_SECONDARY_USER_ID;
 
+    // TODO: use UserInfo.FLAG_MAIN when available
+    private static final int FLAG_MAIN = 0x00004000;
+
     public UserManagerState() {
       int id = UserHandle.USER_SYSTEM;
       String name = "system_user";
-      int flags = UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN;
+      int flags = UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN | FLAG_MAIN;
 
       userSerialNumbers.put(id, (long) id);
       // Start the user as shut down.
@@ -298,15 +306,20 @@
     }
   }
 
-  /** Add a profile to be returned by {@link #getProfiles(int)}.* */
+  /** Add a profile to be returned by {@link #getProfiles(int)}. */
   public void addProfile(
       int userHandle, int profileUserHandle, String profileName, int profileFlags) {
+    UserInfo profileUserInfo = new UserInfo(profileUserHandle, profileName, profileFlags);
+    addProfile(userHandle, profileUserHandle, profileUserInfo);
+  }
+
+  /** Add a profile to be returned by {@link #getProfiles(int)}. */
+  public void addProfile(int userHandle, int profileUserHandle, UserInfo profileUserInfo) {
     // Don't override serial number set by setSerialNumberForUser()
     if (!userManagerState.userSerialNumbers.containsKey(profileUserHandle)) {
       // use UserHandle id as serial number unless setSerialNumberForUser() is used
       userManagerState.userSerialNumbers.put(profileUserHandle, (long) profileUserHandle);
     }
-    UserInfo profileUserInfo = new UserInfo(profileUserHandle, profileName, profileFlags);
     if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) {
       profileUserInfo.profileGroupId = userHandle;
       UserInfo parentUserInfo = getUserInfo(userHandle);
@@ -401,6 +414,34 @@
     this.managedProfile = managedProfile;
   }
 
+  /**
+   * If permissions are enforced (see {@link #enforcePermissionChecks(boolean)}) and the application
+   * doesn't have the {@link android.Manifest.permission#MANAGE_USERS} permission, throws a {@link
+   * SecurityManager} exception.
+   *
+   * @return true if the user is clone, or the value specified via {@link #setCloneProfile(boolean)}
+   * @see #enforcePermissionChecks(boolean)
+   * @see #setCloneProfile(boolean)
+   */
+  @Implementation(minSdk = S)
+  protected boolean isCloneProfile() {
+    if (enforcePermissions && !hasManageUsersPermission()) {
+      throw new SecurityException("You need MANAGE_USERS permission to: check isCloneProfile");
+    }
+
+    if (cloneProfile != null) {
+      return cloneProfile;
+    }
+
+    UserInfo info = getUserInfo(context.getUserId());
+    return info != null && info.isCloneProfile();
+  }
+
+  /** Setter for {@link UserManager#isCloneProfile()}. */
+  public void setCloneProfile(boolean cloneProfile) {
+    this.cloneProfile = cloneProfile;
+  }
+
   @Implementation(minSdk = R)
   protected boolean isProfile() {
     if (enforcePermissions && !hasManageUsersPermission()) {
@@ -1195,4 +1236,19 @@
     @Accessor("mUserId")
     void setUserId(int userId);
   }
+
+  @Implementation(minSdk = TIRAMISU)
+  protected boolean someUserHasAccount(String accountName, String accountType) {
+    return userAccounts.contains(new Account(accountName, accountType));
+  }
+
+  /** Setter for {@link UserManager#someUserHasAccount(String, String)}. */
+  public void setSomeUserHasAccount(String accountName, String accountType) {
+    userAccounts.add(new Account(accountName, accountType));
+  }
+
+  /** Removes user account set via {@link #setSomeUserHasAccount(String, String)}. */
+  public void removeSomeUserHasAccount(String accountName, String accountType) {
+    userAccounts.remove(new Account(accountName, accountType));
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java
index b66a0a4..276a31d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java
@@ -3,18 +3,19 @@
 import static android.os.Build.VERSION_CODES.R;
 
 import android.media.AudioAttributes;
-import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.os.vibrator.VibrationEffectSegment;
+import android.os.vibrator.PrimitiveSegment;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
+import org.robolectric.util.ReflectionHelpers;
 
 @Implements(Vibrator.class)
 public class ShadowVibrator {
@@ -22,10 +23,10 @@
   static boolean cancelled;
   static long milliseconds;
   protected static long[] pattern;
-  protected static final List<VibrationEffectSegment> vibrationEffectSegments = new ArrayList<>();
+  protected static final List<Object> vibrationEffectSegments = new ArrayList<>();
   protected static final List<PrimitiveEffect> primitiveEffects = new ArrayList<>();
   protected static final List<Integer> supportedPrimitives = new ArrayList<>();
-  @Nullable protected static VibrationAttributes vibrationAttributesFromLastVibration;
+  @Nullable protected static Object vibrationAttributesFromLastVibration;
   @Nullable protected static AudioAttributes audioAttributesFromLastVibration;
   static int repeat;
   static boolean hasVibrator = true;
@@ -81,9 +82,18 @@
     return repeat;
   }
 
-  /** Returns the last list of {@link VibrationEffectSegment}. */
-  public List<VibrationEffectSegment> getVibrationEffectSegments() {
-    return vibrationEffectSegments;
+  /** Returns the last list of {@link PrimitiveSegment} vibrations in {@link PrimitiveEffect}. */
+  @SuppressWarnings("JdkCollectors") // toImmutableList is only supported in Java 8+.
+  public List<PrimitiveEffect> getPrimitiveSegmentsInPrimitiveEffects() {
+    return vibrationEffectSegments.stream()
+        .filter(segment -> segment instanceof PrimitiveSegment)
+        .map(
+            segment ->
+                new PrimitiveEffect(
+                    ReflectionHelpers.getField(segment, "mPrimitiveId"),
+                    ReflectionHelpers.getField(segment, "mScale"),
+                    ReflectionHelpers.getField(segment, "mDelay")))
+        .collect(Collectors.toList());
   }
 
   /** Returns the last list of {@link PrimitiveEffect}. */
@@ -108,9 +118,9 @@
     supportedPrimitives.addAll(primitives);
   }
 
-  /** Returns the {@link VibrationAttributes} from the last vibration. */
+  /** Returns the {@link android.os.VibrationAttributes} from the last vibration. */
   @Nullable
-  public VibrationAttributes getVibrationAttributesFromLastVibration() {
+  public Object getVibrationAttributesFromLastVibration() {
     return vibrationAttributesFromLastVibration;
   }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java
index 5d06f58..5d4f329 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java
@@ -36,6 +36,7 @@
 import android.view.WindowManager;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
+import com.google.common.annotations.Beta;
 import com.google.common.collect.ImmutableList;
 import java.io.PrintStream;
 import java.util.ArrayList;
@@ -44,12 +45,15 @@
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.GraphicsMode;
+import org.robolectric.annotation.GraphicsMode.Mode;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.RealObject;
 import org.robolectric.annotation.ReflectorObject;
 import org.robolectric.annotation.Resetter;
+import org.robolectric.config.ConfigurationRegistry;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.ReflectionHelpers.ClassParameter;
 import org.robolectric.util.TimeUtils;
@@ -575,7 +579,7 @@
 
   @Implementation
   protected void scrollTo(int x, int y) {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       reflector(_View_.class, realView).scrollTo(x, y);
     } else {
       reflector(_View_.class, realView)
@@ -588,7 +592,7 @@
 
   @Implementation
   protected void scrollBy(int x, int y) {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       reflector(_View_.class, realView).scrollBy(x, y);
     } else {
       scrollTo(getScrollX() + x, getScrollY() + y);
@@ -597,7 +601,7 @@
 
   @Implementation
   protected int getScrollX() {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       return reflector(_View_.class, realView).getScrollX();
     } else {
       return scrollToCoordinates != null ? scrollToCoordinates.x : 0;
@@ -606,7 +610,7 @@
 
   @Implementation
   protected int getScrollY() {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       return reflector(_View_.class, realView).getScrollY();
     } else {
       return scrollToCoordinates != null ? scrollToCoordinates.y : 0;
@@ -615,7 +619,7 @@
 
   @Implementation
   protected void setScrollX(int scrollX) {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       reflector(_View_.class, realView).setScrollX(scrollX);
     } else {
       scrollTo(scrollX, scrollToCoordinates.y);
@@ -624,7 +628,7 @@
 
   @Implementation
   protected void setScrollY(int scrollY) {
-    if (useRealGraphics()) {
+    if (useRealScrolling()) {
       reflector(_View_.class, realView).setScrollY(scrollY);
     } else {
       scrollTo(scrollToCoordinates.x, scrollY);
@@ -1054,7 +1058,26 @@
     void setWindowId(WindowId windowId);
   }
 
-  static boolean useRealGraphics() {
-    return Boolean.getBoolean("robolectric.nativeruntime.enableGraphics");
+  /**
+   * Internal API to determine if native graphics is enabled.
+   *
+   * <p>This is currently public because it has to be accessed from multiple packages, but it is not
+   * recommended to depend on this API.
+   */
+  @Beta
+  public static boolean useRealGraphics() {
+    GraphicsMode.Mode graphicsMode = ConfigurationRegistry.get(GraphicsMode.Mode.class);
+    return graphicsMode == Mode.NATIVE && RuntimeEnvironment.getApiLevel() >= O;
+  }
+
+  /**
+   * Currently the default View scrolling implementation is broken and low-fidelty. For instance,
+   * even if a View has no children, Robolectric will still happily set the scroll position of a
+   * View. Long-term we want to eliminate this broken behavior, but in the mean time the real
+   * scrolling behavior is enabled when native graphics are enabled, or when a system property is
+   * set.
+   */
+  static boolean useRealScrolling() {
+    return useRealGraphics() || Boolean.getBoolean("robolectric.useRealScrolling");
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
index cfe2bc7..2dd2e9a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
@@ -123,7 +123,30 @@
   }
 
   public void callDispatchResized() {
-    if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.S_V2) {
+    if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) {
+      Display display = getDisplay();
+      Rect frame = new Rect();
+      display.getRectSize(frame);
+
+      ClientWindowFrames frames = new ClientWindowFrames();
+      // set the final field
+      ReflectionHelpers.setField(frames, "frame", frame);
+
+      ReflectionHelpers.callInstanceMethod(
+          ViewRootImpl.class,
+          realObject,
+          "dispatchResized",
+          ClassParameter.from(ClientWindowFrames.class, frames),
+          ClassParameter.from(boolean.class, true), /* reportDraw */
+          ClassParameter.from(
+              MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */
+          ClassParameter.from(InsetsState.class, new InsetsState()), /* insetsState */
+          ClassParameter.from(boolean.class, false), /* forceLayout */
+          ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */
+          ClassParameter.from(int.class, 0), /* displayId */
+          ClassParameter.from(int.class, 0), /* syncSeqId */
+          ClassParameter.from(boolean.class, false) /* dragResizing */);
+    } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.S_V2) {
       Display display = getDisplay();
       Rect frame = new Rect();
       display.getRectSize(frame);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java
index 706d86e..1a8131f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java
@@ -569,7 +569,7 @@
    *
    * @param canGoBack Value to return from {@code android.webkit.WebView#canGoBack()}
    * @deprecated Do not depend on this method as it will be removed in a future update. The
-   *     preferered method is to populate a fake web history to use for going back.
+   *     preferred method is to populate a fake web history to use for going back.
    */
   @Deprecated
   public void setCanGoBack(boolean canGoBack) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
index 7221e69..cc6cfcc 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
@@ -9,6 +9,7 @@
 import static android.os.Build.VERSION_CODES.TIRAMISU;
 import static java.util.stream.Collectors.toList;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
@@ -19,6 +20,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.AddNetworkResult;
 import android.net.wifi.WifiManager.MulticastLock;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WifiUsabilityStatsEntry;
@@ -58,6 +60,7 @@
 
   private static float sSignalLevelInPercent = 1f;
   private boolean accessWifiStatePermission = true;
+  private boolean changeWifiStatePermission = true;
   private int wifiState = WifiManager.WIFI_STATE_ENABLED;
   private boolean wasSaved = false;
   private WifiInfo wifiInfo;
@@ -151,6 +154,19 @@
     return scanResults;
   }
 
+  /**
+   * The original implementation allows this to be called by the Device Owner (DO), Profile Owner
+   * (PO), callers with carrier privilege and system apps, but this shadow can be called by all apps
+   * carrying the ACCESS_WIFI_STATE permission.
+   *
+   * <p>This shadow is a wrapper for getConfiguredNetworks() and does not actually check the caller.
+   */
+  @Implementation(minSdk = S)
+  protected List<WifiConfiguration> getCallerConfiguredNetworks() {
+    checkAccessWifiStatePermission();
+    return getConfiguredNetworks();
+  }
+
   @Implementation
   protected List<WifiConfiguration> getConfiguredNetworks() {
     final ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
@@ -176,6 +192,21 @@
     return networkId;
   }
 
+  /**
+   * The new version of {@link #addNetwork(WifiConfiguration)} which returns a more detailed failure
+   * codes. The original implementation of this API is limited to Device Owner (DO), Profile Owner
+   * (PO), system app, and privileged apps but this shadow can be called by all apps.
+   */
+  @Implementation(minSdk = S)
+  protected AddNetworkResult addNetworkPrivileged(WifiConfiguration config) {
+    if (config == null) {
+      throw new IllegalArgumentException("config cannot be null");
+    }
+
+    int networkId = addNetwork(config);
+    return new AddNetworkResult(AddNetworkResult.STATUS_SUCCESS, networkId);
+  }
+
   @Implementation
   protected boolean removeNetwork(int netId) {
     networkIdToConfiguredNetworks.remove(netId);
@@ -183,6 +214,21 @@
   }
 
   /**
+   * Removes all configured networks regardless of the app that created the network. Can only be
+   * called by a Device Owner (DO) app.
+   *
+   * @return {@code true} if at least one network is removed, {@code false} otherwise
+   */
+  @Implementation(minSdk = S)
+  protected boolean removeNonCallerConfiguredNetworks() {
+    checkChangeWifiStatePermission();
+    checkDeviceOwner();
+    int previousSize = networkIdToConfiguredNetworks.size();
+    networkIdToConfiguredNetworks.clear();
+    return networkIdToConfiguredNetworks.size() < previousSize;
+  }
+
+  /**
    * Adds or updates a network which can later be retrieved with {@link #getWifiConfiguration(int)}
    * method. A null {@param config}, or one with a networkId less than 0, or a networkId that had
    * its updatePermission removed using the {@link #setUpdateNetworkPermission(int, boolean)} will
@@ -365,6 +411,10 @@
     this.accessWifiStatePermission = accessWifiStatePermission;
   }
 
+  public void setChangeWifiStatePermission(boolean changeWifiStatePermission) {
+    this.changeWifiStatePermission = changeWifiStatePermission;
+  }
+
   /**
    * Prevents a networkId from being updated using the {@link updateNetwork(WifiConfiguration)}
    * method. This is to simulate the case where a separate application creates a network, and the
@@ -409,7 +459,21 @@
 
   private void checkAccessWifiStatePermission() {
     if (!accessWifiStatePermission) {
-      throw new SecurityException();
+      throw new SecurityException("Caller does not hold ACCESS_WIFI_STATE permission");
+    }
+  }
+
+  private void checkChangeWifiStatePermission() {
+    if (!changeWifiStatePermission) {
+      throw new SecurityException("Caller does not hold CHANGE_WIFI_STATE permission");
+    }
+  }
+
+  private void checkDeviceOwner() {
+    if (!getContext()
+        .getSystemService(DevicePolicyManager.class)
+        .isDeviceOwnerApp(getContext().getPackageName())) {
+      throw new SecurityException("Caller is not device owner");
     }
   }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java b/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java
index 23dc71c..ee6b903 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java
@@ -19,10 +19,12 @@
 
     if (apiLevel >= VERSION_CODES.O) {
       features.put(PackageManager.FEATURE_WIFI_AWARE, true);
+      features.put(PackageManager.FEATURE_COMPANION_DEVICE_SETUP, true);
     }
 
     if (apiLevel >= VERSION_CODES.P) {
       features.put(PackageManager.FEATURE_WIFI_DIRECT, true);
+      features.put(PackageManager.FEATURE_WIFI_RTT, true);
     }
 
     return ImmutableMap.copyOf(features);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/WifiUsabilityStatsEntryBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/WifiUsabilityStatsEntryBuilder.java
index fc63fc9..4c38710 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/WifiUsabilityStatsEntryBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/WifiUsabilityStatsEntryBuilder.java
@@ -2,6 +2,7 @@
 
 import android.net.wifi.WifiUsabilityStatsEntry;
 import android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats;
+import android.net.wifi.WifiUsabilityStatsEntry.LinkStats;
 import android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
 import android.net.wifi.WifiUsabilityStatsEntry.RateStats;
 import android.os.Build.VERSION_CODES;
@@ -152,7 +153,9 @@
           cellularDataNetworkType,
           cellularSignalStrengthDbm,
           cellularSignalStrengthDb,
-          isSameRegisteredCell);
+          isSameRegisteredCell,
+          new SparseArray<LinkStats>());
+
     }
   }
 
diff --git a/shadows/httpclient/build.gradle b/shadows/httpclient/build.gradle
index 332c83e..4e77a09 100644
--- a/shadows/httpclient/build.gradle
+++ b/shadows/httpclient/build.gradle
@@ -21,17 +21,17 @@
     api project(":utils")
 
     // We should keep httpclient version for low level API compatibility.
-    earlyRuntime "org.apache.httpcomponents:httpcore:4.0.1"
-    api "org.apache.httpcomponents:httpclient:4.0.3"
-    compileOnly(AndroidSdk.LOLLIPOP_MR1.coordinates) { force = true }
+    earlyRuntime libs.apache.http.core
+    api libs.apache.http.client
+    compileOnly(AndroidSdk.LOLLIPOP_MR1.coordinates)
 
     testImplementation project(":robolectric")
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockito
     testImplementation "androidx.test.ext:junit:$axtJunitVersion@aar"
 
-    testCompileOnly(AndroidSdk.LOLLIPOP_MR1.coordinates) { force = true }
+    testCompileOnly(AndroidSdk.LOLLIPOP_MR1.coordinates)
     testRuntimeOnly AndroidSdk.S.coordinates
 }
 
diff --git a/shadows/playservices/build.gradle b/shadows/playservices/build.gradle
index c3abbba..b009838 100644
--- a/shadows/playservices/build.gradle
+++ b/shadows/playservices/build.gradle
@@ -14,25 +14,20 @@
 dependencies {
     compileOnly project(":shadows:framework")
     api project(":annotations")
-    api "com.google.guava:guava:$guavaJREVersion"
+    api libs.guava
 
-    compileOnly "androidx.fragment:fragment:1.2.0"
-    compileOnly "com.google.android.gms:play-services-base:8.4.0"
-    compileOnly "com.google.android.gms:play-services-basement:8.4.0"
+    compileOnly libs.bundles.play.services.base.for.shadows
 
     compileOnly AndroidSdk.MAX_SDK.coordinates
 
     testCompileOnly AndroidSdk.MAX_SDK.coordinates
-    testCompileOnly "com.google.android.gms:play-services-base:8.4.0"
-    testCompileOnly "com.google.android.gms:play-services-basement:8.4.0"
+    testCompileOnly libs.bundles.play.services.base.for.shadows
 
     testImplementation project(":robolectric")
-    testImplementation "junit:junit:$junitVersion"
-    testImplementation "com.google.truth:truth:$truthVersion"
-    testImplementation "org.mockito:mockito-core:$mockitoVersion"
-    testRuntimeOnly "androidx.fragment:fragment:1.2.0"
-    testRuntimeOnly "com.google.android.gms:play-services-base:8.4.0"
-    testRuntimeOnly "com.google.android.gms:play-services-basement:8.4.0"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.mockito
+    testRuntimeOnly libs.bundles.play.services.base.for.shadows
 
     testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
 }
diff --git a/shadows/versioning/Android.bp b/shadows/versioning/Android.bp
new file mode 100644
index 0000000..b630e22
--- /dev/null
+++ b/shadows/versioning/Android.bp
@@ -0,0 +1,67 @@
+//#############################################
+// Compile Robolectric utils
+//#############################################
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_robolectric_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+    name: "Robolectric_shadows_versioning_upstream",
+    srcs: ["src/main/java/**/*.java"],
+    static_libs: [
+        "robolectric-javax.annotation-api-1.2",
+        "Robolectric_shadowapi_upstream",
+        "Robolectric_utils_upstream",
+        "jsr305",
+    ],
+    libs: ["robolectric-host-android_all_upstream"],
+}
+
+//#############################################
+// Compile Robolectric utils tests
+//#############################################
+
+java_test_host {
+    name: "Robolectric_shadows_versioning_tests_upstream",
+    srcs: ["src/test/java/**/AndroidVersionsEdgeCaseTest.java"],
+    static_libs: [
+        "Robolectric_shadows_versioning_upstream",
+        "hamcrest",
+        "guava",
+        "junit",
+        "truth-prebuilt",
+    ],
+    test_suites: ["general-tests"],
+    test_options: {
+        unit_test: false,
+    },
+}
+
+//android_robolectric_test {
+//    enabled: true,
+//
+//    name: "Robolectric_shadows_versioning_tests_e2e_upstream",
+//
+//    srcs: [
+//        "src/**/*.AndroidVersionsTest.java",
+//    ],
+//
+//    java_resource_dirs: ["config"],
+//
+//    libs: [
+//        "androidx.test.core",
+//        "androidx.test.runner",
+//    ],
+//
+//    instrumentation_for: "MyRoboApplication",
+//
+//    upstream: true,
+//}
+
+
diff --git a/shadows/versioning/build.gradle b/shadows/versioning/build.gradle
new file mode 100644
index 0000000..68a8fb7
--- /dev/null
+++ b/shadows/versioning/build.gradle
@@ -0,0 +1,21 @@
+import org.robolectric.gradle.DeployedRoboJavaModulePlugin
+import org.robolectric.gradle.RoboJavaModulePlugin
+
+apply plugin: RoboJavaModulePlugin
+apply plugin: DeployedRoboJavaModulePlugin
+
+configurations {
+    earlyRuntime
+}
+
+dependencies {
+    api project(":shadowapi")
+    compileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK (AndroidSdk.s.coordinates) { force = true }
+    testImplementation project(":robolectric")
+    testImplementation libs.truth
+    testImplementation "androidx.test.ext:junit:$axtJunitVersion@aar"
+    testCompileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK
+    testRuntimeOnly AndroidSdk.MAX_SDK.coordinates // run against whatever this JDK supports
+}
+
+
diff --git a/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java b/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java
new file mode 100644
index 0000000..316365b
--- /dev/null
+++ b/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java
@@ -0,0 +1,23 @@
+package org.robolectric.versioning;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.jar.JarFile;
+import org.robolectric.versioning.AndroidVersions.AndroidRelease;
+
+/**
+ * Utility access method to allow robolectric to instantiate AndroidVersions without cluttering code
+ * completion for users of AndroidVersions's embedded Types of one per Android Releases.
+ */
+public final class AndroidVersionInitTools {
+
+  private AndroidVersionInitTools() {}
+
+  public static AndroidRelease computeReleaseVersion(JarFile jarFile) throws IOException {
+    return AndroidVersions.computeReleaseVersion(jarFile);
+  }
+
+  public static AndroidRelease computeCurrentSdkFromBuildProps(Properties buildProps) {
+    return AndroidVersions.computeCurrentSdkFromBuildProps(buildProps);
+  }
+}
diff --git a/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersions.java b/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersions.java
new file mode 100644
index 0000000..6314518
--- /dev/null
+++ b/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersions.java
@@ -0,0 +1,779 @@
+package org.robolectric.versioning;
+
+/*
+ * Copyright (C) 2023 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.
+ */
+
+import static java.util.Arrays.asList;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import javax.annotation.Nullable;
+import org.robolectric.util.Logger;
+import org.robolectric.util.ReflectionHelpers;
+
+/**
+ * Android versioning is complicated.<br>
+ * 1) There is a yearly letter release with an increasing of one alpha step each year A-> B, B-> C,
+ * and so on. While commonly referenced these are not the release numbers. This class calls these
+ * shortcodes. Also minor version number releases (usually within the same year) will start with the
+ * same letter.<br>
+ * 2) There is an SDK_INT field in android.os.Build.VERSION that tracks a version of the internal
+ * SDK. While useful to track the actual released versions of Android, these are not the release
+ * number. More importantly, android.os.Build.VERSION uses code names to describe future versions.
+ * Multiple code names may be in development at once on different branches of Android.<br>
+ * 3) There is a yearly release major number followed by a minor number, which may or may not be
+ * used.<br>
+ * 4) Relevant logic and reasoning should match androidx.core.os.BuildCompat.java with the caveat
+ * that this class guess at the future release version number and short of the current dev branch.
+ * <br>
+ */
+public final class AndroidVersions {
+
+  private AndroidVersions() {}
+
+  /** Representation of an android release, one that has occurred, or is expected. */
+  public abstract static class AndroidRelease implements Comparable<AndroidRelease> {
+
+    /**
+     * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt may
+     * still be that of the prior release.
+     */
+    public int getSdkInt() {
+      return ReflectionHelpers.getStaticField(this.getClass(), "SDK_INT");
+    }
+
+    /**
+     * single character short code for the release, multiple characters for minor releases (only
+     * minor version numbers increment - usually within the same year).
+     */
+    public String getShortCode() {
+      return ReflectionHelpers.getStaticField(this.getClass(), "SHORT_CODE");
+    }
+
+    /**
+     * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt will
+     * guess at the likely sdk number. Your code will need to recompile if this value changes -
+     * including most modern build tools; bazle, soong all are full build systems - and as such
+     * organizations using them have no concerns.
+     */
+    public boolean isReleased() {
+      return ReflectionHelpers.getStaticField(this.getClass(), "RELEASED");
+    }
+
+    /** major.minor version number as String. */
+    public String getVersion() {
+      return ReflectionHelpers.getStaticField(this.getClass(), "VERSION");
+    }
+
+    /**
+     * Implements comparable.
+     *
+     * @param other the object to be compared.
+     * @return 1 if this is greater than other, 0 if equal, -1 if less
+     * @throws RuntimeException if other is not an instance of AndroidRelease.
+     */
+    @Override
+    public int compareTo(AndroidRelease other) {
+      if (other == null) {
+        throw new RuntimeException(
+            "Only "
+                + AndroidVersions.class.getName()
+                + " should define Releases, illegal class "
+                + other.getClass());
+      }
+      return Integer.compare(this.getSdkInt(), other.getSdkInt());
+    }
+
+    @Override
+    public String toString() {
+      return "Android "
+          + (this.isReleased() ? "" : "Future ")
+          + "Release: "
+          + this.getVersion()
+          + " ( sdk: "
+          + this.getSdkInt()
+          + " code: "
+          + this.getShortCode()
+          + " )";
+    }
+  }
+
+  /**
+   * Version: 4.1 <br>
+   * ShortCode: J <br>
+   * SDK API Level: 16 <br>
+   * release: true <br>
+   */
+  public static final class J extends AndroidRelease {
+
+    public static final int SDK_INT = 16;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "J";
+
+    public static final String VERSION = "4.1";
+  }
+
+  /**
+   * Version: 4.2 <br>
+   * ShortCode: JMR1 <br>
+   * SDK API Level: 17 <br>
+   * release: true <br>
+   */
+  public static final class JMR1 extends AndroidRelease {
+
+    public static final int SDK_INT = 17;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "JMR1";
+
+    public static final String VERSION = "4.2";
+  }
+
+  /**
+   * Version: 4.3 <br>
+   * ShortCode: JMR2 <br>
+   * SDK API Level: 18 <br>
+   * release: true <br>
+   */
+  public static final class JMR2 extends AndroidRelease {
+
+    public static final int SDK_INT = 18;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "JMR2";
+
+    public static final String VERSION = "4.3";
+  }
+
+  /**
+   * Version: 4.4 <br>
+   * ShortCode: K <br>
+   * SDK API Level: 19 <br>
+   * release: true <br>
+   */
+  public static final class K extends AndroidRelease {
+
+    public static final int SDK_INT = 19;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "K";
+
+    public static final String VERSION = "4.4";
+  }
+
+  // Skipping K Watch release, which was 20.
+
+  /**
+   * Version: 5.0 <br>
+   * ShortCode: L <br>
+   * SDK API Level: 21 <br>
+   * release: true <br>
+   */
+  public static final class L extends AndroidRelease {
+
+    public static final int SDK_INT = 21;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "L";
+
+    public static final String VERSION = "5.0";
+  }
+
+  /**
+   * Version: 5.1 <br>
+   * ShortCode: LMR1 <br>
+   * SDK API Level: 22 <br>
+   * release: true <br>
+   */
+  public static final class LMR1 extends AndroidRelease {
+
+    public static final int SDK_INT = 22;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "LMR1";
+
+    public static final String VERSION = "5.1";
+  }
+
+  /**
+   * Version: 6.0 <br>
+   * ShortCode: M <br>
+   * SDK API Level: 23 <br>
+   * release: true <br>
+   */
+  public static final class M extends AndroidRelease {
+
+    public static final int SDK_INT = 23;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "M";
+
+    public static final String VERSION = "6.0";
+  }
+
+  /**
+   * Version: 7.0 <br>
+   * ShortCode: N <br>
+   * SDK API Level: 24 <br>
+   * release: true <br>
+   */
+  public static final class N extends AndroidRelease {
+
+    public static final int SDK_INT = 24;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "N";
+
+    public static final String VERSION = "7.0";
+  }
+
+  /**
+   * Release: 7.1 <br>
+   * ShortCode: NMR1 <br>
+   * SDK Framework: 25 <br>
+   * release: true <br>
+   */
+  public static final class NMR1 extends AndroidRelease {
+
+    public static final int SDK_INT = 25;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "NMR1";
+
+    private static final String VERSION = "7.1";
+  }
+
+  /**
+   * Release: 8.0 <br>
+   * ShortCode: O <br>
+   * SDK API Level: 26 <br>
+   * release: true <br>
+   */
+  public static final class O extends AndroidRelease {
+
+    public static final int SDK_INT = 26;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "O";
+
+    public static final String VERSION = "8.0";
+  }
+
+  /**
+   * Release: 8.1 <br>
+   * ShortCode: OMR1 <br>
+   * SDK API Level: 27 <br>
+   * release: true <br>
+   */
+  public static final class OMR1 extends AndroidRelease {
+
+    public static final int SDK_INT = 27;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "OMR1";
+
+    public static final String VERSION = "8.1";
+  }
+
+  /**
+   * Release: 9.0 <br>
+   * ShortCode: P <br>
+   * SDK API Level: 28 <br>
+   * release: true <br>
+   */
+  public static final class P extends AndroidRelease {
+
+    public static final int SDK_INT = 28;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "P";
+
+    public static final String VERSION = "9.0";
+  }
+
+  /**
+   * Release: 10.0 <br>
+   * ShortCode: Q <br>
+   * SDK API Level: 29 <br>
+   * release: true <br>
+   */
+  public static final class Q extends AndroidRelease {
+
+    public static final int SDK_INT = 29;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "Q";
+
+    public static final String VERSION = "10.0";
+  }
+
+  /**
+   * Release: 11.0 <br>
+   * ShortCode: R <br>
+   * SDK API Level: 30 <br>
+   * release: true <br>
+   */
+  public static final class R extends AndroidRelease {
+
+    public static final int SDK_INT = 30;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "R";
+
+    public static final String VERSION = "11.0";
+  }
+
+  /**
+   * Release: 12.0 <br>
+   * ShortCode: S <br>
+   * SDK API Level: 31 <br>
+   * release: true <br>
+   */
+  public static final class S extends AndroidRelease {
+
+    public static final int SDK_INT = 31;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "S";
+
+    public static final String VERSION = "12.0";
+  }
+
+  /**
+   * Release: 12.1 <br>
+   * ShortCode: Sv2 <br>
+   * SDK API Level: 32 <br>
+   * release: true <br>
+   */
+  @SuppressWarnings("UPPER_SNAKE_CASE")
+  public static final class Sv2 extends AndroidRelease {
+
+    public static final int SDK_INT = 32;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "Sv2";
+
+    public static final String VERSION = "12.1";
+  }
+
+  /**
+   * Release: 13.0 <br>
+   * ShortCode: T <br>
+   * SDK API Level: 33 <br>
+   * release: true <br>
+   */
+  public static final class T extends AndroidRelease {
+
+    public static final int SDK_INT = 33;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "T";
+
+    public static final String VERSION = "13.0";
+  }
+
+  /**
+   * Potential Release: 14.0 <br>
+   * ShortCode: U <br>
+   * SDK API Level: 34 <br>
+   * release: false <br>
+   */
+  public static final class U extends AndroidRelease {
+
+    public static final int SDK_INT = 34;
+
+    public static final boolean RELEASED = true;
+
+    public static final String SHORT_CODE = "U";
+
+    public static final String VERSION = "14.0";
+  }
+
+  /**
+   * Potential Release: 15.0 <br>
+   * ShortCode: V <br>
+   * SDK API Level: 34+ <br>
+   * release: false <br>
+   */
+  public static final class V extends AndroidRelease {
+
+    public static final int SDK_INT = 35;
+
+    public static final boolean RELEASED = false;
+
+    public static final String SHORT_CODE = "V";
+
+    public static final String VERSION = "15.0";
+  }
+
+  /** The current release this process is running on. */
+  public static final AndroidRelease CURRENT;
+
+  @Nullable
+  public static AndroidRelease getReleaseForSdkInt(@Nullable Integer sdkInt) {
+    if (sdkInt == null) {
+      return null;
+    } else {
+      return information.sdkIntToAllReleases.get(sdkInt);
+    }
+  }
+
+  public static List<AndroidRelease> getReleases() {
+    List<AndroidRelease> output = new ArrayList<>();
+    for (AndroidRelease release : information.allReleases) {
+      if (release.isReleased()) {
+        output.add(release);
+      }
+    }
+    return output;
+  }
+
+  public static List<AndroidRelease> getUnreleased() {
+    List<AndroidRelease> output = new ArrayList<>();
+    for (AndroidRelease release : information.allReleases) {
+      if (!release.isReleased()) {
+        output.add(release);
+      }
+    }
+    return output;
+  }
+
+  /**
+   * Responsible for aggregating and interpreting the static state representing the current
+   * AndroidReleases known to AndroidVersions class.
+   */
+  static class SdkInformation {
+    final List<AndroidRelease> allReleases;
+    final List<Class<? extends AndroidRelease>> classesWithIllegalNames;
+    final AndroidRelease latestRelease;
+    final AndroidRelease earliestUnreleased;
+
+    // In the future we may need a multimap for sdkInts should they stay static across releases.
+    final Map<Integer, AndroidRelease> sdkIntToAllReleases = new HashMap<>();
+    final Map<String, AndroidRelease> shortCodeToAllReleases = new HashMap<>();
+
+    // detected errors
+    final List<Map.Entry<AndroidRelease, AndroidRelease>> sdkIntCollisions = new ArrayList<>();
+    Map.Entry<AndroidRelease, AndroidRelease> sdkApiMisordered = null;
+
+    public SdkInformation(
+        List<AndroidRelease> releases,
+        List<Class<? extends AndroidRelease>> classesWithIllegalNames) {
+      this.allReleases = releases;
+      this.classesWithIllegalNames = classesWithIllegalNames;
+      AndroidRelease latestRelease = null;
+      AndroidRelease earliestUnreleased = null;
+      for (AndroidRelease release : allReleases) {
+        if (release.isReleased()) {
+          if (latestRelease == null || latestRelease.compareTo(release) > 0) {
+            latestRelease = release;
+          }
+        } else {
+          if (earliestUnreleased == null || earliestUnreleased.compareTo(release) < 0) {
+            earliestUnreleased = release;
+          }
+        }
+      }
+      this.latestRelease = latestRelease;
+      this.earliestUnreleased = earliestUnreleased;
+      verifyStaticInformation();
+    }
+
+    private void verifyStaticInformation() {
+      for (AndroidRelease release : this.allReleases) {
+        // Construct a map of all sdkInts to releases and note duplicates
+        AndroidRelease sdkCollision = this.sdkIntToAllReleases.put(release.getSdkInt(), release);
+        if (sdkCollision != null) {
+          this.sdkIntCollisions.add(new AbstractMap.SimpleEntry<>(release, sdkCollision));
+        }
+        // Construct a map of all short codes to releases, and note duplicates
+        this.shortCodeToAllReleases.put(release.getShortCode(), release);
+        // There is no need to check for shortCode duplicates as the Field name must match the
+        // short code.
+      }
+      if (earliestUnreleased != null
+          && latestRelease != null
+          && latestRelease.getSdkInt() >= earliestUnreleased.getSdkInt()) {
+        sdkApiMisordered = new AbstractMap.SimpleEntry<>(latestRelease, earliestUnreleased);
+      }
+    }
+
+    private void throwStaticErrors() {
+      StringBuilder errors = new StringBuilder();
+      if (!this.classesWithIllegalNames.isEmpty()) {
+        errors
+            .append("The following classes do not follow the naming criteria for ")
+            .append("releases or do not have the short codes in ")
+            .append("their internal fields. Please correct them: ")
+            .append(this.classesWithIllegalNames)
+            .append("\n");
+      }
+      if (sdkApiMisordered != null) {
+        errors
+            .append("The latest released sdk ")
+            .append(sdkApiMisordered.getKey().getShortCode())
+            .append(" has a sdkInt greater than the earliest unreleased sdk ")
+            .append(sdkApiMisordered.getValue().getShortCode())
+            .append("this implies sdks were released out of order which is highly unlikely.\n");
+      }
+      if (!sdkIntCollisions.isEmpty()) {
+        errors.append(
+            "The following sdks have different shortCodes, but identical sdkInt " + "versions:\n");
+        for (Map.Entry<AndroidRelease, AndroidRelease> entry : sdkIntCollisions) {
+          errors
+              .append("Both ")
+              .append(entry.getKey().getShortCode())
+              .append(" and ")
+              .append(entry.getValue().getShortCode())
+              .append("have the same sdkInt value of ")
+              .append(entry.getKey().getSdkInt())
+              .append("\n");
+        }
+      }
+      if (errors.length() > 0) {
+        throw new RuntimeException(
+            errors
+                .append("Please check the AndroidReleases defined ")
+                .append("in ")
+                .append(AndroidVersions.class.getName())
+                .append("and ensure they are aligned with the versions of")
+                .append(" Android.")
+                .toString());
+      }
+    }
+
+    public AndroidRelease computeCurrentSdk(
+        int reportedVersion, String releaseName, String codename, List<String> activeCodeNames) {
+      Logger.info("Reported Version: " + reportedVersion);
+      Logger.info("Release Name: " + releaseName);
+      Logger.info("Code Name: " + codename);
+      Logger.info("Active Code Names: " + String.join(",", activeCodeNames));
+
+      AndroidRelease current = null;
+      // Special case "REL", which means the build is not a pre-release build.
+      if ("REL".equals(codename)) {
+        // the first letter of the code name equal to the release number.
+        current = sdkIntToAllReleases.get(reportedVersion);
+        if (current != null && !current.isReleased()) {
+          throw new RuntimeException(
+              "The current sdk "
+                  + current.getShortCode()
+                  + " has been released. Please update the contents of "
+                  + AndroidVersions.class.getName()
+                  + " to mark sdk "
+                  + current.getShortCode()
+                  + " as released.");
+        }
+      } else {
+        // Get known active code name letters
+
+        List<String> activeCodenameLetter = new ArrayList<>();
+        for (String name : activeCodeNames) {
+          activeCodenameLetter.add(name.toUpperCase(Locale.getDefault()).substring(0, 1));
+        }
+
+        // If the process is operating with a code name.
+        if (codename != null) {
+          StringBuilder detectedProblems = new StringBuilder();
+          // This is safe for minor releases ( X.1 ) as long as they have added an entry
+          // corresponding to the sdk of that release and the prior major release is marked as
+          // "released" on its entry in this file.  If not this class will fail to initialize.
+          // The assumption is that only one of the major or minor version of a code name
+          // is under development and unreleased at any give time (S or Sv2).
+          String foundCode = codename.toUpperCase(Locale.getDefault()).substring(0, 1);
+          int loc = activeCodenameLetter.indexOf(foundCode);
+          if (loc == -1) {
+            detectedProblems
+                .append("The current codename's (")
+                .append(codename)
+                .append(") first letter (")
+                .append(foundCode)
+                .append(") is not in the list of active code's first letters: ")
+                .append(activeCodenameLetter)
+                .append("\n");
+          } else {
+            // attempt to find assume the fullname is the "shortCode", aka "Sv2", "OMR1".
+            current = shortCodeToAllReleases.get(codename);
+            // else, assume the fullname is the first letter is correct.
+            if (current == null) {
+              current = shortCodeToAllReleases.get(String.valueOf(foundCode));
+            }
+          }
+          if (current == null) {
+            detectedProblems
+                .append("No known release is associated with the shortCode of \"")
+                .append(foundCode)
+                .append("\" or \"")
+                .append(codename)
+                .append("\"\n");
+          } else if (current.isReleased()) {
+            detectedProblems
+                .append("The current sdk ")
+                .append(current.getShortCode())
+                .append(" has been been marked as released. Please update the ")
+                .append("contents of current sdk jar to the released version.\n");
+          }
+          if (detectedProblems.length() > 0) {
+            throw new RuntimeException(detectedProblems.toString());
+          }
+        }
+      }
+      return current;
+    }
+  }
+
+  /**
+   * Reads all AndroidReleases in this class and populates SdkInformation, checking for sanity in
+   * the shortCode, sdkInt, and release information.
+   *
+   * <p>All errors are stored and can be reported at once by asking the SdkInformation to throw a
+   * runtime exception after it has been populated.
+   */
+  static SdkInformation gatherStaticSdkInformationFromThisClass() {
+    List<AndroidRelease> allReleases = new ArrayList<>();
+    List<Class<? extends AndroidRelease>> classesWithIllegalNames = new ArrayList<>();
+    for (Class<?> clazz : AndroidVersions.class.getClasses()) {
+      if (AndroidRelease.class.isAssignableFrom(clazz)
+          && !clazz.isInterface()
+          && !Modifier.isAbstract(clazz.getModifiers())) {
+        try {
+          AndroidRelease rel = (AndroidRelease) clazz.getDeclaredConstructor().newInstance();
+          allReleases.add(rel);
+          // inspect field name - as this is our only chance to inspect it.
+          if (!rel.getClass().getSimpleName().equals(rel.getShortCode())) {
+            classesWithIllegalNames.add(rel.getClass());
+          }
+        } catch (NoSuchMethodException
+            | InstantiationException
+            | IllegalArgumentException
+            | IllegalAccessException
+            | InvocationTargetException ex) {
+          throw new RuntimeException(
+              "Classes "
+                  + clazz.getName()
+                  + "should be accessible via "
+                  + AndroidVersions.class.getCanonicalName()
+                  + " and have a default public no-op constructor ",
+              ex);
+        }
+      }
+    }
+    Collections.sort(allReleases, AndroidRelease::compareTo);
+
+    SdkInformation sdkInformation = new SdkInformation(allReleases, classesWithIllegalNames);
+    sdkInformation.throwStaticErrors();
+    return sdkInformation;
+  }
+
+  static AndroidRelease computeReleaseVersion(JarFile jarFile) throws IOException {
+    ZipEntry buildProp = jarFile.getEntry("build.prop");
+    Properties buildProps = new Properties();
+    buildProps.load(jarFile.getInputStream(buildProp));
+    return computeCurrentSdkFromBuildProps(buildProps);
+  }
+
+  static AndroidRelease computeCurrentSdkFromBuildProps(Properties buildProps) {
+    // 33, 34, 35 ....
+    String sdkVersionString = buildProps.getProperty("ro.build.version.sdk");
+    int sdk = sdkVersionString == null ? 0 : Integer.parseInt(sdkVersionString);
+    // "REL"
+    String release = buildProps.getProperty("ro.build.version.release");
+    // "Tiramasu", "UpsideDownCake"
+    String codename = buildProps.getProperty("ro.build.version.codename");
+    // "Tiramasu,UpsideDownCake", "UpsideDownCake", "REL"
+    String codenames = buildProps.getProperty("ro.build.version.all_codenames");
+    String[] allCodeNames = codenames == null ? new String[0] : codenames.split(",");
+    String[] activeCodeNames =
+        allCodeNames.length > 0 && allCodeNames[0].equals("REL") ? new String[0] : allCodeNames;
+    return information.computeCurrentSdk(sdk, release, codename, asList(activeCodeNames));
+  }
+
+  /**
+   * If we are working in android source, this code detects the list of active code names if any.
+   */
+  private static List<String> getActiveCodeNamesIfAny(Class<?> targetClass) {
+    try {
+      Field activeCodeFields = targetClass.getDeclaredField("ACTIVE_CODENAMES");
+      String[] activeCodeNames = (String[]) activeCodeFields.get(null);
+      return asList(activeCodeNames);
+    } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException ex) {
+      return new ArrayList<>();
+    }
+  }
+
+  private static final SdkInformation information;
+
+  static {
+    AndroidRelease currentRelease = null;
+    information = gatherStaticSdkInformationFromThisClass();
+    try {
+      Class<?> buildClass =
+          Class.forName("android.os.Build", false, Thread.currentThread().getContextClassLoader());
+      System.out.println("build class " + buildClass);
+      Class<?> versionClass = null;
+      for (Class<?> c : buildClass.getClasses()) {
+        if (c.getSimpleName().equals("VERSION")) {
+          versionClass = c;
+          System.out.println("Version class " + versionClass);
+          break;
+        }
+      }
+      if (versionClass != null) {
+        // 33, 34, etc....
+        int sdkInt = (int) ReflectionHelpers.getStaticField(versionClass, "SDK_INT");
+        // Either unset, or 13, 14, etc....
+        String release = ReflectionHelpers.getStaticField(versionClass, "RELEASE");
+        // Either REL if release is set, or Tiramasu, UpsideDownCake, etc
+        String codename = ReflectionHelpers.getStaticField(versionClass, "CODENAME");
+        List<String> activeCodeNames = getActiveCodeNamesIfAny(versionClass);
+        currentRelease = information.computeCurrentSdk(sdkInt, release, codename, activeCodeNames);
+      }
+    } catch (ClassNotFoundException | IllegalArgumentException | UnsatisfiedLinkError e) {
+      // No op, this class should be usable outside of a Robolectric sandbox.
+    }
+    CURRENT = currentRelease;
+  }
+}
diff --git a/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java b/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java
new file mode 100644
index 0000000..95b2c42
--- /dev/null
+++ b/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java
@@ -0,0 +1,71 @@
+package org.robolectric.versioning;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.robolectric.versioning.AndroidVersions.AndroidRelease;
+import org.robolectric.versioning.AndroidVersions.SdkInformation;
+
+/** Test more esoteric versions mismatches in sdkInt numbers, and codenames. */
+@RunWith(JUnit4.class)
+public final class AndroidVersionsEdgeCaseTest {
+
+  /**
+   * sdkInt higher than any known release, claims it's released. Expects an error message to update
+   * to update the AndroidVersions.class
+   */
+  @Test
+  public void sdkIntHigherThanKnownReleasesClaimsIsReleased_throwsException() {
+    AndroidRelease earliestUnrelease = null;
+    try {
+      SdkInformation information = AndroidVersions.gatherStaticSdkInformationFromThisClass();
+      earliestUnrelease = information.earliestUnreleased;
+      information.computeCurrentSdk(
+          earliestUnrelease.getSdkInt(), earliestUnrelease.getVersion(), "REL", Arrays.asList());
+      assertThat(this).isNull();
+    } catch (RuntimeException e) {
+      assertThat(e)
+          .hasMessageThat()
+          .contains(
+              "The current sdk "
+                  + earliestUnrelease.getShortCode()
+                  + " has been released. Please update the contents of "
+                  + AndroidVersions.class.getName()
+                  + " to mark sdk "
+                  + earliestUnrelease.getShortCode()
+                  + " as released.");
+      assertThat(e).isInstanceOf(RuntimeException.class);
+    }
+  }
+
+  /**
+   * sdkInt lower than known release, claims it's released. Expects an error message to update the
+   * jar.
+   */
+  @Test
+  public void sdkIntReleasedButStillReportsCodeName_throwsException() {
+    AndroidRelease latestRelease = null;
+    try {
+      SdkInformation information = AndroidVersions.gatherStaticSdkInformationFromThisClass();
+      latestRelease = information.latestRelease;
+      information.computeCurrentSdk(
+          latestRelease.getSdkInt(),
+          null,
+          information.latestRelease.getShortCode(),
+          Arrays.asList(latestRelease.getShortCode()));
+      assertThat(this).isNull();
+    } catch (RuntimeException e) {
+      assertThat(e)
+          .hasMessageThat()
+          .contains(
+              "The current sdk "
+                  + latestRelease.getShortCode()
+                  + " has been been marked as released. Please update the contents of current sdk"
+                  + " jar to the released version.");
+      assertThat(e).isInstanceOf(RuntimeException.class);
+    }
+  }
+}
diff --git a/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java b/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java
new file mode 100644
index 0000000..dba93ca
--- /dev/null
+++ b/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java
@@ -0,0 +1,205 @@
+package org.robolectric.versioning;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build.VERSION;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.versioning.AndroidVersions.T;
+
+/**
+ * Check versions information aligns with runtime information. Primarily, selected SDK with
+ * internally detected version number.
+ */
+@RunWith(RobolectricTestRunner.class)
+public final class AndroidVersionsTest {
+
+  @Test
+  @Config(sdk = T.SDK_INT)
+  public void testStandardInitializationT() {
+    assertThat(VERSION.SDK_INT).isEqualTo(33);
+    assertThat(VERSION.RELEASE).isEqualTo("13");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.T.SHORT_CODE).isEqualTo("T");
+    assertThat(new AndroidVersions.T().getVersion()).isEqualTo("13.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("T");
+  }
+
+  @Test
+  @Config(sdk = 32)
+  public void testStandardInitializationSv2() {
+    assertThat(VERSION.SDK_INT).isEqualTo(32);
+    assertThat(VERSION.RELEASE).isEqualTo("12");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.Sv2.SHORT_CODE).isEqualTo("Sv2");
+    assertThat(new AndroidVersions.Sv2().getVersion()).isEqualTo("12.1");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("Sv2");
+  }
+
+  @Test
+  @Config(sdk = 31)
+  public void testStandardInitializationS() {
+    assertThat(VERSION.SDK_INT).isEqualTo(31);
+    assertThat(VERSION.RELEASE).isEqualTo("12");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.S.SHORT_CODE).isEqualTo("S");
+    assertThat(new AndroidVersions.S().getVersion()).isEqualTo("12.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("S");
+  }
+
+  @Test
+  @Config(sdk = 30)
+  public void testStandardInitializationR() {
+    assertThat(VERSION.SDK_INT).isEqualTo(30);
+    assertThat(VERSION.RELEASE).isEqualTo("11");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.R.SHORT_CODE).isEqualTo("R");
+    assertThat(new AndroidVersions.R().getVersion()).isEqualTo("11.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("R");
+  }
+
+  @Test
+  @Config(sdk = 29)
+  public void testStandardInitializationQ() {
+    assertThat(VERSION.SDK_INT).isEqualTo(29);
+    assertThat(VERSION.RELEASE).isEqualTo("10");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.Q.SHORT_CODE).isEqualTo("Q");
+    assertThat(new AndroidVersions.Q().getVersion()).isEqualTo("10.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("Q");
+  }
+
+  @Test
+  @Config(sdk = 28)
+  public void testStandardInitializationP() {
+    assertThat(VERSION.SDK_INT).isEqualTo(28);
+    assertThat(VERSION.RELEASE).isEqualTo("9");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.P.SHORT_CODE).isEqualTo("P");
+    assertThat(new AndroidVersions.P().getVersion()).isEqualTo("9.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("P");
+  }
+
+  @Test
+  @Config(sdk = 27)
+  public void testStandardInitializationOMR1() {
+    assertThat(VERSION.SDK_INT).isEqualTo(27);
+    assertThat(VERSION.RELEASE).isEqualTo("8.1.0");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.OMR1.SHORT_CODE).isEqualTo("OMR1");
+    assertThat(new AndroidVersions.OMR1().getVersion()).isEqualTo("8.1");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("OMR1");
+  }
+
+  @Test
+  @Config(sdk = 26)
+  public void testStandardInitializationO() {
+    assertThat(VERSION.SDK_INT).isEqualTo(26);
+    assertThat(VERSION.RELEASE).isEqualTo("8.0.0");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.O.SHORT_CODE).isEqualTo("O");
+    assertThat(new AndroidVersions.O().getVersion()).isEqualTo("8.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("O");
+  }
+
+  @Test
+  @Config(sdk = 25)
+  public void testStandardInitializationNMR1() {
+    assertThat(VERSION.SDK_INT).isEqualTo(25);
+    assertThat(VERSION.RELEASE).isEqualTo("7.1");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.NMR1.SHORT_CODE).isEqualTo("NMR1");
+    assertThat(new AndroidVersions.NMR1().getVersion()).isEqualTo("7.1");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("NMR1");
+  }
+
+  @Test
+  @Config(sdk = 24)
+  public void testStandardInitializationN() {
+    assertThat(VERSION.SDK_INT).isEqualTo(24);
+    assertThat(VERSION.RELEASE).isEqualTo("7.0");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.N.SHORT_CODE).isEqualTo("N");
+    assertThat(new AndroidVersions.N().getVersion()).isEqualTo("7.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("N");
+  }
+
+  @Test
+  @Config(sdk = 23)
+  public void testStandardInitializationM() {
+    assertThat(VERSION.SDK_INT).isEqualTo(23);
+    assertThat(VERSION.RELEASE).isEqualTo("6.0.1");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.M.SHORT_CODE).isEqualTo("M");
+    assertThat(new AndroidVersions.M().getVersion()).isEqualTo("6.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("M");
+  }
+
+  @Test
+  @Config(sdk = 22)
+  public void testStandardInitializationLMR1() {
+    assertThat(VERSION.SDK_INT).isEqualTo(22);
+    assertThat(VERSION.RELEASE).isEqualTo("5.1.1");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.LMR1.SHORT_CODE).isEqualTo("LMR1");
+    assertThat(new AndroidVersions.LMR1().getVersion()).isEqualTo("5.1");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("LMR1");
+  }
+
+  @Test
+  @Config(sdk = 21)
+  public void testStandardInitializationL() {
+    assertThat(VERSION.SDK_INT).isEqualTo(21);
+    assertThat(VERSION.RELEASE).isEqualTo("5.0.2");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.L.SHORT_CODE).isEqualTo("L");
+    assertThat(new AndroidVersions.L().getVersion()).isEqualTo("5.0");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("L");
+  }
+
+  @Test
+  @Config(sdk = 19)
+  public void testStandardInitializationK() {
+    assertThat(VERSION.SDK_INT).isEqualTo(19);
+    assertThat(VERSION.RELEASE).isEqualTo("4.4");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.K.SHORT_CODE).isEqualTo("K");
+    assertThat(new AndroidVersions.K().getVersion()).isEqualTo("4.4");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("K");
+  }
+
+  @Test
+  @Config(sdk = 18)
+  public void testStandardInitializationJMR2() {
+    assertThat(VERSION.SDK_INT).isEqualTo(18);
+    assertThat(VERSION.RELEASE).isEqualTo("4.3");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.JMR2.SHORT_CODE).isEqualTo("JMR2");
+    assertThat(new AndroidVersions.JMR2().getVersion()).isEqualTo("4.3");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("JMR2");
+  }
+
+  @Test
+  @Config(sdk = 17)
+  public void testStandardInitializationJMR1() {
+    assertThat(VERSION.SDK_INT).isEqualTo(17);
+    assertThat(VERSION.RELEASE).isEqualTo("4.2.2");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.JMR1.SHORT_CODE).isEqualTo("JMR1");
+    assertThat(new AndroidVersions.JMR1().getVersion()).isEqualTo("4.2");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("JMR1");
+  }
+
+  @Test
+  @Config(sdk = 16)
+  public void testStandardInitializationJ() {
+    assertThat(VERSION.SDK_INT).isEqualTo(16);
+    assertThat(VERSION.RELEASE).isEqualTo("4.1.2");
+    assertThat(VERSION.CODENAME).isEqualTo("REL");
+    assertThat(AndroidVersions.J.SHORT_CODE).isEqualTo("J");
+    assertThat(new AndroidVersions.J().getVersion()).isEqualTo("4.1");
+    assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("J");
+  }
+}
diff --git a/shadows/versioning/src/test/resources/AndroidManifest.xml b/shadows/versioning/src/test/resources/AndroidManifest.xml
new file mode 100644
index 0000000..65383ac
--- /dev/null
+++ b/shadows/versioning/src/test/resources/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.robolectric">
+    <uses-sdk android:targetSdkVersion="33" android:minSdkVersion="33"/>
+    <application android:name="android.app.Application">
+    </application>
+</manifest>
diff --git a/testapp/Android.bp b/testapp/Android.bp
new file mode 100644
index 0000000..a11ba44
--- /dev/null
+++ b/testapp/Android.bp
@@ -0,0 +1,41 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "GlobalRobolectricAssetsLib",
+    asset_dirs: ["src/main/assets"],
+    resource_dirs: ["src/main/res"],
+    min_sdk_version: "16",
+    target_sdk_version: "33",
+    platform_apis: true,
+    manifest: "src/main/AndroidManifest.xml",
+    optimize: {
+        enabled: false
+    },
+}
+
+android_app {
+    name: "GlobalRobolectricTestStub",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: ["GlobalRobolectricAssetsLib"],
+    manifest: "src/main/AndroidManifest.xml",
+    aaptflags: [
+        "--extra-packages",
+        "org.robolectric.testapp",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    min_sdk_version: "16",
+    target_sdk_version: "33",
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: ["src/main/res"],
+    kotlincflags: ["-Xjvm-default=all"],
+
+    plugins: ["dagger2-compiler"],
+}
diff --git a/testapp/build.gradle b/testapp/build.gradle
index 651ced0..0abf895 100644
--- a/testapp/build.gradle
+++ b/testapp/build.gradle
@@ -2,6 +2,7 @@
 
 android {
     compileSdk 33
+    namespace 'org.robolectric.testapp'
 
     defaultConfig {
         minSdk 16
diff --git a/utils/build.gradle b/utils/build.gradle
index c10cca2..c31c9a0 100644
--- a/utils/build.gradle
+++ b/utils/build.gradle
@@ -1,3 +1,4 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 import org.robolectric.gradle.DeployedRoboJavaModulePlugin
 import org.robolectric.gradle.RoboJavaModulePlugin
 
@@ -13,7 +14,7 @@
     }
 }
 
-tasks.withType(GenerateModuleMetadata) {
+tasks.withType(GenerateModuleMetadata).configureEach {
     // We don't want to release gradle module metadata now to avoid
     // potential compatibility problems.
     enabled = false
@@ -26,7 +27,7 @@
     // in production. If utils module starts to add Kotlin code in main source
     // set, we can remove this destinationDirectory modification.
     destinationDirectory = file("${projectDir}/build/classes/java/main")
-    compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
+    compilerOptions.jvmTarget = JvmTarget.JVM_1_8
 }
 
 afterEvaluate {
@@ -48,20 +49,19 @@
 dependencies {
     api project(":annotations")
     api project(":pluginapi")
-    api "javax.inject:javax.inject:1"
-    api "javax.annotation:javax.annotation-api:1.3.2"
+    api libs.javax.inject
+    api libs.javax.annotation.api
 
     // For @VisibleForTesting and ByteStreams
-    implementation "com.google.guava:guava:$guavaJREVersion"
-    compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+    implementation libs.guava
+    compileOnly libs.findbugs.jsr305
 
-    testCompileOnly "com.google.auto.service:auto-service-annotations:$autoServiceVersion"
-    testAnnotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
-    testAnnotationProcessor "com.google.errorprone:error_prone_core:$errorproneVersion"
-    implementation "com.google.errorprone:error_prone_annotations:$errorproneVersion"
+    testCompileOnly libs.auto.service.annotations
+    testAnnotationProcessor libs.auto.service
+    testAnnotationProcessor libs.error.prone.core
+    implementation libs.error.prone.annotations
 
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
-    testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
+    testImplementation libs.kotlin.stdlib
 }
diff --git a/utils/reflector/build.gradle b/utils/reflector/build.gradle
index 3027345..140e987 100644
--- a/utils/reflector/build.gradle
+++ b/utils/reflector/build.gradle
@@ -5,12 +5,12 @@
 apply plugin: DeployedRoboJavaModulePlugin
 
 dependencies {
-    api "org.ow2.asm:asm:${asmVersion}"
-    api "org.ow2.asm:asm-commons:${asmVersion}"
-    api "org.ow2.asm:asm-util:${asmVersion}"
+    api libs.asm
+    api libs.asm.commons
+    api libs.asm.util
     api project(":utils")
 
     testImplementation project(":shadowapi")
-    testImplementation "junit:junit:${junitVersion}"
-    testImplementation "com.google.truth:truth:${truthVersion}"
+    testImplementation libs.junit4
+    testImplementation libs.truth
 }
diff --git a/utils/src/main/java/org/robolectric/util/Util.java b/utils/src/main/java/org/robolectric/util/Util.java
old mode 100755
new mode 100644