Merge changes I6f151498,I12fa5cda,I116437bb,I3c945497 into androidx-main

* changes:
  Remove settings of values that are default in AndroidXPlugin
  Clean up project references in build.gradle files
  Clean up plugin blocks in build.gradle
  Remove uses of path: in build.gradle
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/RuntimeImageTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/RuntimeImageTest.kt
index 5251431..5714c68 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/RuntimeImageTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/RuntimeImageTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.benchmark.macro
 
+import android.annotation.SuppressLint
 import androidx.benchmark.DeviceInfo
 import androidx.benchmark.json.BenchmarkData.TestResult.SingleMetricResult
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,7 +31,9 @@
 @RunWith(AndroidJUnit4::class)
 class RuntimeImageTest {
     private val className = RuntimeImageTest::class.java.name
+    private val iterCount = 3
 
+    @SuppressLint("BanThreadSleep")
     private fun captureRecyclerViewListStartupMetrics(
         testName: String,
     ): Map<String, SingleMetricResult> =
@@ -41,7 +44,7 @@
                 packageName = Packages.TARGET,
                 metrics = listOf(ArtMetric()),
                 compilationMode = CompilationMode.None(),
-                iterations = 3,
+                iterations = iterCount,
                 experimentalConfig = null,
                 startupMode = StartupMode.COLD,
                 setupBlock = {},
@@ -52,6 +55,14 @@
                             "androidx.benchmark.integration.macrobenchmark.target.RECYCLER_VIEW"
                         it.putExtra("ITEM_COUNT", 5)
                     }
+                    if (iteration != iterCount - 1) {
+                        // For every iter but last we wait for the runtime image flush. Subsequent
+                        // iterations will then be able to observe runtime image presence via class
+                        // loading counts. Unfortunately, there's no way to force runtime image
+                        // flush (b/372921569) other than waiting, though we skip it on the last
+                        // iter to save some time
+                        Thread.sleep(5000)
+                    }
                 }
             )
             .metrics
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
index 762383a..5ec4de0 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -55,20 +55,22 @@
     /**
      * Contextual information about the environment where metrics are captured, such as [apiLevel]
      * and [targetPackageName].
+     *
+     * @property apiLevel `Build.VERSION.SDK_INT` at time of capture.
+     * @property targetPackageName Package name of the app process being measured.
+     * @property testPackageName Package name of the test/benchmarking process.
+     * @property startupMode StartupMode for the target application, if the app was forced to launch
+     *   in a specific state, `null` otherwise.
+     * @property artMainlineVersion ART mainline version, or `-1` if on a OS version without ART
+     *   mainline (<30). `null` if captured from a fixed trace, where mainline version is unknown.
      */
     @ExperimentalMetricApi
     class CaptureInfo(
-        /** Build.VERSION.SDK_INT at time of capture */
         val apiLevel: Int,
         val targetPackageName: String,
         val testPackageName: String,
         val startupMode: StartupMode?,
-        /**
-         * ART mainline version, or -1 if on a OS version without ART mainline (<30)
-         *
-         * If `null`, ART version is unknown. This should only occur if capturing metrics from a
-         * fixed trace, where the ART version is unknown.
-         */
+
         // allocations for tests not relevant, not in critical path
         @Suppress("AutoBoxing")
         @get:Suppress("AutoBoxing")
@@ -97,6 +99,10 @@
             /**
              * Constructs a CaptureInfo for a local run on the current device, from the current
              * process.
+             *
+             * @param targetPackageName Package name of the app being measured.
+             * @param startupMode StartupMode for the target application, if the app was forced to
+             *   launch in a specific state, `null` otherwise.
              */
             @JvmStatic
             fun forLocalCapture(targetPackageName: String, startupMode: StartupMode?) =
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/BenchmarkConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/BenchmarkConfiguration.kt
index 069eacd..cde47d1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/BenchmarkConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/BenchmarkConfiguration.kt
@@ -52,6 +52,12 @@
             // Check that speed compilation always used when benchmark invoked
             deviceTest.instrumentationRunnerArguments.put("androidx.benchmark.requireAot", "true")
 
+            // Throw if measureRepeated() called on main thread to avoid ANRs
+            deviceTest.instrumentationRunnerArguments.put(
+                "androidx.benchmark.throwOnMainThreadMeasureRepeated",
+                "true"
+            )
+
             // Enables long-running method tracing on the UI thread, even if that risks ANR for
             // profiling convenience.
             // NOTE, this *must* be suppressed in CI!!
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/kythe/GenerateJavaKzipTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/kythe/GenerateJavaKzipTask.kt
new file mode 100644
index 0000000..fb20b69
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/kythe/GenerateJavaKzipTask.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.build.kythe
+
+import androidx.build.addToBuildOnServer
+import androidx.build.getCheckoutRoot
+import androidx.build.getPrebuiltsRoot
+import androidx.build.java.JavaCompileInputs
+import java.io.File
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.process.ExecOperations
+
+/** Generates kzip files that are used to index the Java source code in Kythe. */
+@CacheableTask
+abstract class GenerateJavaKzipTask
+@Inject
+constructor(private val execOperations: ExecOperations) : DefaultTask() {
+
+    /** Must be run in the checkout root so as to be free of relative markers */
+    @get:Internal val checkoutRoot: File = project.getCheckoutRoot()
+
+    @get:InputFile
+    @get:PathSensitive(PathSensitivity.NONE)
+    abstract val javaExtractorJar: RegularFileProperty
+
+    @get:InputFiles
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val sourcePaths: ConfigurableFileCollection
+
+    @get:Input abstract val javacCompilerArgs: ListProperty<String>
+
+    /** Path to `vnames.json` file, used for name mappings within Kythe. */
+    @get:InputFiles
+    @get:PathSensitive(PathSensitivity.NONE)
+    abstract val vnamesJson: RegularFileProperty
+
+    @get:Classpath abstract val dependencyClasspath: ConfigurableFileCollection
+
+    @get:Classpath abstract val annotationProcessor: ConfigurableFileCollection
+
+    @get:OutputFile abstract val kzipOutputFile: RegularFileProperty
+
+    @get:OutputDirectory abstract val kytheBuildDirectory: DirectoryProperty
+
+    @TaskAction
+    fun exec() {
+        val sourceFiles =
+            if (sourcePaths.asFileTree.files.none { it.extension == "kt" }) {
+                sourcePaths.asFileTree.files
+                    .filter { it.extension == "java" }
+                    .map { it.relativeTo(checkoutRoot) }
+            } else {
+                emptyList()
+            }
+
+        if (sourceFiles.isEmpty()) {
+            return
+        }
+
+        val dependencyClasspath = dependencyClasspath.filter { it.extension == "jar" }
+        val kytheBuildDirectory = kytheBuildDirectory.get().asFile.apply { mkdirs() }
+
+        execOperations.javaexec {
+            it.mainClass.set("-jar")
+            it.args(javaExtractorJar.get().asFile)
+            it.args("--class-path", dependencyClasspath.joinToString(":"))
+            it.args("--processor-path", annotationProcessor.joinToString(":"))
+            it.args(javacCompilerArgs.get())
+            it.args("-d", kytheBuildDirectory)
+            it.args(sourceFiles)
+            it.jvmArgs(
+                // Without all these flags, the extractor fails to run. Copied from:
+                // https://github.com/kythe/kythe/blob/v0.0.67/kythe/release/release.BUILD#L99-L106
+                "--add-opens=java.base/java.nio=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+                "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+                "--add-exports=jdk.internal.opt/jdk.internal.opt=ALL-UNNAMED"
+            )
+            it.environment("KYTHE_CORPUS", ANDROIDX_CORPUS)
+            it.environment("KYTHE_KZIP_ENCODING", "proto")
+            it.environment(
+                "KYTHE_OUTPUT_FILE",
+                kzipOutputFile.get().asFile.relativeTo(checkoutRoot).path
+            )
+            it.environment("KYTHE_ROOT_DIRECTORY", checkoutRoot.path)
+            it.environment("KYTHE_VNAMES", vnamesJson.get().asFile.path)
+            it.workingDir = checkoutRoot
+        }
+    }
+
+    internal companion object {
+        fun setupProject(
+            project: Project,
+            javaInputs: JavaCompileInputs,
+        ) {
+            val annotationProcessorPaths =
+                project.objects.fileCollection().apply {
+                    project.tasks.withType(JavaCompile::class.java).configureEach {
+                        it.options.annotationProcessorPath?.let { path -> from(path) }
+                    }
+                }
+
+            val javacCompilerArgs =
+                project.objects.listProperty(String::class.java).apply {
+                    project.tasks.withType(JavaCompile::class.java).configureEach {
+                        addAll(it.options.compilerArgs)
+                    }
+                }
+
+            project.tasks
+                .register("generateJavaKzip", GenerateJavaKzipTask::class.java) { task ->
+                    task.apply {
+                        javaExtractorJar.set(
+                            File(
+                                project.getPrebuiltsRoot(),
+                                "build-tools/common/javac_extractor.jar"
+                            )
+                        )
+                        sourcePaths.setFrom(javaInputs.sourcePaths)
+                        vnamesJson.set(project.getVnamesJson())
+                        dependencyClasspath.setFrom(
+                            javaInputs.dependencyClasspath + javaInputs.bootClasspath
+                        )
+                        kzipOutputFile.set(
+                            project.layout.buildDirectory.file(
+                                "kzips/${project.group}-${project.name}.java.kzip"
+                            )
+                        )
+                        kytheBuildDirectory.set(
+                            project.layout.buildDirectory.dir("kythe-java-classes")
+                        )
+                        annotationProcessor.setFrom(annotationProcessorPaths)
+                        this.javacCompilerArgs.set(javacCompilerArgs)
+                        // Needed so generated files (e.g. protos) are present when generating kzip
+                        // Without this, javac_extractor will throw a compilation error
+                        dependsOn(project.tasks.withType(JavaCompile::class.java))
+                    }
+                }
+                .also { project.addToBuildOnServer(it) }
+        }
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/kythe/GenerateKotlinKzipTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/kythe/GenerateKotlinKzipTask.kt
index b6246c99..536f9cc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/kythe/GenerateKotlinKzipTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/kythe/GenerateKotlinKzipTask.kt
@@ -22,7 +22,6 @@
 import androidx.build.getCheckoutRoot
 import androidx.build.getOperatingSystem
 import androidx.build.getPrebuiltsRoot
-import androidx.build.getSupportRootFolder
 import androidx.build.java.JavaCompileInputs
 import androidx.build.multiplatformExtension
 import java.io.File
@@ -164,7 +163,7 @@
             addAll(
                 listOf(
                     "-corpus",
-                    "android.googlesource.com/platform/frameworks/support//androidx-main",
+                    ANDROIDX_CORPUS,
                     "-kotlin_out",
                     compiledSources.singleFile.relativeTo(checkoutRoot).path,
                     "-o",
@@ -186,7 +185,7 @@
         }
     }
 
-    companion object {
+    internal companion object {
         fun setupProject(
             project: Project,
             javaInputs: JavaCompileInputs,
@@ -194,21 +193,6 @@
             kotlinTarget: Property<KotlinTarget>,
             javaVersion: JavaVersion,
         ) {
-            // TODO(b/379936315): Make these compatible with koltinc/javac that indexer is using
-            if (
-                project.path in
-                    listOf(
-                        // Uses Java 9+ APIs, which are not part of any dependency in the classpath
-                        ":room:room-compiler-processing",
-                        ":room:room-compiler-processing-testing",
-                        // KSP generated folders not visible to AGP variant api (b/380363756)
-                        ":privacysandbox:tools:integration-tests:testsdk",
-                        ":room:room-runtime"
-                    )
-            ) {
-                return
-            }
-
             val kotlincFreeCompilerArgs =
                 project.objects.listProperty(String::class.java).apply {
                     project.tasks.withType(KotlinCompilationTask::class.java).configureEach {
@@ -226,7 +210,7 @@
                         )
                         sourcePaths.setFrom(javaInputs.sourcePaths)
                         commonModuleSourcePaths.from(javaInputs.commonModuleSourcePaths)
-                        vnamesJson.set(File(project.getSupportRootFolder(), "buildSrc/vnames.json"))
+                        vnamesJson.set(project.getVnamesJson())
                         dependencyClasspath.setFrom(
                             javaInputs.dependencyClasspath + javaInputs.bootClasspath
                         )
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt
index c28b599..65a84d0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/kythe/KzipTasks.kt
@@ -22,6 +22,8 @@
 import androidx.build.checkapi.configureJavaInputsAndManifest
 import androidx.build.checkapi.createReleaseApiConfiguration
 import androidx.build.getDefaultTargetJavaVersion
+import androidx.build.getSupportRootFolder
+import java.io.File
 import org.gradle.api.Project
 
 /** Sets up tasks for generating kzip files that are used for generating xref support on website. */
@@ -31,17 +33,48 @@
     if (ProjectLayoutType.isPlayground(this)) {
         return
     }
+
+    // TODO(b/379936315): Make these compatible with koltinc/javac that indexer is using
+    if (
+        project.path in
+            listOf(
+                // Uses Java 9+ APIs, which are not part of any dependency in the classpath
+                ":room:room-compiler-processing",
+                ":room:room-compiler-processing-testing",
+                // KSP generated folders not visible to AGP variant api (b/380363756)
+                ":privacysandbox:tools:integration-tests:testsdk",
+                ":room:room-runtime",
+                // Javac extractor throws an error: package exists in another module:
+                // jdk.unsupported
+                ":performance:performance-unsafe",
+                // Depends on the generated output of the proto project
+                // :wear:protolayout:protolayout-proto
+                // which we haven't captured for Java Kzip generation.
+                ":wear:tiles:tiles-proto"
+            )
+    ) {
+        return
+    }
+
     // afterEvaluate required to read extension properties
     afterEvaluate {
         val (javaInputs, _) = configureJavaInputsAndManifest(config) ?: return@afterEvaluate
-        val generateApiDependencies = createReleaseApiConfiguration()
+        val compiledSources = createReleaseApiConfiguration()
 
         GenerateKotlinKzipTask.setupProject(
             project,
             javaInputs,
-            generateApiDependencies,
+            compiledSources,
             extension.kotlinTarget,
             getDefaultTargetJavaVersion(extension.type, project.name)
         )
+
+        GenerateJavaKzipTask.setupProject(project, javaInputs)
     }
 }
+
+internal const val ANDROIDX_CORPUS =
+    "android.googlesource.com/platform/frameworks/support//androidx-main"
+
+internal fun Project.getVnamesJson(): File =
+    File(project.getSupportRootFolder(), "buildSrc/vnames.json")
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 95262c4..523289c 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2573,6 +2573,10 @@
     method public default int minIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope, java.util.List<? extends java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>> measurables, int height);
   }
 
+  public final class OnGlobalLayoutListenerKt {
+    method public static kotlinx.coroutines.DisposableHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, int throttleMs, int debounceMs, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RectInfo,kotlin.Unit> callback);
+  }
+
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
     method public void onGloballyPositioned(androidx.compose.ui.layout.LayoutCoordinates coordinates);
   }
@@ -4010,6 +4014,7 @@
 package androidx.compose.ui.spatial {
 
   public final class RectInfo {
+    method public java.util.List<androidx.compose.ui.unit.IntRect> calculateOcclusions();
     method public int getHeight();
     method public long getPositionInRoot();
     method public long getPositionInScreen();
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index e5551c9..85924e2 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2581,6 +2581,10 @@
     method @kotlin.PublishedApi internal static androidx.compose.ui.layout.MeasurePolicy createMeasurePolicy(androidx.compose.ui.layout.MultiContentMeasurePolicy measurePolicy);
   }
 
+  public final class OnGlobalLayoutListenerKt {
+    method public static kotlinx.coroutines.DisposableHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, int throttleMs, int debounceMs, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RectInfo,kotlin.Unit> callback);
+  }
+
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
     method public void onGloballyPositioned(androidx.compose.ui.layout.LayoutCoordinates coordinates);
   }
@@ -4071,6 +4075,7 @@
 package androidx.compose.ui.spatial {
 
   public final class RectInfo {
+    method public java.util.List<androidx.compose.ui.unit.IntRect> calculateOcclusions();
     method public int getHeight();
     method public long getPositionInRoot();
     method public long getPositionInScreen();
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index 74fd029..84314e3e 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -63,6 +63,7 @@
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.spatial.RectManager
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
@@ -3420,6 +3421,9 @@
     override val pointerIconService: PointerIconService
         get() = TODO("Not yet implemented")
 
+    override val semanticsOwner: SemanticsOwner
+        get() = TODO("Not yet implemented")
+
     override val focusOwner: FocusOwner
         get() = TODO("Not yet implemented")
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index c14f441..3f9150a 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -58,6 +58,7 @@
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.spatial.RectManager
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
@@ -2910,6 +2911,9 @@
     override val pointerIconService: PointerIconService
         get() = TODO("Not yet implemented")
 
+    override val semanticsOwner: SemanticsOwner
+        get() = TODO("Not yet implemented")
+
     override val focusOwner: FocusOwner
         get() = TODO("Not yet implemented")
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
index c96e64d..76aa7ce 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
@@ -19,6 +19,7 @@
 package androidx.compose.ui.layout
 
 import androidx.collection.IntObjectMap
+import androidx.collection.intObjectMapOf
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.InternalComposeUiApi
 import androidx.compose.ui.autofill.Autofill
@@ -54,6 +55,8 @@
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.semantics.EmptySemanticsModifier
+import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.spatial.RectManager
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
@@ -165,8 +168,10 @@
 
     override fun onDetach(node: LayoutNode) {}
 
-    override val root: LayoutNode
-        get() = TODO("Not yet implemented")
+    override val root: LayoutNode = LayoutNode()
+
+    override val semanticsOwner: SemanticsOwner
+        get() = SemanticsOwner(root, EmptySemanticsModifier(), intObjectMapOf())
 
     override val layoutNodes: IntObjectMap<LayoutNode>
         get() = TODO("Not yet implemented")
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListenerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListenerTest.kt
new file mode 100644
index 0000000..089021b
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListenerTest.kt
@@ -0,0 +1,623 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.layout
+
+import androidx.collection.mutableIntSetOf
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.requireOwner
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.spatial.RectInfo
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertAll
+import androidx.compose.ui.test.assertCountEquals
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithTag
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.util.fastRoundToInt
+import androidx.compose.ui.zIndex
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class OnGlobalLayoutListenerTest {
+    @get:Rule val rule = createComposeRule()
+
+    private val targetTag = "target"
+
+    private val occlusionTag = "occluding"
+
+    @Test
+    fun notOccludingSiblings_whenNotOverlapping() =
+        with(rule.density) {
+            val rootSizePx = 300f
+            val itemSizePx = 100f
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    Box(Modifier.size(itemSizePx.toDp()).testTag(targetTag))
+                    Box(Modifier.size(itemSizePx.toDp()).testTag(occlusionTag))
+                }
+            }
+            rule.onNodeWithTag(targetTag).assertNoOcclusions()
+        }
+
+    @Test
+    fun notOccludingSiblings_whenPlacedBeforeWithHigherZIndex() =
+        with(rule.density) {
+            val rootSizePx = 150f
+            val itemSizePx = 100f
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    Box(Modifier.size(itemSizePx.toDp()).zIndex(zIndex = 1f).testTag(targetTag))
+                    Box(Modifier.size(itemSizePx.toDp()).zIndex(zIndex = 0f).testTag(occlusionTag))
+                }
+            }
+            rule.onNodeWithTag(targetTag).assertNoOcclusions()
+        }
+
+    @Test
+    fun notOccludingSiblings_whenPlacedAfter() =
+        with(rule.density) {
+            val rootSizePx = 150f
+            val itemSizePx = 100f
+
+            rule.setContent {
+                Box(Modifier.size(rootSizePx.toDp())) {
+                    Box(Modifier.size(itemSizePx.toDp()).testTag(occlusionTag))
+                    Box(Modifier.size(itemSizePx.toDp()).testTag(targetTag))
+                }
+            }
+            rule.onNodeWithTag(targetTag).assertNoOcclusions()
+        }
+
+    @Test
+    fun notOccludingByChildrenAndGrandChildren() =
+        with(rule.density) {
+            val itemSizePx = 100f
+
+            rule.setContent {
+                Box(
+                    // Root Composable cannot be occluded
+                    Modifier.testTag(targetTag)
+                ) {
+                    Row { repeat(3) { Box(Modifier.size(itemSizePx.toDp())) } }
+                    Column { repeat(3) { Box(Modifier.size(itemSizePx.toDp())) } }
+                }
+            }
+            rule.onNodeWithTag(targetTag).assertNoOcclusions()
+        }
+
+    @Test
+    fun notOccluding_whenBranchingParentIsPlacedBeforeWithHigherZIndex() =
+        with(rule.density) {
+            // Size that forces overlap
+            val rootSizePx = 150f
+            val itemSizePx = 100f
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    // Branching parent is above its sibling, despite being placed before
+                    Column(Modifier.zIndex(1f)) {
+                        Box(Modifier.size(itemSizePx.toDp()).testTag(targetTag))
+                    }
+                    Column(Modifier.zIndex(0f).testTag(occlusionTag)) {
+                        Box(Modifier.size(itemSizePx.toDp()).testTag(occlusionTag))
+                    }
+                }
+            }
+            rule.onNodeWithTag(targetTag).assertNoOcclusions()
+        }
+
+    @Test
+    fun occludingSiblings_whenPlacedBefore() =
+        with(rule.density) {
+            val rootSizePx = 150f
+            val itemSizePx = 100f
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    Box(Modifier.size(itemSizePx.toDp()).testTag(targetTag))
+                    Box(Modifier.size(itemSizePx.toDp()).testTag(occlusionTag))
+                }
+            }
+            rule
+                .onNodeWithTag(targetTag)
+                .assertOcclusions(occludingTag = occlusionTag, expectedCount = 1)
+        }
+
+    @Test
+    fun occludingSiblings_whenPlacedAfterWithLowerZIndex() =
+        with(rule.density) {
+            val rootSizePx = 150f
+            val itemSizePx = 100f
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    Box(Modifier.size(itemSizePx.toDp()).zIndex(1f).testTag(occlusionTag))
+                    Box(Modifier.size(itemSizePx.toDp()).zIndex(0f).testTag(targetTag))
+                }
+            }
+            rule
+                .onNodeWithTag(targetTag)
+                .assertOcclusions(occludingTag = occlusionTag, expectedCount = 1)
+        }
+
+    @Test
+    fun occludingSiblings_afterRotatingTargetOnZAxis() =
+        with(rule.density) {
+            val rootSizePx = 200f
+            val itemHeightPx = 90f
+
+            var degrees by mutableFloatStateOf(0f)
+
+            rule.setContent {
+                Column(
+                    modifier = Modifier.size(rootSizePx.toDp()),
+                    verticalArrangement = Arrangement.SpaceBetween
+                ) {
+                    Box(
+                        Modifier.graphicsLayer {
+                            transformOrigin = TransformOrigin(0f, 1f)
+                            rotationZ = degrees
+                        }
+                    ) {
+                        Box(Modifier.fillMaxWidth().height(itemHeightPx.toDp()).testTag(targetTag))
+                    }
+                    Box(Modifier.fillMaxWidth().height(itemHeightPx.toDp()).testTag(occlusionTag))
+                }
+            }
+            rule.onNodeWithTag(targetTag).assertNoOcclusions()
+
+            degrees = 90f
+            rule.waitForIdle()
+
+            rule
+                .onNodeWithTag(targetTag)
+                .assertOcclusions(occludingTag = occlusionTag, expectedCount = 1)
+        }
+
+    @Test
+    fun occludingTopBar_whenTargetIsScrolled() =
+        with(rule.density) {
+            // Pick sizes to avoid pixel rounding error
+            val topBarHeightPx = 49f
+            val topBarComponentWidthPx = 52f
+
+            val columnBoxSizePx = 50f
+
+            val scrollState = ScrollState(0)
+
+            rule.setContent {
+                Scaffold(
+                    // Top bar root component and one child will occlude target after scroll
+                    topBar = {
+                        Row(
+                            modifier = Modifier.height(topBarHeightPx.toDp()).testTag(occlusionTag),
+                            horizontalArrangement = Arrangement.SpaceBetween
+                        ) {
+                            Box(
+                                Modifier.fillMaxHeight()
+                                    .width(topBarComponentWidthPx.toDp())
+                                    .testTag(occlusionTag)
+                            )
+
+                            // This will not occlude due to the size of the target Box
+                            Box(Modifier.fillMaxHeight().width(topBarComponentWidthPx.toDp()))
+                        }
+                    }
+                ) { paddingValues ->
+                    Column(
+                        modifier =
+                            Modifier.padding(paddingValues = paddingValues)
+                                // Extra padding to avoid adjacency collision
+                                .padding(top = 2f.toDp())
+                                // Will only fit one box, so that it can scroll up
+                                .size(columnBoxSizePx.toDp())
+                                .verticalScroll(scrollState),
+                        verticalArrangement = Arrangement.spacedBy(2f.toDp())
+                    ) {
+                        Box(Modifier.size(columnBoxSizePx.toDp()).testTag(targetTag))
+                        Box(Modifier.size(columnBoxSizePx.toDp()))
+                    }
+                }
+            }
+            rule.onNodeWithTag(targetTag).assertNoOcclusions()
+
+            rule.runOnIdle { runBlocking { scrollState.scrollBy(columnBoxSizePx) } }
+            rule.waitForIdle()
+
+            rule
+                .onNodeWithTag(targetTag)
+                .assertOcclusions(occludingTag = occlusionTag, expectedCount = 2)
+        }
+
+    @Test
+    fun occluding_whenBranchingParentIsPlacedAfterWithLowerZIndex() =
+        with(rule.density) {
+            // Size that forces overlap
+            val rootSizePx = 150f
+            val itemSizePx = 100f
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    Column(Modifier.zIndex(1f).testTag(occlusionTag)) {
+                        Box(Modifier.size(itemSizePx.toDp()).testTag(occlusionTag))
+                    }
+                    // Branching parent is below its sibling, it should not occlude its own child
+                    Column(Modifier.zIndex(0f)) {
+                        Box(Modifier.size(itemSizePx.toDp()).testTag(targetTag))
+                    }
+                }
+            }
+            rule.waitForIdle()
+
+            rule
+                .onNodeWithTag(targetTag)
+                .assertOcclusions(occludingTag = occlusionTag, expectedCount = 2)
+        }
+
+    @Test
+    fun globalLayoutListenerCallback_whenTargetChangesIntoOcclusion() =
+        with(rule.density) {
+            val rootSizePx = 300f
+            val itemSizePx = 100f
+
+            var sizeMultiplier by mutableFloatStateOf(1f)
+
+            var width = -1
+            var occlusionCount = -1
+            var countDown = CountDownLatch(1)
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    Box(
+                        Modifier.testGlobalLayoutListener { rectInfo ->
+                                // Check rectInfo is also updated properly
+                                width = rectInfo.width
+                                occlusionCount = rectInfo.calculateOcclusions().size
+                                countDown.countDown()
+                            }
+                            .size((itemSizePx * sizeMultiplier).toDp())
+                    )
+                    Box(Modifier.size(itemSizePx.toDp()))
+                }
+            }
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            assertEquals(100, width)
+            assertEquals(0, occlusionCount)
+
+            countDown = CountDownLatch(1)
+            sizeMultiplier = 3f
+            rule.waitForIdle()
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            assertEquals(300, width)
+            assertEquals(1, occlusionCount)
+        }
+
+    // Tests that changes not related to the target node still trigger a callback
+    @Test
+    fun globalLayoutListener_whenSiblingChangesIntoOcclusion() =
+        with(rule.density) {
+            val rootSizePx = 300f
+            val itemSizePx = 100f
+
+            var sizeMultiplier by mutableFloatStateOf(1f)
+
+            var occlusionCount = -1
+            var countDown = CountDownLatch(1)
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size(rootSizePx.toDp())) {
+                    Box(
+                        Modifier.size(itemSizePx.toDp()).testGlobalLayoutListener { rectInfo ->
+                            occlusionCount = rectInfo.calculateOcclusions().size
+                            countDown.countDown()
+                        }
+                    )
+                    Box(Modifier.size((itemSizePx * sizeMultiplier).toDp()))
+                }
+            }
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            assertEquals(0, occlusionCount)
+
+            countDown = CountDownLatch(1)
+            // Changes the sibling size, should trigger a callback on the target node too
+            sizeMultiplier = 3f
+            rule.waitForIdle()
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            assertEquals(1, occlusionCount)
+        }
+
+    @Test
+    fun globalLayoutListener_whenParentChangesIntoOcclusion() =
+        with(rule.density) {
+            val rootSizePx = 300f
+            val itemSizePx = 100f
+
+            var sizeMultiplier by mutableFloatStateOf(1f)
+
+            var occlusionCount = -1
+            var countDown = CountDownLatch(1)
+
+            rule.setContent {
+                PlaceTwoLayoutsApartVert(Modifier.size((rootSizePx * sizeMultiplier).toDp())) {
+                    Box(
+                        Modifier.size(itemSizePx.toDp()).testGlobalLayoutListener { rectInfo ->
+                            occlusionCount = rectInfo.calculateOcclusions().size
+                            countDown.countDown()
+                        }
+                    )
+                    Box(Modifier.size(itemSizePx.toDp()))
+                }
+            }
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            assertEquals(0, occlusionCount)
+
+            countDown = CountDownLatch(1)
+            // Changes the parent size, the new size should case an occlusion between the children,
+            // and
+            // the callback should reflect on that change
+            sizeMultiplier = 1f / 3f
+            rule.waitForIdle()
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            assertEquals(1, occlusionCount)
+        }
+
+    @Test
+    fun globalLayoutListener_updatedOffset() =
+        with(rule.density) {
+            val rootSizePx = 300f
+            val itemSizePx = 100f
+
+            var sizeMultiplier by mutableFloatStateOf(1f)
+
+            var fromRoot = IntOffset(-1, -1)
+            var countDown = CountDownLatch(1)
+
+            rule.setContent {
+                Column(
+                    modifier = Modifier.size((rootSizePx).toDp()),
+                ) {
+                    Box(Modifier.size((itemSizePx * sizeMultiplier).toDp()))
+                    Box(
+                        Modifier.testGlobalLayoutListener { rectInfo ->
+                                fromRoot = rectInfo.positionInRoot
+                                countDown.countDown()
+                            }
+                            .size(itemSizePx.toDp())
+                    )
+                }
+            }
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            // Position should be the size of the first item
+            assertEquals(IntOffset(0, (itemSizePx * sizeMultiplier).fastRoundToInt()), fromRoot)
+
+            countDown = CountDownLatch(1)
+            sizeMultiplier = 2f
+            rule.waitForIdle()
+
+            assertTrue(countDown.await(1, TimeUnit.SECONDS))
+            assertEquals(IntOffset(0, (itemSizePx * sizeMultiplier).fastRoundToInt()), fromRoot)
+        }
+
+    /** Asserts that there are no occlusions around the current SemanticsNode. */
+    private fun SemanticsNodeInteraction.assertNoOcclusions() {
+        var callbackCount = 0
+        forEachOcclusionValue { _ -> callbackCount++ }
+        assertEquals(0, callbackCount)
+    }
+
+    /**
+     * Asserts expected amount of occlusions around the current SemanticsNode.
+     *
+     * It also asserts that every Node tagged with [occludingTag] corresponds to the Rects found to
+     * be occluding in RectManager.
+     */
+    private fun SemanticsNodeInteraction.assertOcclusions(
+        occludingTag: String,
+        expectedCount: Int
+    ) {
+        val occlusionSet = mutableIntSetOf()
+        var callbackCount = 0
+        forEachOcclusionValue { value ->
+            callbackCount++
+            occlusionSet.add(value)
+        }
+        // Assert that the expected amount of occlusions happened
+        assertEquals(expectedCount, callbackCount)
+        assertEquals(expectedCount, occlusionSet.size)
+
+        val nodeCollection = rule.onAllNodesWithTag(occludingTag)
+
+        // Assert that every tagged node is an occluding node
+        nodeCollection.assertAll(
+            SemanticsMatcher("Tagged node is occluding") { node ->
+                occlusionSet.contains(node.layoutNode.semanticsId)
+            }
+        )
+
+        // Assert that all occluding Rect had their Nodes tagged.
+        nodeCollection.assertCountEquals(expectedCount)
+    }
+
+    private inline fun SemanticsNodeInteraction.forEachOcclusionValue(block: (value: Int) -> Unit) {
+        val node = fetchSemanticsNode().layoutNode
+        val rectManager = node.requireOwner().rectManager
+        val rectList = rectManager.rects
+        val id = this.semanticsId()
+        val idIndex = rectList.indexOf(id)
+        if (idIndex < 0) {
+            return
+        }
+        rectList.forEachIntersectingRectWithValueAt(idIndex) { _, _, _, _, intersectingValue ->
+            if (rectManager.isTargetDrawnFirst(id, intersectingValue)) {
+                block(intersectingValue)
+            }
+        }
+    }
+
+    private fun Modifier.testGlobalLayoutListener(callback: (RectInfo) -> Unit) =
+        this then OnGlobaLayoutListenerElement(throttleMs = 0, debounceMs = 0, callback = callback)
+}
+
+/**
+ * Similar to Box when child0 is top aligned and child1 bottom aligned, but helps keeping the outer
+ * modifier clear.
+ */
+@Composable
+private fun PlaceTwoLayoutsApartVert(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit,
+) {
+    Layout(
+        measurePolicy = { measurables, constraints ->
+            require(constraints.hasFixedHeight)
+            require(measurables.size == 2)
+
+            val placeable0 = measurables[0].measure(Constraints())
+            val placeable1 = measurables[1].measure(Constraints())
+            val wrapWidth = maxOf(placeable0.width, placeable1.width)
+
+            val width =
+                if (constraints.hasFixedWidth) {
+                    constraints.maxWidth
+                } else if (constraints.hasBoundedWidth) {
+                    wrapWidth.coerceAtMost(constraints.maxWidth)
+                } else {
+                    maxOf(wrapWidth, constraints.minWidth)
+                }
+
+            layout(width, constraints.maxHeight) {
+                placeable0.place(0, 0)
+                placeable1.place(0, constraints.maxHeight - placeable1.height)
+            }
+        },
+        modifier = modifier,
+        content = content
+    )
+}
+
+private data class OnGlobaLayoutListenerElement(
+    val throttleMs: Int,
+    val debounceMs: Int,
+    val callback: (RectInfo) -> Unit,
+) : ModifierNodeElement<OnGlobalLayoutListenerNode>() {
+    override fun create(): OnGlobalLayoutListenerNode =
+        OnGlobalLayoutListenerNode(
+            throttleMs = throttleMs,
+            debounceMs = debounceMs,
+            callback = callback
+        )
+
+    override fun update(node: OnGlobalLayoutListenerNode) {
+        node.throttleMs = throttleMs
+        node.debounceMs = debounceMs
+        node.callback = callback
+        node.diposeAndRegister()
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "onLayoutCalculatorChanged"
+        properties["throttleMs"] = throttleMs
+        properties["debounceMs"] = debounceMs
+        properties["callback"] = callback
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as OnGlobaLayoutListenerElement
+
+        if (throttleMs != other.throttleMs) return false
+        if (debounceMs != other.debounceMs) return false
+        if (callback != other.callback) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = throttleMs
+        result = 31 * result + debounceMs
+        result = 31 * result + callback.hashCode()
+        return result
+    }
+}
+
+private class OnGlobalLayoutListenerNode(
+    var throttleMs: Int,
+    var debounceMs: Int,
+    var callback: (RectInfo) -> Unit,
+) : Modifier.Node() {
+    var handle: DisposableHandle? = null
+
+    fun diposeAndRegister() {
+        handle?.dispose()
+        handle = registerOnGlobalLayoutListener(throttleMs, debounceMs, callback)
+    }
+
+    override fun onAttach() {
+        diposeAndRegister()
+    }
+
+    override fun onDetach() {
+        handle?.dispose()
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalRectChangedTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalRectChangedTest.kt
index dc7f941..3ba9202 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalRectChangedTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalRectChangedTest.kt
@@ -23,7 +23,9 @@
 import android.widget.LinearLayout
 import android.widget.ScrollView
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
@@ -831,4 +833,81 @@
             assertThat(positionCalled2Count).isEqualTo(1)
         }
     }
+
+    @Test
+    fun occlusionCalculationOnRectChangedCallbacks() {
+        var box2Fraction by mutableStateOf(1f)
+
+        var box0RectInfo: RectInfo? = null
+
+        var box0Occlusions = emptyList<IntRect>()
+        var box1Occlusions = emptyList<IntRect>()
+        var box2Occlusions = emptyList<IntRect>()
+
+        var box0CallbackCount = 0
+        var box1CallbackCount = 0
+        var box2CallbackCount = 0
+        rule.setContent {
+            Box(Modifier.fillMaxSize()) {
+                Box(
+                    Modifier.fillMaxWidth()
+                        .fillMaxHeight(0.5f)
+                        .align(Alignment.TopStart)
+                        .onRectChanged(0, 0) { rectInfo ->
+                            box0RectInfo = rectInfo
+                            box0CallbackCount++
+                            box0Occlusions = rectInfo.calculateOcclusions()
+                        }
+                )
+                Box(
+                    // Should initially occlude first box
+                    Modifier.fillMaxWidth()
+                        .fillMaxHeight(0.7f)
+                        .align(Alignment.BottomStart)
+                        .onRectChanged(0, 0) { rectInfo ->
+                            box1CallbackCount++
+                            box1Occlusions = rectInfo.calculateOcclusions()
+                        }
+                )
+                Box(
+                    // Should initially occlude both boxes
+                    Modifier.fillMaxSize(box2Fraction).align(Alignment.BottomStart).onRectChanged(
+                        0,
+                        0
+                    ) { rectInfo ->
+                        box2CallbackCount++
+                        box2Occlusions = rectInfo.calculateOcclusions()
+                    }
+                )
+            }
+        }
+        rule.waitForIdle()
+
+        // Occlusion calculation should return expected result from each layout
+        assertThat(box0Occlusions.size).isEqualTo(2)
+        assertThat(box1Occlusions.size).isEqualTo(1)
+        assertThat(box2Occlusions.size).isEqualTo(0)
+
+        assertThat(box0CallbackCount).isEqualTo(1)
+        assertThat(box1CallbackCount).isEqualTo(1)
+        assertThat(box2CallbackCount).isEqualTo(1)
+
+        // We change box2 so that it no longer occludes box0
+        box2Fraction = 0.3f
+        rule.waitForIdle()
+
+        // Only box2 should receive a new callback, so most of the pre-existing information should
+        // remain the same, except for box2CallbackCount
+        assertThat(box0Occlusions.size).isEqualTo(2)
+        assertThat(box1Occlusions.size).isEqualTo(1)
+        assertThat(box2Occlusions.size).isEqualTo(0)
+
+        assertThat(box0CallbackCount).isEqualTo(1)
+        assertThat(box1CallbackCount).isEqualTo(1)
+        assertThat(box2CallbackCount).isEqualTo(2)
+
+        // Currently, it's possible to capture rectInfo and re-calculate occlusions
+        // The new calculation should reflect one less occluding box
+        assertThat(box0RectInfo!!.calculateOcclusions().size).isEqualTo(1)
+    }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
index bdaaf27..a36713d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
@@ -19,6 +19,7 @@
 package androidx.compose.ui.node
 
 import androidx.collection.IntObjectMap
+import androidx.collection.intObjectMapOf
 import androidx.compose.ui.InternalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.autofill.Autofill
@@ -49,6 +50,8 @@
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
 import androidx.compose.ui.platform.invertTo
+import androidx.compose.ui.semantics.EmptySemanticsModifier
+import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.spatial.RectManager
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
@@ -389,6 +392,11 @@
     override val layoutDirection: LayoutDirection
         get() = LayoutDirection.Ltr
 
+    override val rectManager: RectManager = RectManager()
+
+    override val semanticsOwner: SemanticsOwner =
+        SemanticsOwner(root, EmptySemanticsModifier(), intObjectMapOf())
+
     override val viewConfiguration: ViewConfiguration
         get() = TODO("Not yet implemented")
 
@@ -431,8 +439,6 @@
     override val windowInfo: WindowInfo
         get() = TODO("Not yet implemented")
 
-    override val rectManager: RectManager = RectManager()
-
     override val fontFamilyResolver: FontFamily.Resolver
         get() = TODO("Not yet implemented")
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsInfoTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsInfoTest.kt
new file mode 100644
index 0000000..dcabb22
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsInfoTest.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.semantics
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.RootForTest
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties.TestTag
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class SemanticsInfoTest {
+
+    @get:Rule val rule = createComposeRule()
+
+    lateinit var semanticsOwner: SemanticsOwner
+
+    @Test
+    fun contentWithNoSemantics() {
+        // Arrange.
+        rule.setTestContent { Box {} }
+        rule.waitForIdle()
+
+        // Act.
+        val rootSemantics = semanticsOwner.rootInfo
+
+        // Assert.
+        assertThat(rootSemantics).isNotNull()
+        assertThat(rootSemantics.parentInfo).isNull()
+        assertThat(rootSemantics.childrenInfo.size).isEqualTo(1)
+
+        // Assert extension Functions.
+        assertThat(rootSemantics.nearestParentThatHasSemantics()).isNull()
+        assertThat(rootSemantics.findMergingSemanticsParent()).isNull()
+        assertThat(rootSemantics.findSemanticsChildren()).isEmpty()
+    }
+
+    @Test
+    fun singleSemanticsModifier() {
+        // Arrange.
+        rule.setTestContent { Box(Modifier.semantics { this.testTag = "testTag" }) }
+        rule.waitForIdle()
+
+        // Act.
+        val rootSemantics = semanticsOwner.rootInfo
+        val semantics = rule.getSemanticsInfoForTag("testTag")!!
+
+        // Assert.
+        assertThat(rootSemantics.parentInfo).isNull()
+        assertThat(rootSemantics.childrenInfo.asMutableList()).containsExactly(semantics)
+
+        assertThat(semantics.parentInfo).isEqualTo(rootSemantics)
+        assertThat(semantics.childrenInfo.size).isEqualTo(0)
+
+        // Assert extension Functions.
+        assertThat(rootSemantics.nearestParentThatHasSemantics()).isNull()
+        assertThat(rootSemantics.findMergingSemanticsParent()).isNull()
+        assertThat(rootSemantics.findSemanticsChildren().map { it.semanticsConfiguration })
+            .comparingElementsUsing(SemanticsConfigurationComparator)
+            .containsExactly(SemanticsConfiguration().apply { testTag = "testTag" })
+
+        assertThat(semantics.nearestParentThatHasSemantics()).isEqualTo(rootSemantics)
+        assertThat(semantics.findMergingSemanticsParent()).isNull()
+        assertThat(semantics.findSemanticsChildren()).isEmpty()
+    }
+
+    @Test
+    fun twoSemanticsModifiers() {
+        // Arrange.
+        rule.setTestContent {
+            Box(Modifier.semantics { this.testTag = "item1" })
+            Box(Modifier.semantics { this.testTag = "item2" })
+        }
+        rule.waitForIdle()
+
+        // Act.
+        val rootSemantics: SemanticsInfo = semanticsOwner.rootInfo
+        val semantics1 = rule.getSemanticsInfoForTag("item1")
+        val semantics2 = rule.getSemanticsInfoForTag("item2")
+
+        // Assert.
+        assertThat(rootSemantics.parentInfo).isNull()
+        assertThat(rootSemantics.childrenInfo.map { it.semanticsConfiguration }.toList())
+            .comparingElementsUsing(SemanticsConfigurationComparator)
+            .containsExactly(
+                SemanticsConfiguration().apply { testTag = "item1" },
+                SemanticsConfiguration().apply { testTag = "item2" }
+            )
+            .inOrder()
+
+        assertThat(rootSemantics.findSemanticsChildren().map { it.semanticsConfiguration })
+            .comparingElementsUsing(SemanticsConfigurationComparator)
+            .containsExactly(
+                SemanticsConfiguration().apply { testTag = "item1" },
+                SemanticsConfiguration().apply { testTag = "item2" }
+            )
+            .inOrder()
+
+        checkNotNull(semantics1)
+        assertThat(semantics1.parentInfo).isEqualTo(rootSemantics)
+        assertThat(semantics1.childrenInfo.size).isEqualTo(0)
+
+        checkNotNull(semantics2)
+        assertThat(semantics2.parentInfo).isEqualTo(rootSemantics)
+        assertThat(semantics2.childrenInfo.size).isEqualTo(0)
+
+        // Assert extension Functions.
+        assertThat(rootSemantics.nearestParentThatHasSemantics()).isNull()
+        assertThat(rootSemantics.findMergingSemanticsParent()).isNull()
+        assertThat(rootSemantics.findSemanticsChildren().map { it.semanticsConfiguration })
+            .comparingElementsUsing(SemanticsConfigurationComparator)
+            .containsExactly(
+                SemanticsConfiguration().apply { testTag = "item1" },
+                SemanticsConfiguration().apply { testTag = "item2" }
+            )
+            .inOrder()
+
+        assertThat(semantics1.nearestParentThatHasSemantics()).isEqualTo(rootSemantics)
+        assertThat(semantics1.findMergingSemanticsParent()).isNull()
+        assertThat(semantics1.findSemanticsChildren()).isEmpty()
+
+        assertThat(semantics2.nearestParentThatHasSemantics()).isEqualTo(rootSemantics)
+        assertThat(semantics2.findMergingSemanticsParent()).isNull()
+        assertThat(semantics2.findSemanticsChildren()).isEmpty()
+    }
+
+    // TODO(ralu): Split this into multiple tests.
+    @Test
+    fun nodeDeepInHierarchy() {
+        // Arrange.
+        rule.setTestContent {
+            Column(Modifier.semantics(mergeDescendants = true) { testTag = "outerColumn" }) {
+                Row(Modifier.semantics { testTag = "outerRow" }) {
+                    Column(Modifier.semantics(mergeDescendants = true) { testTag = "column" }) {
+                        Row(Modifier.semantics { testTag = "row" }) {
+                            Column {
+                                Box(Modifier.semantics { testTag = "box" })
+                                Row(
+                                    Modifier.semantics {}
+                                        .semantics { testTag = "testTarget" }
+                                        .semantics { testTag = "extra modifier2" }
+                                ) {
+                                    Box { Box(Modifier.semantics { testTag = "child1" }) }
+                                    Box(Modifier.semantics { testTag = "child2" }) {
+                                        Box(Modifier.semantics { testTag = "grandChild" })
+                                    }
+                                    Box {}
+                                    Row {
+                                        Box {}
+                                        Box {}
+                                    }
+                                    Box { Box(Modifier.semantics { testTag = "child3" }) }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        val row = rule.getSemanticsInfoForTag(tag = "row", useUnmergedTree = true)
+        val column = rule.getSemanticsInfoForTag("column")
+
+        // Act.
+        val testTarget = rule.getSemanticsInfoForTag(tag = "testTarget", useUnmergedTree = true)
+
+        // Assert.
+        checkNotNull(testTarget)
+        assertThat(testTarget.parentInfo).isNotEqualTo(row)
+        assertThat(testTarget.nearestParentThatHasSemantics()).isEqualTo(row)
+        assertThat(testTarget.findMergingSemanticsParent()).isEqualTo(column)
+        assertThat(testTarget.childrenInfo.size).isEqualTo(5)
+        assertThat(testTarget.findSemanticsChildren().map { it.semanticsConfiguration })
+            .comparingElementsUsing(SemanticsConfigurationComparator)
+            .containsExactly(
+                SemanticsConfiguration().apply { testTag = "child1" },
+                SemanticsConfiguration().apply { testTag = "child2" },
+                SemanticsConfiguration().apply { testTag = "child3" }
+            )
+            .inOrder()
+        assertThat(testTarget.semanticsConfiguration?.getOrNull(TestTag)).isEqualTo("testTarget")
+    }
+
+    @Test
+    fun readingSemanticsConfigurationOfDeactivatedNode() {
+        // Arrange.
+        lateinit var lazyListState: LazyListState
+        lateinit var rootForTest: RootForTest
+        rule.setContent {
+            rootForTest = LocalView.current as RootForTest
+            lazyListState = rememberLazyListState()
+            LazyRow(state = lazyListState, modifier = Modifier.size(10.dp)) {
+                items(2) { index -> Box(Modifier.size(10.dp).testTag("$index")) }
+            }
+        }
+        val semanticsId = rule.onNodeWithTag("0").semanticsId()
+        val semanticsInfo = checkNotNull(rootForTest.semanticsOwner[semanticsId])
+
+        // Act.
+        rule.runOnIdle { lazyListState.requestScrollToItem(1) }
+        val semanticsConfiguration = rule.runOnIdle { semanticsInfo.semanticsConfiguration }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(semanticsInfo.isDeactivated).isTrue()
+            assertThat(semanticsConfiguration).isNull()
+        }
+    }
+
+    private fun ComposeContentTestRule.setTestContent(composable: @Composable () -> Unit) {
+        setContent {
+            semanticsOwner = (LocalView.current as RootForTest).semanticsOwner
+            composable()
+        }
+    }
+
+    /** Helper function that returns a list of children that is easier to assert on in tests. */
+    private fun SemanticsInfo.findSemanticsChildren(): List<SemanticsInfo> {
+        val children = mutableListOf<SemanticsInfo>()
+        [email protected] { children.add(it) }
+        return children
+    }
+
+    private fun ComposeContentTestRule.getSemanticsInfoForTag(
+        tag: String,
+        useUnmergedTree: Boolean = true
+    ): SemanticsInfo? {
+        return semanticsOwner[onNodeWithTag(tag, useUnmergedTree).semanticsId()]
+    }
+
+    companion object {
+        private val SemanticsConfigurationComparator =
+            Correspondence.from<SemanticsConfiguration, SemanticsConfiguration>(
+                { actual, expected ->
+                    actual != null &&
+                        expected != null &&
+                        actual.getOrNull(TestTag) == expected.getOrNull(TestTag)
+                },
+                "has same test tag as "
+            )
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsListenerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsListenerTest.kt
new file mode 100644
index 0000000..77965fe
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsListenerTest.kt
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.semantics
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ComposeUiFlags
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.isExactly
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color.Companion.Black
+import androidx.compose.ui.graphics.Color.Companion.Red
+import androidx.compose.ui.node.RootForTest
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.node.invalidateSemantics
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastJoinToString
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.Rule
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@MediumTest
+@RunWith(Parameterized::class)
+class SemanticsListenerTest(private val isSemanticAutofillEnabled: Boolean) {
+
+    @get:Rule val rule = createComposeRule()
+
+    private lateinit var semanticsOwner: SemanticsOwner
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "isSemanticAutofillEnabled = {0}")
+        fun initParameters() = listOf(false, true)
+    }
+
+    @Before
+    fun setup() {
+        @OptIn(ExperimentalComposeUiApi::class)
+        ComposeUiFlags.isSemanticAutofillEnabled = isSemanticAutofillEnabled
+    }
+
+    // Initial layout does not trigger listeners. Users have to detect the initial semantics
+    //  values by detecting first layout (You can get the bounds from RectManager.RectList).
+    @Test
+    fun initialComposition_doesNotTriggerListeners() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Text(text = "text")
+        }
+
+        // Assert.
+        rule.runOnIdle { assertThat(events).isEmpty() }
+    }
+
+    @Test
+    fun addingNonSemanticsModifier() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var addModifier by mutableStateOf(false)
+        val text = AnnotatedString("text")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Box(
+                modifier =
+                    Modifier.then(if (addModifier) Modifier.size(1000.dp) else Modifier)
+                        .semantics { this.text = text }
+                        .testTag("item")
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { addModifier = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(events).isEmpty() }
+    }
+
+    @Test
+    fun removingNonSemanticsModifier() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var removeModifier by mutableStateOf(false)
+        val text = AnnotatedString("text")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Box(
+                modifier =
+                    Modifier.then(if (removeModifier) Modifier else Modifier.size(1000.dp))
+                        .semantics { this.text = text }
+                        .testTag("item")
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { removeModifier = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(events).isEmpty() }
+    }
+
+    @Test
+    fun addingSemanticsModifier() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var addModifier by mutableStateOf(false)
+        val text = AnnotatedString("text")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Box(
+                modifier =
+                    Modifier.size(100.dp)
+                        .then(
+                            if (addModifier) Modifier.semantics { this.text = text } else Modifier
+                        )
+                        .testTag("item")
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { addModifier = true }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(Event(semanticsId, prevSemantics = null, newSemantics = "text"))
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun removingSemanticsModifier() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var removeModifier by mutableStateOf(false)
+        val text = AnnotatedString("text")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Box(
+                modifier =
+                    Modifier.size(1000.dp)
+                        .then(
+                            if (removeModifier) Modifier
+                            else Modifier.semantics { this.text = text }
+                        )
+                        .testTag("item")
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { removeModifier = true }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(Event(semanticsId, prevSemantics = "text", newSemantics = null))
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun changingMutableSemanticsProperty() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var text by mutableStateOf(AnnotatedString("text1"))
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Box(modifier = Modifier.semantics { this.text = text }.testTag("item"))
+        }
+
+        // Act.
+        rule.runOnIdle { text = AnnotatedString("text2") }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(Event(semanticsId, prevSemantics = "text1", newSemantics = "text2"))
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun changingMutableSemanticsProperty_alongWithRecomposition() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var text by mutableStateOf(AnnotatedString("text1"))
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Box(
+                modifier =
+                    Modifier.border(2.dp, if (text.text == "text1") Red else Black)
+                        .semantics { this.text = text }
+                        .testTag("item")
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { text = AnnotatedString("text2") }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(Event(semanticsId, prevSemantics = "text1", newSemantics = "text2"))
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun changingSemanticsProperty_andCallingInvalidateSemantics() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        val modifierNode =
+            object : SemanticsModifierNode, Modifier.Node() {
+                override fun SemanticsPropertyReceiver.applySemantics() {}
+            }
+        var text = AnnotatedString("text1")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Box(
+                modifier =
+                    Modifier.elementFor(modifierNode).semantics { this.text = text }.testTag("item")
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            text = AnnotatedString("text2")
+            modifierNode.invalidateSemantics()
+        }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(Event(semanticsId, prevSemantics = "text1", newSemantics = "text2"))
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun textChange() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var text by mutableStateOf("text1")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Text(text = text, modifier = Modifier.testTag("item"))
+        }
+
+        // Act.
+        rule.runOnIdle { text = "text2" }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(Event(semanticsId, prevSemantics = "text1", newSemantics = "text2"))
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun multipleTextChanges() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var text by mutableStateOf("text1")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(Event(info.semanticsId, prev?.Text, info.semanticsConfiguration?.Text))
+            }
+        ) {
+            Text(text = text, modifier = Modifier.testTag("item"))
+        }
+
+        // Act.
+        rule.runOnIdle { text = "text2" }
+        rule.runOnIdle { text = "text3" }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(
+                        Event(semanticsId, prevSemantics = "text1", newSemantics = "text2"),
+                        Event(semanticsId, prevSemantics = "text2", newSemantics = "text3")
+                    )
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun EditTextChange() {
+        // Arrange.
+        val events = mutableListOf<Event<String>>()
+        var text by mutableStateOf("text1")
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(
+                    Event(
+                        info.semanticsId,
+                        prev?.EditableText,
+                        info.semanticsConfiguration?.EditableText
+                    )
+                )
+            }
+        ) {
+            TextField(
+                value = text,
+                onValueChange = { text = it },
+                modifier = Modifier.testTag("item")
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { text = "text2" }
+
+        // Assert.
+        val semanticsId = rule.onNodeWithTag("item").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(Event(semanticsId, prevSemantics = "text1", newSemantics = "text2"))
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun FocusChange_withNoRecomposition() {
+        // Arrange.
+        val events = mutableListOf<Event<Boolean>>()
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(
+                    Event(
+                        info.semanticsId,
+                        prev?.getOrNull(SemanticsProperties.Focused),
+                        info.semanticsConfiguration?.getOrNull(SemanticsProperties.Focused)
+                    )
+                )
+            }
+        ) {
+            Column {
+                Box(Modifier.testTag("item1").size(100.dp).focusable())
+                Box(Modifier.testTag("item2").size(100.dp).focusable())
+            }
+        }
+        rule.onNodeWithTag("item1").requestFocus()
+        rule.runOnIdle { events.clear() }
+
+        // Act.
+        rule.onNodeWithTag("item2").requestFocus()
+
+        // Assert.
+        val item1 = rule.onNodeWithTag("item1").semanticsId
+        val item2 = rule.onNodeWithTag("item2").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(
+                        Event(item1, prevSemantics = true, newSemantics = false),
+                        Event(item2, prevSemantics = false, newSemantics = true)
+                    )
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    @Test
+    fun FocusChange_thatCausesRecomposition() {
+        // Arrange.
+        val events = mutableListOf<Event<Boolean>>()
+        rule.setTestContent(
+            onSemanticsChange = { info, prev ->
+                events.add(
+                    Event(
+                        info.semanticsId,
+                        prev?.getOrNull(SemanticsProperties.Focused),
+                        info.semanticsConfiguration?.getOrNull(SemanticsProperties.Focused)
+                    )
+                )
+            }
+        ) {
+            Column {
+                FocusableBox(Modifier.testTag("item1"))
+                FocusableBox(Modifier.testTag("item2"))
+            }
+        }
+        rule.onNodeWithTag("item1").requestFocus()
+        rule.runOnIdle { events.clear() }
+
+        // Act.
+        rule.onNodeWithTag("item2").requestFocus()
+
+        // Assert.
+        val item1 = rule.onNodeWithTag("item1").semanticsId
+        val item2 = rule.onNodeWithTag("item2").semanticsId
+        rule.runOnIdle {
+            if (isSemanticAutofillEnabled) {
+                assertThat(events)
+                    .isExactly(
+                        Event(item1, prevSemantics = true, newSemantics = false),
+                        Event(item2, prevSemantics = false, newSemantics = true)
+                    )
+            } else {
+                assertThat(events).isEmpty()
+            }
+        }
+    }
+
+    private val SemanticsConfiguration.Text
+        get() = getOrNull(SemanticsProperties.Text)?.fastJoinToString()
+
+    private val SemanticsConfiguration.EditableText
+        get() = getOrNull(SemanticsProperties.EditableText)?.toString()
+
+    private fun ComposeContentTestRule.setTestContent(
+        onSemanticsChange: (SemanticsInfo, SemanticsConfiguration?) -> Unit,
+        composable: @Composable () -> Unit
+    ) {
+        val semanticsListener =
+            object : SemanticsListener {
+                override fun onSemanticsChanged(
+                    semanticsInfo: SemanticsInfo,
+                    previousSemanticsConfiguration: SemanticsConfiguration?
+                ) {
+                    onSemanticsChange(semanticsInfo, previousSemanticsConfiguration)
+                }
+            }
+        setContent {
+            semanticsOwner = (LocalView.current as RootForTest).semanticsOwner
+            DisposableEffect(semanticsOwner) {
+                semanticsOwner.listeners.add(semanticsListener)
+                onDispose { semanticsOwner.listeners.remove(semanticsListener) }
+            }
+            composable()
+        }
+    }
+
+    data class Event<T>(val semanticsId: Int, val prevSemantics: T?, val newSemantics: T?)
+
+    // TODO(b/272068594): Add api to fetch the semantics id from SemanticsNodeInteraction directly.
+    private val SemanticsNodeInteraction.semanticsId: Int
+        get() = fetchSemanticsNode().id
+
+    @Composable
+    private fun FocusableBox(
+        modifier: Modifier = Modifier,
+        content: @Composable BoxScope.() -> Unit = {}
+    ) {
+        var borderColor by remember { mutableStateOf(Black) }
+        Box(
+            modifier =
+                modifier
+                    .size(100.dp)
+                    .onFocusChanged { borderColor = if (it.isFocused) Red else Black }
+                    .border(2.dp, borderColor)
+                    .focusable(),
+            content = content
+        )
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsModifierNodeTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsModifierNodeTest.kt
index 18aed18..d2cbd28 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsModifierNodeTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsModifierNodeTest.kt
@@ -23,6 +23,8 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.ComposeUiFlags
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.SemanticsModifierNode
 import androidx.compose.ui.node.elementOf
@@ -31,18 +33,31 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class SemanticsModifierNodeTest {
+@RunWith(Parameterized::class)
+class SemanticsModifierNodeTest(private val precomputedSemantics: Boolean) {
     @get:Rule val rule = createComposeRule()
 
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "pre-computed semantics = {0}")
+        fun initParameters() = listOf(false, true)
+    }
+
+    @Before
+    fun setup() {
+        @OptIn(ExperimentalComposeUiApi::class)
+        ComposeUiFlags.isSemanticAutofillEnabled = precomputedSemantics
+    }
+
     @Test
     fun applySemantics_firstComposition() {
         // Arrange.
@@ -50,7 +65,15 @@
         rule.setContent { Box(Modifier.elementOf(semanticsModifier)) }
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(0) }
+        rule.runOnIdle {
+            if (precomputedSemantics) {
+                // One invocation when the modifier node calls autoInvalidateNodeSelf and another
+                // when the Layout node is attached.
+                assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(2)
+            } else {
+                assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(0)
+            }
+        }
     }
 
     @Test
@@ -58,12 +81,16 @@
         // Arrange.
         val semanticsModifier = TestSemanticsModifier { testTag = "TestTag" }
         rule.setContent { Box(Modifier.elementOf(semanticsModifier)) }
+        rule.runOnIdle { semanticsModifier.resetCounters() }
 
         // Act.
         rule.onNodeWithTag("TestTag").assertExists()
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(1) }
+        rule.runOnIdle {
+            assertThat(semanticsModifier.applySemanticsInvocations)
+                .isEqualTo(if (precomputedSemantics) 0 else 1)
+        }
     }
 
     @Test
@@ -71,12 +98,16 @@
         // Arrange.
         val semanticsModifier = TestSemanticsModifier { testTag = "TestTag" }
         rule.setContent { Box(Modifier.elementOf(semanticsModifier)) }
+        rule.runOnIdle { semanticsModifier.resetCounters() }
 
         // Act.
         rule.onNodeWithTag("TestTag").fetchSemanticsNode()
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(1) }
+        rule.runOnIdle {
+            assertThat(semanticsModifier.applySemanticsInvocations)
+                .isEqualTo(if (precomputedSemantics) 0 else 1)
+        }
     }
 
     @Test
@@ -84,6 +115,7 @@
         // Arrange.
         val semanticsModifier = TestSemanticsModifier { testTag = "TestTag" }
         rule.setContent { Box(Modifier.elementOf(semanticsModifier)) }
+        rule.runOnIdle { semanticsModifier.resetCounters() }
 
         // Act.
         rule.onNodeWithTag("TestTag").fetchSemanticsNode()
@@ -91,7 +123,10 @@
         rule.onNodeWithTag("TestTag").fetchSemanticsNode()
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(1) }
+        rule.runOnIdle {
+            assertThat(semanticsModifier.applySemanticsInvocations)
+                .isEqualTo(if (precomputedSemantics) 0 else 1)
+        }
     }
 
     @Test
@@ -99,12 +134,16 @@
         // Arrange.
         val semanticsModifier = TestSemanticsModifier { testTag = "TestTag" }
         rule.setContent { Box(Modifier.elementOf(semanticsModifier)) }
+        rule.runOnIdle { semanticsModifier.resetCounters() }
 
         // Act.
         rule.runOnIdle { semanticsModifier.invalidateSemantics() }
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(0) }
+        rule.runOnIdle {
+            assertThat(semanticsModifier.applySemanticsInvocations)
+                .isEqualTo(if (precomputedSemantics) 1 else 0)
+        }
     }
 
     @Test
@@ -112,6 +151,7 @@
         // Arrange.
         val semanticsModifier = TestSemanticsModifier { testTag = "TestTag" }
         rule.setContent { Box(Modifier.elementOf(semanticsModifier)) }
+        rule.runOnIdle { semanticsModifier.resetCounters() }
 
         // Act.
         rule.onNodeWithTag("TestTag").assertExists()
@@ -119,7 +159,10 @@
         rule.onNodeWithTag("TestTag").assertExists()
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(2) }
+        rule.runOnIdle {
+            assertThat(semanticsModifier.applySemanticsInvocations)
+                .isEqualTo(if (precomputedSemantics) 1 else 2)
+        }
     }
 
     @Test
@@ -139,7 +182,10 @@
         rule.onNodeWithTag("TestTag").assertExists()
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(2) }
+        rule.runOnIdle {
+            assertThat(semanticsModifier.applySemanticsInvocations)
+                .isEqualTo(if (precomputedSemantics) 8 else 2)
+        }
     }
 
     @Test
@@ -215,7 +261,7 @@
         // Assert.
         rule.onNodeWithTag("0").assertDoesNotExist()
         assertThat(semanticsModifiers[0].applySemanticsInvocations).isEqualTo(0)
-        assertThat(semanticsModifiers[0].requireLayoutNode().collapsedSemantics).isNull()
+        assertThat(semanticsModifiers[0].requireLayoutNode().semanticsConfiguration).isNull()
         assertThat(semanticsModifiers[0].applySemanticsInvocations).isEqualTo(0)
     }
 
@@ -237,7 +283,7 @@
 
         // Assert.
         rule.onNodeWithTag("0").assertDoesNotExist()
-        assertThat(semanticsModifiers[0].requireLayoutNode().collapsedSemantics).isNull()
+        assertThat(semanticsModifiers[0].requireLayoutNode().semanticsConfiguration).isNull()
     }
 
     @Test
@@ -249,12 +295,17 @@
             semanticsModifier.invalidateSemantics()
         }
         rule.setContent { Box(Modifier.elementOf(semanticsModifier)) }
+        rule.runOnIdle { semanticsModifier.resetCounters() }
 
         // Act.
         rule.onNodeWithTag("tag").assertExists()
 
         // Assert.
-        rule.runOnIdle { assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(1) }
+        if (precomputedSemantics) {
+            assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(0)
+        } else {
+            assertThat(semanticsModifier.applySemanticsInvocations).isEqualTo(1)
+        }
     }
 
     private class TestSemanticsModifier(
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
index 60afb10..4f63ead 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
@@ -302,6 +302,9 @@
                     SemanticsContentDataType
                 )
         }
+
+    // TODO(b/138549623): Instead of creating a flattened tree by using the nodes from the map, we
+    //  can use SemanticsOwner to get the root SemanticsInfo and create a more representative tree.
     var index = AutofillApi26Helper.addChildCount(root, count)
 
     // Iterate through currentSemanticsNodes, finding autofill-related nodes
@@ -481,7 +484,7 @@
 }
 
 @RequiresApi(Build.VERSION_CODES.O)
-private class AutofillManagerWrapperImpl(val view: AndroidComposeView) : AutofillManagerWrapper {
+private class AutofillManagerWrapperImpl(val view: View) : AutofillManagerWrapper {
     override val autofillManager =
         view.context.getSystemService(PlatformAndroidManager::class.java)
             ?: error("Autofill service could not be located.")
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 1b595e5..49f669b 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -457,7 +457,8 @@
 
     override val rootForTest: RootForTest = this
 
-    override val semanticsOwner: SemanticsOwner = SemanticsOwner(root, rootSemanticsNode)
+    override val semanticsOwner: SemanticsOwner =
+        SemanticsOwner(root, rootSemanticsNode, layoutNodes)
     private val composeAccessibilityDelegate = AndroidComposeViewAccessibilityDelegateCompat(this)
     internal var contentCaptureManager =
         AndroidContentCaptureManager(
@@ -1634,7 +1635,7 @@
         contentCaptureManager.onLayoutChange(layoutNode)
     }
 
-    override val rectManager = RectManager()
+    override val rectManager = RectManager(layoutNodes)
 
     override fun onLayoutNodeDeactivated(layoutNode: LayoutNode) {
         @OptIn(ExperimentalComposeUiApi::class)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index d0c32c1..811066f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -2020,11 +2020,11 @@
             if (layoutNode.nodes.has(Nodes.Semantics)) layoutNode
             else layoutNode.findClosestParentNode { it.nodes.has(Nodes.Semantics) }
 
-        val config = semanticsNode?.collapsedSemantics ?: return
+        val config = semanticsNode?.semanticsConfiguration ?: return
         if (!config.isMergingSemanticsOfDescendants) {
             semanticsNode
                 .findClosestParentNode {
-                    it.collapsedSemantics?.isMergingSemanticsOfDescendants == true
+                    it.semanticsConfiguration?.isMergingSemanticsOfDescendants == true
                 }
                 ?.let { semanticsNode = it }
         }
@@ -3288,12 +3288,12 @@
     val ancestor =
         layoutNode.findClosestParentNode {
             // looking for text field merging node
-            val ancestorSemanticsConfiguration = it.collapsedSemantics
+            val ancestorSemanticsConfiguration = it.semanticsConfiguration
             ancestorSemanticsConfiguration?.isMergingSemanticsOfDescendants == true &&
                 ancestorSemanticsConfiguration.contains(SemanticsProperties.EditableText)
         }
     return ancestor != null &&
-        ancestor.collapsedSemantics?.getOrNull(SemanticsProperties.Focused) != true
+        ancestor.semanticsConfiguration?.getOrNull(SemanticsProperties.Focused) != true
 }
 
 private fun AccessibilityAction<*>.accessibilityEquals(other: Any?): Boolean {
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index b2845bc..f13b1f1 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -18,6 +18,7 @@
 package androidx.compose.ui.node
 
 import androidx.collection.IntObjectMap
+import androidx.collection.intObjectMapOf
 import androidx.compose.testutils.TestViewConfiguration
 import androidx.compose.ui.InternalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -67,8 +68,10 @@
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
 import androidx.compose.ui.platform.invertTo
+import androidx.compose.ui.semantics.EmptySemanticsModifier
 import androidx.compose.ui.semantics.SemanticsConfiguration
 import androidx.compose.ui.semantics.SemanticsModifier
+import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.spatial.RectManager
 import androidx.compose.ui.text.font.Font
@@ -2315,6 +2318,8 @@
 internal class MockOwner(
     private val position: IntOffset = IntOffset.Zero,
     override val root: LayoutNode = LayoutNode(),
+    override val semanticsOwner: SemanticsOwner =
+        SemanticsOwner(root, EmptySemanticsModifier(), intObjectMapOf()),
     override val coroutineContext: CoroutineContext =
         Executors.newFixedThreadPool(3).asCoroutineDispatcher()
 ) : Owner {
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
index bf672d2..4fdd4ea 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
@@ -19,6 +19,7 @@
 package androidx.compose.ui.node
 
 import androidx.collection.IntObjectMap
+import androidx.collection.intObjectMapOf
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -51,6 +52,8 @@
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.semantics.EmptySemanticsModifier
+import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.spatial.RectManager
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
@@ -336,8 +339,10 @@
 
         override fun onDetach(node: LayoutNode) {}
 
-        override val root: LayoutNode
-            get() = TODO("Not yet implemented")
+        override val root: LayoutNode = LayoutNode()
+
+        override val semanticsOwner: SemanticsOwner =
+            SemanticsOwner(root, EmptySemanticsModifier(), intObjectMapOf())
 
         override val layoutNodes: IntObjectMap<LayoutNode>
             get() = TODO("Not yet implemented")
@@ -450,7 +455,7 @@
         override fun forceMeasureTheSubtree(layoutNode: LayoutNode, affectsLookahead: Boolean) =
             TODO("Not yet implemented")
 
-        override fun onSemanticsChange() = TODO("Not yet implemented")
+        override fun onSemanticsChange() {}
 
         override fun onLayoutChange(layoutNode: LayoutNode) = TODO("Not yet implemented")
 
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt
index a98b7e9..ed03be2 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt
@@ -216,7 +216,7 @@
     }
 
     private fun ThrottledCallbacks.fire(id: Int) {
-        fire(id, 0, 0, currentTime)
+        fireOnUpdatedRect(id, 0, 0, currentTime)
     }
 
     private fun ThrottledCallbacks.register(
@@ -225,7 +225,7 @@
         debounceMs: Long,
         callback: (RectInfo) -> Unit
     ): DisposableHandle {
-        return register(id, throttleMs, debounceMs, fakeNode(), callback)
+        return registerOnRectChanged(id, throttleMs, debounceMs, fakeNode(), callback)
     }
 
     private inline fun test(block: ThrottledCallbacks.() -> Unit) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListener.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListener.kt
new file mode 100644
index 0000000..55a4457
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListener.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.layout
+
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.requireLayoutNode
+import androidx.compose.ui.node.requireOwner
+import androidx.compose.ui.spatial.RectInfo
+import kotlinx.coroutines.DisposableHandle
+
+/**
+ * Registers a [callback] to be executed with the position of this modifier node relative to the
+ * coordinate system of the root of the composition, as well as in screen coordinates and window
+ * coordinates, see [RectInfo].
+ *
+ * It may also be used to calculate certain Layout relationships at the time of the callback
+ * execution, such as [RectInfo.calculateOcclusions].
+ *
+ * This will be called after layout pass. This API allows for throttling and debouncing parameters
+ * in order to moderate the frequency with which the callback gets invoked during high rates of
+ * change (e.g. scrolling).
+ *
+ * Specifying [throttleMs] will prevent [callback] from being executed more than once over that time
+ * period. Specifying [debounceMs] will delay the execution of [callback] until that amount of time
+ * has elapsed without a new position.
+ *
+ * Specifying 0 for both [throttleMs] and [debounceMs] will result in the callback being executed
+ * every time the position has changed. Specifying non-zero amounts for both will result in both
+ * conditions being met.
+ *
+ * @param throttleMs The duration, in milliseconds, to prevent [callback] from being executed more
+ *   than once over that time period.
+ * @param debounceMs The duration, in milliseconds, to delay the execution of [callback] until that
+ *   amount of time has elapsed without a new position.
+ * @param callback The callback to be executed, provides a new [RectInfo] instance associated to
+ *   this [DelegatableNode]. Keep in mind this callback is executed on the main thread even when
+ *   debounced.
+ * @return an object which should be used to unregister/dispose this callback, such as when a node
+ *   is detached
+ */
+@Suppress("PairedRegistration") // User expected to handle disposing
+fun DelegatableNode.registerOnGlobalLayoutListener(
+    throttleMs: Int,
+    debounceMs: Int,
+    callback: (RectInfo) -> Unit
+): DisposableHandle {
+    val layoutNode = requireLayoutNode()
+    val id = layoutNode.semanticsId
+    val rectManager = layoutNode.requireOwner().rectManager
+    return rectManager.registerOnGlobalLayoutCallback(
+        id = id,
+        throttleMs = throttleMs,
+        debounceMs = debounceMs,
+        node = node,
+        callback = callback
+    )
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index a697f39..623b582 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -19,6 +19,8 @@
 import androidx.compose.runtime.CompositionLocalMap
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.ui.ComposeUiFlags
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.InternalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -54,6 +56,7 @@
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.simpleIdentityToString
 import androidx.compose.ui.semantics.SemanticsConfiguration
+import androidx.compose.ui.semantics.SemanticsInfo
 import androidx.compose.ui.semantics.generateSemanticsId
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
@@ -88,6 +91,7 @@
     Remeasurement,
     OwnerScope,
     LayoutInfo,
+    SemanticsInfo,
     ComposeUiNode,
     InteroperableComposeUiNode,
     Owner.OnLayoutCompletedListener {
@@ -394,29 +398,53 @@
         invalidateMeasurements()
     }
 
-    private var _collapsedSemantics: SemanticsConfiguration? = null
+    private var isSemanticsInvalidated = false
 
     internal fun invalidateSemantics() {
         // Ignore calls to invalidate Semantics while semantics are being applied (b/378114177).
         if (isCurrentlyCalculatingSemanticsConfiguration) return
 
-        _collapsedSemantics = null
-        // TODO(lmr): this ends up scheduling work that diffs the entire tree, but we should
-        //  eventually move to marking just this node as invalidated since we are invalidating
-        //  on a per-node level. This should preserve current behavior for now.
-        requireOwner().onSemanticsChange()
+        if (@OptIn(ExperimentalComposeUiApi::class) !ComposeUiFlags.isSemanticAutofillEnabled) {
+            _semanticsConfiguration = null
+
+            // TODO(lmr): this ends up scheduling work that diffs the entire tree, but we should
+            //  eventually move to marking just this node as invalidated since we are invalidating
+            //  on a per-node level. This should preserve current behavior for now..
+            requireOwner().onSemanticsChange()
+        } else if (nodes.isUpdating || applyingModifierOnAttach) {
+            // We are currently updating the modifier, so just schedule an invalidation. After
+            // applying the modifier, we will notify listeners of semantics changes.
+            isSemanticsInvalidated = true
+        } else {
+            // We are not currently updating the modifier, so instead of scheduling invalidation,
+            // we update the semantics configuration and send the notification event right away.
+            val prev = _semanticsConfiguration
+            _semanticsConfiguration = calculateSemanticsConfiguration()
+            isSemanticsInvalidated = false
+
+            val owner = requireOwner()
+            owner.semanticsOwner.notifySemanticsChange(this, prev)
+
+            // This is needed for Accessibility and ContentCapture. Remove after these systems
+            // are migrated to use SemanticsInfo and SemanticListeners.
+            owner.onSemanticsChange()
+        }
     }
 
-    internal val collapsedSemantics: SemanticsConfiguration?
+    // This is needed until we completely move to the new world where we always pre-compute the
+    // semantics configuration. At that point, this can just be a property with a private setter.
+    private var _semanticsConfiguration: SemanticsConfiguration? = null
+    override val semanticsConfiguration: SemanticsConfiguration?
         get() {
             // TODO: investigate if there's a better way to approach "half attached" state and
             // whether or not deactivated nodes should be considered removed or not.
             if (!isAttached || isDeactivated || !nodes.has(Nodes.Semantics)) return null
 
-            if (_collapsedSemantics == null) {
-                _collapsedSemantics = calculateSemanticsConfiguration()
+            @OptIn(ExperimentalComposeUiApi::class)
+            if (!ComposeUiFlags.isSemanticAutofillEnabled && _semanticsConfiguration == null) {
+                _semanticsConfiguration = calculateSemanticsConfiguration()
             }
-            return _collapsedSemantics
+            return _semanticsConfiguration
         }
 
     private var isCurrentlyCalculatingSemanticsConfiguration = false
@@ -474,7 +502,10 @@
         pendingModifier?.let { applyModifier(it) }
         pendingModifier = null
 
-        if (nodes.has(Nodes.Semantics)) {
+        // Note: With precomputed semantics config, calling invalidateSemantics() before the
+        // layoutNode is marked as attached would result in semantics not being calculated..
+        @OptIn(ExperimentalComposeUiApi::class)
+        if (!ComposeUiFlags.isSemanticAutofillEnabled && nodes.has(Nodes.Semantics)) {
             invalidateSemantics()
         }
         owner.onPreAttach(this)
@@ -507,6 +538,13 @@
         onAttach?.invoke(owner)
 
         layoutDelegate.updateParentData()
+
+        if (@OptIn(ExperimentalComposeUiApi::class) ComposeUiFlags.isSemanticAutofillEnabled) {
+            if (!isDeactivated && nodes.has(Nodes.Semantics)) {
+                invalidateSemantics()
+            }
+        }
+
         owner.onPostAttach(this)
     }
 
@@ -530,7 +568,8 @@
         layoutDelegate.resetAlignmentLines()
         onDetach?.invoke(owner)
 
-        if (nodes.has(Nodes.Semantics)) {
+        @OptIn(ExperimentalComposeUiApi::class)
+        if (!ComposeUiFlags.isSemanticAutofillEnabled && nodes.has(Nodes.Semantics)) {
             invalidateSemantics()
         }
         nodes.runDetachLifecycle()
@@ -543,6 +582,22 @@
         depth = 0
         measurePassDelegate.onNodeDetached()
         lookaheadPassDelegate?.onNodeDetached()
+
+        // Note: Don't call invalidateSemantics() from within detach() because the modifier nodes
+        // are detached before the LayoutNode, and invalidateSemantics() can trigger a call to
+        // calculateSemanticsConfiguration() which will encounter unattached nodes. Instead, just
+        // set the semantics configuration to null over here since we know the node is detached.
+        @OptIn(ExperimentalComposeUiApi::class)
+        if (ComposeUiFlags.isSemanticAutofillEnabled && nodes.has(Nodes.Semantics)) {
+            val prev = _semanticsConfiguration
+            _semanticsConfiguration = null
+            isSemanticsInvalidated = false
+            owner.semanticsOwner.notifySemanticsChange(this, prev)
+
+            // This is needed for Accessibility and ContentCapture. Remove after these systems
+            // are migrated to use SemanticsInfo and SemanticListeners.
+            owner.onSemanticsChange()
+        }
     }
 
     private val _zSortedChildren = mutableVectorOf<LayoutNode>()
@@ -567,6 +622,10 @@
             return _zSortedChildren
         }
 
+    @Suppress("UNCHECKED_CAST")
+    override val childrenInfo: MutableVector<SemanticsInfo>
+        get() = zSortedChildren as MutableVector<SemanticsInfo>
+
     override val isValidOwnerScope: Boolean
         get() = isAttached
 
@@ -878,6 +937,19 @@
         if (lookaheadRoot == null && nodes.has(Nodes.ApproachMeasure)) {
             lookaheadRoot = this
         }
+        // Notify semantics listeners if semantics was invalidated.
+        @OptIn(ExperimentalComposeUiApi::class)
+        if (ComposeUiFlags.isSemanticAutofillEnabled && isSemanticsInvalidated) {
+            val prev = _semanticsConfiguration
+            _semanticsConfiguration = calculateSemanticsConfiguration()
+            isSemanticsInvalidated = false
+            val owner = requireOwner()
+            owner.semanticsOwner.notifySemanticsChange(this, prev)
+
+            // This is needed for Accessibility and ContentCapture. Remove after these systems
+            // are migrated to use SemanticsInfo and SemanticListeners.
+            owner.onSemanticsChange()
+        }
     }
 
     private fun resetModifierState() {
@@ -1282,7 +1354,7 @@
         }
     }
 
-    override val parentInfo: LayoutInfo?
+    override val parentInfo: SemanticsInfo?
         get() = parent
 
     override var isDeactivated = false
@@ -1295,7 +1367,9 @@
         isCurrentlyCalculatingSemanticsConfiguration = false
         if (isDeactivated) {
             isDeactivated = false
-            invalidateSemantics()
+            if (@OptIn(ExperimentalComposeUiApi::class) !ComposeUiFlags.isSemanticAutofillEnabled) {
+                invalidateSemantics()
+            }
             // we don't need to reset state as it was done when deactivated
         } else {
             resetModifierState()
@@ -1306,6 +1380,10 @@
         // resetModifierState detaches all nodes, so we need to re-attach them upon reuse.
         nodes.markAsAttached()
         nodes.runAttachLifecycle()
+        @OptIn(ExperimentalComposeUiApi::class)
+        if (ComposeUiFlags.isSemanticAutofillEnabled && nodes.has(Nodes.Semantics)) {
+            invalidateSemantics()
+        }
         rescheduleRemeasureOrRelayout(this)
     }
 
@@ -1316,7 +1394,12 @@
         resetModifierState()
         // if the node is detached the semantics were already updated without this node.
         if (isAttached) {
-            invalidateSemantics()
+            if (@OptIn(ExperimentalComposeUiApi::class) !ComposeUiFlags.isSemanticAutofillEnabled) {
+                invalidateSemantics()
+            } else {
+                _semanticsConfiguration = null
+                isSemanticsInvalidated = false
+            }
         }
         owner?.onLayoutNodeDeactivated(this)
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
index ff19036..c08a929 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
@@ -40,8 +40,8 @@
     internal var head: Modifier.Node = tail
         private set
 
-    private val isUpdating: Boolean
-        get() = head === SentinelHead
+    internal val isUpdating: Boolean
+        get() = head.parent != null
 
     private val aggregateChildKindSet: Int
         get() = head.aggregateChildKindSet
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index 0ce80dd..f102b02 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -1512,7 +1512,7 @@
                 override fun interceptOutOfBoundsChildEvents(node: Modifier.Node) = false
 
                 override fun shouldHitTestChildren(parentLayoutNode: LayoutNode) =
-                    parentLayoutNode.collapsedSemantics?.isClearingSemantics != true
+                    parentLayoutNode.semanticsConfiguration?.isClearingSemantics != true
 
                 override fun childHitTest(
                     layoutNode: LayoutNode,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
index c1df2ef..2f4b773 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
@@ -48,6 +48,7 @@
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.spatial.RectManager
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
@@ -139,6 +140,13 @@
 
     val pointerIconService: PointerIconService
 
+    /**
+     * Semantics owner that provides access to
+     * [SemanticsInfo][androidx.compose.ui.semantics.SemanticsInfo] and
+     * [SemanticListeners][androidx.compose.ui.semantics.SemanticsListener].
+     */
+    val semanticsOwner: SemanticsOwner
+
     /** Provide a focus owner that controls focus within Compose. */
     val focusOwner: FocusOwner
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsInfo.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsInfo.kt
new file mode 100644
index 0000000..d6849c4
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsInfo.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.semantics
+
+import androidx.compose.runtime.collection.MutableVector
+import androidx.compose.ui.layout.LayoutInfo
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.node.LayoutNode
+
+/**
+ * This is an internal interface that can be used by [SemanticsListener]s to read semantic
+ * information from layout nodes. The root [SemanticsInfo] can be accessed using
+ * [SemanticsOwner.rootInfo], and particular [SemanticsInfo] can be looked up by their [semanticsId]
+ * by using [SemanticsOwner.get].
+ */
+internal interface SemanticsInfo : LayoutInfo {
+    /** The semantics configuration (Semantic properties and actions) associated with this node. */
+    val semanticsConfiguration: SemanticsConfiguration?
+
+    /**
+     * The [SemanticsInfo] of the parent.
+     *
+     * This includes parents that do not have any semantics modifiers.
+     */
+    override val parentInfo: SemanticsInfo?
+
+    /**
+     * Returns the children list sorted by their [LayoutNode.zIndex] first (smaller first) and the
+     * order they were placed via [Placeable.placeAt] by parent (smaller first). Please note that
+     * this list contains not placed items as well, so you have to manually filter them.
+     *
+     * Note that the object is reused so you shouldn't save it for later.
+     */
+    val childrenInfo: MutableVector<SemanticsInfo>
+}
+
+/** The semantics parent (nearest ancestor which has semantic properties). */
+internal fun SemanticsInfo.nearestParentThatHasSemantics(): SemanticsInfo? {
+    var parent = parentInfo
+    while (parent != null) {
+        if (parent.semanticsConfiguration != null) return parent
+        parent = parent.parentInfo
+    }
+    return null
+}
+
+/** The nearest semantics ancestor that is merging descendants. */
+internal fun SemanticsInfo.findMergingSemanticsParent(): SemanticsInfo? {
+    var parent = parentInfo
+    while (parent != null) {
+        if (parent.semanticsConfiguration?.isMergingSemanticsOfDescendants == true) return parent
+        parent = parent.parentInfo
+    }
+    return null
+}
+
+internal inline fun SemanticsInfo.findSemanticsChildren(
+    includeDeactivated: Boolean = false,
+    block: (SemanticsInfo) -> Unit
+) {
+    val unvisitedStack = MutableVector<SemanticsInfo>(childrenInfo.size)
+    childrenInfo.forEachReversed { unvisitedStack += it }
+    while (unvisitedStack.isNotEmpty()) {
+        val child = unvisitedStack.removeAt(unvisitedStack.lastIndex)
+        when {
+            child.isDeactivated && !includeDeactivated -> continue
+            child.semanticsConfiguration != null -> block(child)
+            else -> child.childrenInfo.forEachReversed { unvisitedStack += it }
+        }
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsListener.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsListener.kt
new file mode 100644
index 0000000..b51d7c8
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsListener.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.semantics
+
+/** A listener that can be used to observe semantic changes. */
+internal interface SemanticsListener {
+
+    /**
+     * [onSemanticsChanged] is called when the [SemanticsConfiguration] of a LayoutNode changes, or
+     * when a node calls SemanticsModifierNode.invalidateSemantics.
+     *
+     * @param semanticsInfo the current [SemanticsInfo] of the layout node that has changed.
+     * @param previousSemanticsConfiguration the previous [SemanticsConfiguration] associated with
+     *   the layout node.
+     */
+    fun onSemanticsChanged(
+        semanticsInfo: SemanticsInfo,
+        previousSemanticsConfiguration: SemanticsConfiguration?
+    )
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
index 2e38c29..84ae0fc 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
@@ -45,7 +45,7 @@
         layoutNode.nodes.head(Nodes.Semantics)!!.node,
         mergingEnabled,
         layoutNode,
-        layoutNode.collapsedSemantics ?: SemanticsConfiguration()
+        layoutNode.semanticsConfiguration ?: SemanticsConfiguration()
     )
 
 internal fun SemanticsNode(
@@ -70,7 +70,7 @@
         outerSemanticsNode.node,
         mergingEnabled,
         layoutNode,
-        layoutNode.collapsedSemantics ?: SemanticsConfiguration()
+        layoutNode.semanticsConfiguration ?: SemanticsConfiguration()
     )
 
 /**
@@ -99,7 +99,7 @@
             !isFake &&
                 replacedChildren.isEmpty() &&
                 layoutNode.findClosestParentNode {
-                    it.collapsedSemantics?.isMergingSemanticsOfDescendants == true
+                    it.semanticsConfiguration?.isMergingSemanticsOfDescendants == true
                 } == null
 
     /** The [LayoutInfo] that this is associated with. */
@@ -345,7 +345,7 @@
             if (mergingEnabled) {
                 node =
                     this.layoutNode.findClosestParentNode {
-                        it.collapsedSemantics?.isMergingSemanticsOfDescendants == true
+                        it.semanticsConfiguration?.isMergingSemanticsOfDescendants == true
                     }
             }
 
@@ -474,7 +474,9 @@
  * Executes [selector] on every parent of this [LayoutNode] and returns the closest [LayoutNode] to
  * return `true` from [selector] or null if [selector] returns false for all ancestors.
  */
-internal fun LayoutNode.findClosestParentNode(selector: (LayoutNode) -> Boolean): LayoutNode? {
+internal inline fun LayoutNode.findClosestParentNode(
+    selector: (LayoutNode) -> Boolean
+): LayoutNode? {
     var currentParent = this.parent
     while (currentParent != null) {
         if (selector(currentParent)) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt
index dffed0f..b987155 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.semantics
 
+import androidx.collection.IntObjectMap
+import androidx.collection.MutableObjectList
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.util.fastForEach
 
@@ -23,7 +25,8 @@
 class SemanticsOwner
 internal constructor(
     private val rootNode: LayoutNode,
-    private val outerSemanticsNode: EmptySemanticsModifier
+    private val outerSemanticsNode: EmptySemanticsModifier,
+    private val nodes: IntObjectMap<LayoutNode>
 ) {
     /**
      * The root node of the semantics tree. Does not contain any unmerged data. May contain merged
@@ -47,6 +50,22 @@
                 unmergedConfig = SemanticsConfiguration()
             )
         }
+
+    internal val listeners = MutableObjectList<SemanticsListener>(2)
+
+    internal val rootInfo: SemanticsInfo
+        get() = rootNode
+
+    internal operator fun get(semanticsId: Int): SemanticsInfo? {
+        return nodes[semanticsId]
+    }
+
+    internal fun notifySemanticsChange(
+        semanticsInfo: SemanticsInfo,
+        previousSemanticsConfiguration: SemanticsConfiguration?
+    ) {
+        listeners.forEach { it.onSemanticsChanged(semanticsInfo, previousSemanticsConfiguration) }
+    }
 }
 
 /**
@@ -70,6 +89,7 @@
         .toList()
 }
 
+@Suppress("unused")
 @Deprecated(message = "Use a new overload instead", level = DeprecationLevel.HIDDEN)
 fun SemanticsOwner.getAllSemanticsNodes(mergingEnabled: Boolean) =
     getAllSemanticsNodes(mergingEnabled, true)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectInfo.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectInfo.kt
index 3537a26..723389a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectInfo.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectInfo.kt
@@ -18,6 +18,9 @@
 
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.requireLayoutNode
+import androidx.compose.ui.node.requireOwner
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntRect
 import androidx.compose.ui.unit.roundToIntRect
@@ -35,6 +38,7 @@
     private val windowOffset: IntOffset,
     private val screenOffset: IntOffset,
     private val viewToWindowMatrix: Matrix?,
+    private val node: DelegatableNode,
 ) {
     /**
      * The top left position of the Rect in the coordinates of the root node of the compose
@@ -132,4 +136,42 @@
             val y = screenOffset.y
             return IntRect(l + x, t + y, r + x, b + y)
         }
+
+    /**
+     * At the current state of the layout, calculates which other Composable Layouts are occluding
+     * the Composable associated with this [RectInfo]. **Note**: Calling this method during measure
+     * or layout may result on calculations with stale (or partially stale) layout information.
+     *
+     * An occlusion is defined by an intersecting Composable that may draw on top of the target
+     * Composable.
+     *
+     * There's no guarantee that something was actually drawn to occlude, so a transparent
+     * Composable that could otherwise draw on top of the target is considered to be occluding.
+     *
+     * There's no differentiation between partial and complete occlusions, they are all included as
+     * part of this calculation.
+     *
+     * Ancestors, child and grandchild Layouts are never considered to be occluding.
+     *
+     * @return A [List] of the rectangles that occlude the associated Composable Layout.
+     */
+    fun calculateOcclusions(): List<IntRect> {
+        val rectManager = node.requireOwner().rectManager
+        val id = node.requireLayoutNode().semanticsId
+        val rectList = rectManager.rects
+        val idIndex = rectList.indexOf(id)
+        if (idIndex < 0) {
+            return emptyList()
+        }
+        // For the given `id`, finds intersections and determines occlusions by the result of
+        // the 'RectManager.isTargetDrawnFirst', if the node for 'id' is drawn first, then it's
+        // being occluded and the intersecting rect is added to the list result.
+        return buildList {
+            rectList.forEachIntersectingRectWithValueAt(idIndex) { l, t, r, b, intersectingId ->
+                if (rectManager.isTargetDrawnFirst(id, intersectingId)) {
+                    [email protected](IntRect(l, t, r, b))
+                }
+            }
+        }
+    }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectList.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectList.kt
index c041f20..259ae65 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectList.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectList.kt
@@ -397,6 +397,28 @@
         return false
     }
 
+    /**
+     * Returns the first index for the item that matches the metadata value of [value], returns -1
+     * if the item wasn't found.
+     *
+     * Note that returned index corresponds to the Long that contains the topLeft data of the item.
+     */
+    fun indexOf(value: Int): Int {
+        val value = value and Lower26Bits
+        val items = items
+        val size = itemsSize
+        var i = 0
+        while (i < items.size - 2) {
+            if (i >= size) break
+            val meta = items[i + 2]
+            if (unpackMetaValue(meta) == value) {
+                return i
+            }
+            i += LongsPerItem
+        }
+        return -1
+    }
+
     fun metaFor(value: Int): Long {
         val value = value and Lower26Bits
         val items = items
@@ -492,6 +514,42 @@
         }
     }
 
+    /**
+     * For the rectangle at the given [index], calls [block] for each other rectangles that
+     * intersects with it. The parameters in [block] are the intersecting rect and its 'value'.
+     */
+    inline fun forEachIntersectingRectWithValueAt(
+        index: Int,
+        block: (Int, Int, Int, Int, Int) -> Unit
+    ) {
+        val items = items
+        val size = itemsSize
+
+        val destTopLeft = items[index]
+        val destBottomRight = items[index + 1]
+
+        var i = 0
+        while (i < items.size - 2) {
+            if (i >= size) break
+            if (i == index) {
+                i += LongsPerItem
+                continue
+            }
+            val topLeft = items[i + 0]
+            val bottomRight = items[i + 1]
+            if (rectIntersectsRect(topLeft, bottomRight, destTopLeft, destBottomRight)) {
+                block(
+                    unpackX(topLeft),
+                    unpackY(topLeft),
+                    unpackX(bottomRight),
+                    unpackY(bottomRight),
+                    unpackMetaValue(items[i + 2])
+                )
+            }
+            i += LongsPerItem
+        }
+    }
+
     internal fun neighborsScoredByDistance(
         searchAxis: Int,
         l: Int,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt
index 894da0c..6e9cf7f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.spatial
 
+import androidx.collection.IntObjectMap
+import androidx.collection.intObjectMapOf
 import androidx.collection.mutableObjectListOf
 import androidx.compose.ui.ComposeUiFlags
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -38,8 +40,10 @@
 import kotlin.math.max
 import kotlinx.coroutines.DisposableHandle
 
-internal class RectManager {
-
+internal class RectManager(
+    /** [LayoutNode.semanticsId] to [LayoutNode] mapping, maintained by Owner. */
+    private val layoutNodes: IntObjectMap<LayoutNode> = intObjectMapOf(),
+) {
     val rects: RectList = RectList()
 
     private val throttledCallbacks = ThrottledCallbacks()
@@ -75,18 +79,26 @@
     // TODO: we need to make sure these are dispatched after draw if needed
     fun dispatchCallbacks() {
         val currentTime = currentTimeMillis()
+
+        // For ThrottledCallbacks on global changes we need to make sure they are all called for any
+        // change
+        val isDispatchGlobalCallbacks = isDirty || isScreenOrWindowDirty
+
         // TODO: we need to move this to avoid double-firing
         if (isDirty) {
             isDirty = false
             callbacks.forEach { it() }
             rects.forEachUpdatedRect { id, topLeft, bottomRight ->
-                throttledCallbacks.fire(id, topLeft, bottomRight, currentTime)
+                throttledCallbacks.fireOnUpdatedRect(id, topLeft, bottomRight, currentTime)
             }
             rects.clearUpdated()
         }
         if (isScreenOrWindowDirty) {
             isScreenOrWindowDirty = false
-            throttledCallbacks.fireAll(currentTime)
+            throttledCallbacks.fireOnRectChangedEntries(currentTime)
+        }
+        if (isDispatchGlobalCallbacks) {
+            throttledCallbacks.fireGlobalChangeEntries(currentTime)
         }
         // The hierarchy is "settled" in terms of nodes being added/removed for this frame
         // This makes it a reasonable time to "defragment" the RectList data structure. This
@@ -131,12 +143,12 @@
         rects.withRect(id) { l, t, r, b ->
             result =
                 rectInfoFor(
-                    node,
-                    packXY(l, t),
-                    packXY(r, b),
-                    throttledCallbacks.windowOffset,
-                    throttledCallbacks.screenOffset,
-                    throttledCallbacks.viewToWindowMatrix,
+                    node = node,
+                    topLeft = packXY(l, t),
+                    bottomRight = packXY(r, b),
+                    windowOffset = throttledCallbacks.windowOffset,
+                    screenOffset = throttledCallbacks.screenOffset,
+                    viewToWindowMatrix = throttledCallbacks.viewToWindowMatrix,
                 )
         }
         return result
@@ -154,7 +166,7 @@
         node: DelegatableNode,
         callback: (RectInfo) -> Unit
     ): DisposableHandle {
-        return throttledCallbacks.register(
+        return throttledCallbacks.registerOnRectChanged(
             id,
             throttleMs.toLong(),
             debounceMs.toLong(),
@@ -163,6 +175,22 @@
         )
     }
 
+    fun registerOnGlobalLayoutCallback(
+        id: Int,
+        throttleMs: Int,
+        debounceMs: Int,
+        node: DelegatableNode,
+        callback: (RectInfo) -> Unit
+    ): DisposableHandle {
+        return throttledCallbacks.registerOnGlobalChange(
+            id = id,
+            throttleMs = throttleMs.toLong(),
+            debounceMs = debounceMs.toLong(),
+            node = node,
+            callback = callback,
+        )
+    }
+
     fun unregisterOnChangedCallback(token: Any?) {
         @Suppress("UNCHECKED_CAST")
         token as? (() -> Unit) ?: return
@@ -409,6 +437,62 @@
         invalidate()
         isFragmented = true
     }
+
+    /**
+     * For occlusion calculation, returns whether the [LayoutNode] corresponding to [targetId] is
+     * drawn first compared to the [LayoutNode] corresponding to [otherId].
+     *
+     * @return true when the target will be drawn first, false for everything else, including when
+     *   either is an ancestor of the other.
+     */
+    internal fun isTargetDrawnFirst(targetId: Int, otherId: Int): Boolean {
+        var nodeA = layoutNodes[targetId] ?: return false
+        var nodeB = layoutNodes[otherId] ?: return false
+
+        if (nodeA.depth == 0 || nodeB.depth == 0) {
+            // Fail early when either is Root
+            return false
+        }
+
+        while (nodeA.depth > nodeB.depth) {
+            nodeA = nodeA.parent ?: return false // Early check avoids these null parent cases
+        }
+
+        if (nodeA === nodeB) {
+            // Node B was parent of Node A
+            return false
+        }
+
+        while (nodeB.depth > nodeA.depth) {
+            nodeB = nodeB.parent ?: return false
+        }
+
+        if (nodeA === nodeB) {
+            // Node A was parent of Node B
+            return false
+        }
+
+        // Keep track of the branching parent for both nodes, the draw order of these decide how
+        // [targetId] and [otherId] compare.
+        var lastParentA: LayoutNode = nodeA
+        var lastParentB: LayoutNode = nodeB
+
+        while (nodeA !== nodeB) {
+            lastParentA = nodeA
+            lastParentB = nodeB
+            // Null parent means we went past the root without finding common ancestor, shouldn't
+            // happen but we only return true when we know the target is drawn first
+            nodeA = nodeA.parent ?: return false
+            nodeB = nodeB.parent ?: return false
+        }
+
+        // Lower zIndex is drawn first, otherwise lower placeOrder decides draw order
+        if (lastParentA.measurePassDelegate.zIndex == lastParentB.measurePassDelegate.zIndex) {
+            return lastParentA.placeOrder < lastParentB.placeOrder
+        } else {
+            return lastParentA.measurePassDelegate.zIndex < lastParentB.measurePassDelegate.zIndex
+        }
+    }
 }
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt
index fdc24a4..b0f577b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt
@@ -30,6 +30,11 @@
 
 internal class ThrottledCallbacks {
 
+    /**
+     * Entry for a throttled callback for [RectInfo] associated to the given [node].
+     *
+     * Supports a linked-list structure for multiple callbacks on the same [node] through [next].
+     */
     inner class Entry(
         val id: Int,
         val throttleMillis: Long,
@@ -46,7 +51,8 @@
         var lastUninvokedFireMillis: Long = -1
 
         override fun dispose() {
-            map.multiRemove(id, this)
+            val result = rectChangedMap.multiRemove(id, this)
+            if (!result) removeFromGlobalEntries(this)
         }
 
         fun fire(
@@ -58,20 +64,30 @@
         ) {
             val rect =
                 rectInfoFor(
-                    node,
-                    topLeft,
-                    bottomRight,
-                    windowOffset,
-                    screenOffset,
-                    viewToWindowMatrix
+                    node = node,
+                    topLeft = topLeft,
+                    bottomRight = bottomRight,
+                    windowOffset = windowOffset,
+                    screenOffset = screenOffset,
+                    viewToWindowMatrix = viewToWindowMatrix,
                 )
-            if (rect != null) {
-                callback(rect)
+            if (rect == null) {
+                return
             }
+
+            callback(rect)
         }
     }
 
-    val map = mutableIntObjectMapOf<Entry>()
+    /** Set of callbacks for onRectChanged. */
+    val rectChangedMap = mutableIntObjectMapOf<Entry>()
+
+    /**
+     * Set of callbacks for onGlobalLayoutListener given as a Linked List using [Entry].
+     *
+     * These are expected to be fired after any Rect or Window/Screen change.
+     */
+    var globalChangeEntries: Entry? = null
 
     // We can use this to schedule a "triggerDebounced" call. If it is -1, then nothing
     // needs to be scheduled.
@@ -101,7 +117,7 @@
         return (x shr 3) shl 3
     }
 
-    fun register(
+    fun registerOnRectChanged(
         id: Int,
         throttleMs: Long,
         debounceMs: Long,
@@ -112,80 +128,89 @@
         // consumers will get the value where the node "settled".
         val debounceToUse = if (debounceMs == 0L) throttleMs else debounceMs
 
-        return map.multiPut(id, Entry(id, throttleMs, debounceToUse, node, callback))
+        return rectChangedMap.multiPut(
+            key = id,
+            value =
+                Entry(
+                    id = id,
+                    throttleMillis = throttleMs,
+                    debounceMillis = debounceToUse,
+                    node = node,
+                    callback = callback,
+                )
+        )
+    }
+
+    fun registerOnGlobalChange(
+        id: Int,
+        throttleMs: Long,
+        debounceMs: Long,
+        node: DelegatableNode,
+        callback: (RectInfo) -> Unit,
+    ): DisposableHandle {
+        // If zero is set for debounce, we use throttle in its place. This guarantees that
+        // consumers will get the value where the node "settled".
+        val debounceToUse = if (debounceMs == 0L) throttleMs else debounceMs
+
+        val entry =
+            Entry(
+                id = id,
+                throttleMillis = throttleMs,
+                debounceMillis = debounceToUse,
+                node = node,
+                callback = callback,
+            )
+        addToGlobalEntries(entry)
+        return entry
     }
 
     // We call this when a layout node with `semanticsId = id` changes it's global bounds. For
     // throttled callbacks this may cause the callback to get invoked, for debounced nodes it
     // updates the deadlines
-    fun fire(id: Int, topLeft: Long, bottomRight: Long, currentMillis: Long) {
-        map.runFor(id) { entry ->
-            val lastInvokeMillis = entry.lastInvokeMillis
-            val throttleMillis = entry.throttleMillis
-            val debounceMillis = entry.debounceMillis
-            val pastThrottleDeadline = currentMillis - lastInvokeMillis >= throttleMillis
-            val zeroDebounce = debounceMillis == 0L
-            val zeroThrottle = throttleMillis == 0L
-
-            entry.topLeft = topLeft
-            entry.bottomRight = bottomRight
-
-            // There are essentially 3 different cases that we need to handle here:
-
-            // 1. throttle = 0, debounce = 0
-            //      -> always invoke immediately
-            // 2. throttle = 0, debounce > 0
-            //      -> set deadline to <debounce> milliseconds from now
-            // 3. throttle > 0, debounce > 0
-            //      -> invoke if we haven't invoked for <throttle> milliseconds, otherwise, set the
-            //         deadline to <debounce>
-
-            // Note that the `throttle > 0, debounce = 0` case is not possible, since we use the
-            // throttle value as a debounce value in that case.
-
-            val canInvoke = (!zeroDebounce && !zeroThrottle) || zeroDebounce
-
-            if (pastThrottleDeadline && canInvoke) {
-                entry.lastUninvokedFireMillis = -1
-                entry.lastInvokeMillis = currentMillis
-                entry.fire(topLeft, bottomRight, windowOffset, screenOffset, viewToWindowMatrix)
-            } else if (!zeroDebounce) {
-                entry.lastUninvokedFireMillis = currentMillis
-                val currentMinDeadline = minDebounceDeadline
-                val thisDeadline = currentMillis + debounceMillis
-                if (currentMinDeadline > 0 && thisDeadline < currentMinDeadline) {
-                    minDebounceDeadline = currentMinDeadline
-                }
-            }
+    fun fireOnUpdatedRect(id: Int, topLeft: Long, bottomRight: Long, currentMillis: Long) {
+        rectChangedMap.runFor(id) { entry ->
+            fireWithUpdatedRect(entry, topLeft, bottomRight, currentMillis)
         }
     }
 
-    fun fireAll(currentMillis: Long) {
+    /** Fires all [rectChangedMap] entries with latest window/screen info. */
+    fun fireOnRectChangedEntries(currentMillis: Long) {
         val windowOffset = windowOffset
         val screenOffset = screenOffset
         val viewToWindowMatrix = viewToWindowMatrix
-        map.multiForEach { entry ->
-            val lastInvokeMillis = entry.lastInvokeMillis
-            val throttleOkay = currentMillis - lastInvokeMillis > entry.throttleMillis
-            val debounceOkay = entry.debounceMillis == 0L
-            entry.lastUninvokedFireMillis = currentMillis
-            if (throttleOkay && debounceOkay) {
-                entry.lastInvokeMillis = currentMillis
-                entry.fire(
-                    entry.topLeft,
-                    entry.bottomRight,
-                    windowOffset,
-                    screenOffset,
-                    viewToWindowMatrix
-                )
-            }
-            if (!debounceOkay) {
-                val currentMinDeadline = minDebounceDeadline
-                val thisDeadline = currentMillis + entry.debounceMillis
-                if (currentMinDeadline > 0 && thisDeadline < currentMinDeadline) {
-                    minDebounceDeadline = currentMinDeadline
-                }
-            }
+        rectChangedMap.multiForEach { entry ->
+            fire(
+                entry = entry,
+                windowOffset = windowOffset,
+                screenOffset = screenOffset,
+                viewToWindowMatrix = viewToWindowMatrix,
+                currentMillis = currentMillis
+            )
+        }
+    }
+
+    /** Fires all [globalChangeEntries] entries with latest window/screen info. */
+    fun fireGlobalChangeEntries(currentMillis: Long) {
+        val windowOffset = windowOffset
+        val screenOffset = screenOffset
+        val viewToWindowMatrix = viewToWindowMatrix
+        globalChangeEntries?.linkedForEach { entry ->
+            val node = entry.node.requireLayoutNode()
+            val offsetFromRoot = node.offsetFromRoot
+            val lastSize = node.lastSize
+
+            // For global change callbacks, we'll still need to update the Entry bounds
+            entry.topLeft = offsetFromRoot.packedValue
+            entry.bottomRight =
+                packXY(offsetFromRoot.x + lastSize.width, offsetFromRoot.y + lastSize.height)
+
+            fire(
+                entry = entry,
+                windowOffset = windowOffset,
+                screenOffset = screenOffset,
+                viewToWindowMatrix = viewToWindowMatrix,
+                currentMillis = currentMillis
+            )
         }
     }
 
@@ -198,22 +223,173 @@
         val screenOffset = screenOffset
         val viewToWindowMatrix = viewToWindowMatrix
         var minDeadline = Long.MAX_VALUE
-        map.multiForEach {
-            if (it.debounceMillis > 0 && it.lastUninvokedFireMillis > 0) {
-                if (currentMillis - it.lastUninvokedFireMillis > it.debounceMillis) {
-                    it.lastInvokeMillis = currentMillis
-                    it.lastUninvokedFireMillis = -1
-                    val topLeft = it.topLeft
-                    val bottomRight = it.bottomRight
-                    it.fire(topLeft, bottomRight, windowOffset, screenOffset, viewToWindowMatrix)
-                } else {
-                    minDeadline = min(minDeadline, it.lastUninvokedFireMillis + it.debounceMillis)
-                }
-            }
+        rectChangedMap.multiForEach { entry ->
+            minDeadline =
+                debounceEntry(
+                    entry = entry,
+                    windowOffset = windowOffset,
+                    screenOffset = screenOffset,
+                    viewToWindowMatrix = viewToWindowMatrix,
+                    currentMillis = currentMillis,
+                    minDeadline = minDeadline
+                )
+        }
+        globalChangeEntries?.linkedForEach { entry ->
+            minDeadline =
+                debounceEntry(
+                    entry = entry,
+                    windowOffset = windowOffset,
+                    screenOffset = screenOffset,
+                    viewToWindowMatrix = viewToWindowMatrix,
+                    currentMillis = currentMillis,
+                    minDeadline = minDeadline
+                )
         }
         minDebounceDeadline = if (minDeadline == Long.MAX_VALUE) -1 else minDeadline
     }
 
+    private fun fireWithUpdatedRect(
+        entry: Entry,
+        topLeft: Long,
+        bottomRight: Long,
+        currentMillis: Long
+    ) {
+        val lastInvokeMillis = entry.lastInvokeMillis
+        val throttleMillis = entry.throttleMillis
+        val debounceMillis = entry.debounceMillis
+        val pastThrottleDeadline = currentMillis - lastInvokeMillis >= throttleMillis
+        val zeroDebounce = debounceMillis == 0L
+        val zeroThrottle = throttleMillis == 0L
+
+        entry.topLeft = topLeft
+        entry.bottomRight = bottomRight
+
+        // There are essentially 3 different cases that we need to handle here:
+
+        // 1. throttle = 0, debounce = 0
+        //      -> always invoke immediately
+        // 2. throttle = 0, debounce > 0
+        //      -> set deadline to <debounce> milliseconds from now
+        // 3. throttle > 0, debounce > 0
+        //      -> invoke if we haven't invoked for <throttle> milliseconds, otherwise, set the
+        //         deadline to <debounce>
+
+        // Note that the `throttle > 0, debounce = 0` case is not possible, since we use the
+        // throttle value as a debounce value in that case.
+
+        val canInvoke = (!zeroDebounce && !zeroThrottle) || zeroDebounce
+
+        if (pastThrottleDeadline && canInvoke) {
+            entry.lastUninvokedFireMillis = -1
+            entry.lastInvokeMillis = currentMillis
+            entry.fire(topLeft, bottomRight, windowOffset, screenOffset, viewToWindowMatrix)
+        } else if (!zeroDebounce) {
+            entry.lastUninvokedFireMillis = currentMillis
+            val currentMinDeadline = minDebounceDeadline
+            val thisDeadline = currentMillis + debounceMillis
+            if (currentMinDeadline > 0 && thisDeadline < currentMinDeadline) {
+                minDebounceDeadline = currentMinDeadline
+            }
+        }
+    }
+
+    private fun fire(
+        entry: Entry,
+        windowOffset: IntOffset,
+        screenOffset: IntOffset,
+        viewToWindowMatrix: Matrix?,
+        currentMillis: Long,
+    ) {
+        val lastInvokeMillis = entry.lastInvokeMillis
+        val throttleOkay = currentMillis - lastInvokeMillis > entry.throttleMillis
+        val debounceOkay = entry.debounceMillis == 0L
+        entry.lastUninvokedFireMillis = currentMillis
+        if (throttleOkay && debounceOkay) {
+            entry.lastInvokeMillis = currentMillis
+            entry.fire(
+                entry.topLeft,
+                entry.bottomRight,
+                windowOffset,
+                screenOffset,
+                viewToWindowMatrix
+            )
+        }
+        if (!debounceOkay) {
+            val currentMinDeadline = minDebounceDeadline
+            val thisDeadline = currentMillis + entry.debounceMillis
+            if (currentMinDeadline > 0 && thisDeadline < currentMinDeadline) {
+                minDebounceDeadline = currentMinDeadline
+            }
+        }
+    }
+
+    /** @return updated minDeadline */
+    private fun debounceEntry(
+        entry: Entry,
+        windowOffset: IntOffset,
+        screenOffset: IntOffset,
+        viewToWindowMatrix: Matrix?,
+        currentMillis: Long,
+        minDeadline: Long
+    ): Long {
+        var newMinDeadline = minDeadline
+        if (entry.debounceMillis > 0 && entry.lastUninvokedFireMillis > 0) {
+            if (currentMillis - entry.lastUninvokedFireMillis > entry.debounceMillis) {
+                entry.lastInvokeMillis = currentMillis
+                entry.lastUninvokedFireMillis = -1
+                val topLeft = entry.topLeft
+                val bottomRight = entry.bottomRight
+                entry.fire(topLeft, bottomRight, windowOffset, screenOffset, viewToWindowMatrix)
+            } else {
+                newMinDeadline =
+                    min(newMinDeadline, entry.lastUninvokedFireMillis + entry.debounceMillis)
+            }
+        }
+        return newMinDeadline
+    }
+
+    private fun addToGlobalEntries(entry: Entry) {
+        // For global entries, we can append the new entry to the start
+        val oldInitialEntry = globalChangeEntries
+        entry.next = oldInitialEntry
+        globalChangeEntries = entry
+    }
+
+    /**
+     * Removes [entry] from the LinkedList in [globalChangeEntries].
+     *
+     * @return Whether the given [entry] was found & removed from [globalChangeEntries].
+     */
+    private fun removeFromGlobalEntries(entry: Entry): Boolean {
+        val initialGlobalEntry = globalChangeEntries
+        if (initialGlobalEntry === entry) {
+            globalChangeEntries = initialGlobalEntry.next
+            entry.next = null
+            return true
+        }
+        var last = initialGlobalEntry
+        var node = last?.next
+        while (node != null) {
+            if (node === entry) {
+                last?.next = node.next
+                entry.next = null
+                return true
+            }
+            last = node
+            node = node.next
+        }
+        return false
+    }
+
+    /** Calls [block] for every [Entry] reachable from the given node through [Entry.next]. */
+    private inline fun Entry.linkedForEach(block: (Entry) -> Unit) {
+        var node: Entry? = this
+        while (node != null) {
+            block(node)
+            node = node.next
+        }
+    }
+
     private inline fun MutableIntObjectMap<Entry>.multiForEach(block: (Entry) -> Unit) {
         forEachValue { it ->
             var entry: Entry? = it
@@ -297,6 +473,7 @@
             windowOffset,
             screenOffset,
             viewToWindowMatrix,
+            node,
         )
     } else
         RectInfo(
@@ -305,5 +482,6 @@
             windowOffset,
             screenOffset,
             viewToWindowMatrix,
+            node,
         )
 }
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 90456ce..cadecaa 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -352,4 +352,8 @@
 # b/382336155 Capture more logging for Kotlin JS Tests
 \[log\].*
 # b/382336155 Capture more logging for Kotlin JS Tests
-.*STANDARD_OUT
\ No newline at end of file
+.*STANDARD_OUT
+# Warning emitted on empty Java files generated from AIDL:
+# https://github.com/kythe/kythe/issues/6145
+.*com.google\.devtools\.kythe\.extractors\.java\.JavaCompilationUnitExtractor.*
+^WARNING: Empty java source file.*
\ No newline at end of file
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 063e5c4..45a7584 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -15,9 +15,9 @@
 }
 
 dependencies {
-    docs("androidx.activity:activity:1.10.0-beta01")
-    docs("androidx.activity:activity-compose:1.10.0-beta01")
-    docs("androidx.activity:activity-ktx:1.10.0-beta01")
+    docs("androidx.activity:activity:1.10.0-rc01")
+    docs("androidx.activity:activity-compose:1.10.0-rc01")
+    docs("androidx.activity:activity-ktx:1.10.0-rc01")
     // ads-identifier is deprecated
     docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
     docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
@@ -26,55 +26,56 @@
     docs("androidx.annotation:annotation-experimental:1.5.0-alpha01")
     docs("androidx.appcompat:appcompat:1.7.0")
     docs("androidx.appcompat:appcompat-resources:1.7.0")
-    docs("androidx.appsearch:appsearch:1.1.0-alpha06")
-    docs("androidx.appsearch:appsearch-builtin-types:1.1.0-alpha06")
-    docs("androidx.appsearch:appsearch-ktx:1.1.0-alpha06")
-    docs("androidx.appsearch:appsearch-local-storage:1.1.0-alpha06")
-    docs("androidx.appsearch:appsearch-platform-storage:1.1.0-alpha06")
-    docs("androidx.appsearch:appsearch-play-services-storage:1.1.0-alpha06")
+    docs("androidx.appsearch:appsearch:1.1.0-alpha07")
+    docs("androidx.appsearch:appsearch-builtin-types:1.1.0-alpha07")
+    docs("androidx.appsearch:appsearch-ktx:1.1.0-alpha07")
+    docs("androidx.appsearch:appsearch-local-storage:1.1.0-alpha07")
+    docs("androidx.appsearch:appsearch-platform-storage:1.1.0-alpha07")
+    docs("androidx.appsearch:appsearch-play-services-storage:1.1.0-alpha07")
     docs("androidx.arch.core:core-common:2.2.0")
     docs("androidx.arch.core:core-runtime:2.2.0")
     docs("androidx.arch.core:core-testing:2.2.0")
     docs("androidx.asynclayoutinflater:asynclayoutinflater:1.1.0-alpha01")
     docs("androidx.asynclayoutinflater:asynclayoutinflater-appcompat:1.1.0-alpha01")
     docs("androidx.autofill:autofill:1.3.0-beta01")
-    docs("androidx.benchmark:benchmark-common:1.4.0-alpha05")
-    docs("androidx.benchmark:benchmark-junit4:1.4.0-alpha05")
-    docs("androidx.benchmark:benchmark-macro:1.4.0-alpha05")
-    docs("androidx.benchmark:benchmark-macro-junit4:1.4.0-alpha05")
+    docs("androidx.benchmark:benchmark-common:1.4.0-alpha06")
+    docs("androidx.benchmark:benchmark-junit4:1.4.0-alpha06")
+    docs("androidx.benchmark:benchmark-macro:1.4.0-alpha06")
+    docs("androidx.benchmark:benchmark-macro-junit4:1.4.0-alpha06")
     docs("androidx.biometric:biometric:1.4.0-alpha02")
     docs("androidx.biometric:biometric-ktx:1.4.0-alpha02")
     docs("androidx.bluetooth:bluetooth:1.0.0-alpha02")
     docs("androidx.bluetooth:bluetooth-testing:1.0.0-alpha02")
     docs("androidx.browser:browser:1.8.0")
-    docs("androidx.camera.viewfinder:viewfinder-compose:1.4.0-alpha10")
-    docs("androidx.camera.viewfinder:viewfinder-core:1.4.0-alpha10")
-    docs("androidx.camera.viewfinder:viewfinder-view:1.4.0-alpha10")
-    docs("androidx.camera:camera-camera2:1.5.0-alpha03")
-    docs("androidx.camera:camera-compose:1.5.0-alpha03")
+    docs("androidx.camera.media3:media3-effect:1.0.0-alpha01")
+    docs("androidx.camera.viewfinder:viewfinder-compose:1.4.0-alpha11")
+    docs("androidx.camera.viewfinder:viewfinder-core:1.4.0-alpha11")
+    docs("androidx.camera.viewfinder:viewfinder-view:1.4.0-alpha11")
+    docs("androidx.camera:camera-camera2:1.5.0-alpha04")
+    docs("androidx.camera:camera-compose:1.5.0-alpha04")
     samples("androidx.camera:camera-compose-samples:1.5.0-alpha01")
-    docs("androidx.camera:camera-core:1.5.0-alpha03")
-    docs("androidx.camera:camera-effects:1.5.0-alpha03")
-    docs("androidx.camera:camera-extensions:1.5.0-alpha03")
+    docs("androidx.camera:camera-core:1.5.0-alpha04")
+    docs("androidx.camera:camera-effects:1.5.0-alpha04")
+    docs("androidx.camera:camera-extensions:1.5.0-alpha04")
     stubs(fileTree(dir: "../camera/camera-extensions-stub", include: ["camera-extensions-stub.jar"]))
-    docs("androidx.camera:camera-feature-combination-query:1.5.0-alpha03")
-    docs("androidx.camera:camera-feature-combination-query-play-services:1.5.0-alpha03")
-    docs("androidx.camera:camera-lifecycle:1.5.0-alpha03")
-    docs("androidx.camera:camera-mlkit-vision:1.5.0-alpha03")
-    docs("androidx.camera:camera-video:1.5.0-alpha03")
-    docs("androidx.camera:camera-view:1.5.0-alpha03")
+    docs("androidx.camera:camera-feature-combination-query:1.5.0-alpha04")
+    docs("androidx.camera:camera-feature-combination-query-play-services:1.5.0-alpha04")
+    docs("androidx.camera:camera-lifecycle:1.5.0-alpha04")
+    docs("androidx.camera:camera-mlkit-vision:1.5.0-alpha04")
+    docs("androidx.camera:camera-video:1.5.0-alpha04")
+    docs("androidx.camera:camera-view:1.5.0-alpha04")
     docs("androidx.car.app:app:1.7.0-beta03")
     docs("androidx.car.app:app-automotive:1.7.0-beta03")
     docs("androidx.car.app:app-projected:1.7.0-beta03")
     docs("androidx.car.app:app-testing:1.7.0-beta03")
     docs("androidx.cardview:cardview:1.0.0")
-    kmpDocs("androidx.collection:collection:1.5.0-alpha06")
-    docs("androidx.collection:collection-ktx:1.5.0-alpha06")
-    kmpDocs("androidx.compose.animation:animation:1.8.0-alpha06")
-    kmpDocs("androidx.compose.animation:animation-core:1.8.0-alpha06")
-    kmpDocs("androidx.compose.animation:animation-graphics:1.8.0-alpha06")
-    kmpDocs("androidx.compose.foundation:foundation:1.8.0-alpha06")
-    kmpDocs("androidx.compose.foundation:foundation-layout:1.8.0-alpha06")
+    kmpDocs("androidx.collection:collection:1.5.0-beta01")
+    docs("androidx.collection:collection-ktx:1.5.0-beta01")
+    kmpDocs("androidx.compose.animation:animation:1.8.0-alpha07")
+    kmpDocs("androidx.compose.animation:animation-core:1.8.0-alpha07")
+    kmpDocs("androidx.compose.animation:animation-graphics:1.8.0-alpha07")
+    kmpDocs("androidx.compose.foundation:foundation:1.8.0-alpha07")
+    kmpDocs("androidx.compose.foundation:foundation-layout:1.8.0-alpha07")
     kmpDocs("androidx.compose.material3.adaptive:adaptive:1.1.0-alpha08")
     kmpDocs("androidx.compose.material3.adaptive:adaptive-layout:1.1.0-alpha08")
     kmpDocs("androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-alpha08")
@@ -83,43 +84,43 @@
     kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha05")
     kmpDocs("androidx.compose.material3:material3-common:1.0.0-alpha01")
     kmpDocs("androidx.compose.material3:material3-window-size-class:1.4.0-alpha05")
-    kmpDocs("androidx.compose.material:material:1.8.0-alpha06")
-    kmpDocs("androidx.compose.material:material-icons-core:1.7.5")
-    docs("androidx.compose.material:material-navigation:1.8.0-alpha06")
-    kmpDocs("androidx.compose.material:material-ripple:1.8.0-alpha06")
-    kmpDocs("androidx.compose.runtime:runtime:1.8.0-alpha06")
-    docs("androidx.compose.runtime:runtime-livedata:1.8.0-alpha06")
-    docs("androidx.compose.runtime:runtime-rxjava2:1.8.0-alpha06")
-    docs("androidx.compose.runtime:runtime-rxjava3:1.8.0-alpha06")
-    kmpDocs("androidx.compose.runtime:runtime-saveable:1.8.0-alpha06")
-    docs("androidx.compose.runtime:runtime-tracing:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-geometry:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-graphics:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-test:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-test-junit4:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-text:1.8.0-alpha06")
-    docs("androidx.compose.ui:ui-text-google-fonts:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-tooling:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-tooling-data:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-unit:1.8.0-alpha06")
-    kmpDocs("androidx.compose.ui:ui-util:1.8.0-alpha06")
-    docs("androidx.compose.ui:ui-viewbinding:1.8.0-alpha06")
-    docs("androidx.concurrent:concurrent-futures:1.2.0")
-    docs("androidx.concurrent:concurrent-futures-ktx:1.2.0")
+    kmpDocs("androidx.compose.material:material:1.8.0-alpha07")
+    kmpDocs("androidx.compose.material:material-icons-core:1.7.6")
+    docs("androidx.compose.material:material-navigation:1.8.0-alpha07")
+    kmpDocs("androidx.compose.material:material-ripple:1.8.0-alpha07")
+    kmpDocs("androidx.compose.runtime:runtime:1.8.0-alpha07")
+    docs("androidx.compose.runtime:runtime-livedata:1.8.0-alpha07")
+    docs("androidx.compose.runtime:runtime-rxjava2:1.8.0-alpha07")
+    docs("androidx.compose.runtime:runtime-rxjava3:1.8.0-alpha07")
+    kmpDocs("androidx.compose.runtime:runtime-saveable:1.8.0-alpha07")
+    docs("androidx.compose.runtime:runtime-tracing:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-geometry:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-graphics:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-test:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-test-junit4:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-text:1.8.0-alpha07")
+    docs("androidx.compose.ui:ui-text-google-fonts:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-tooling:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-tooling-data:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-unit:1.8.0-alpha07")
+    kmpDocs("androidx.compose.ui:ui-util:1.8.0-alpha07")
+    docs("androidx.compose.ui:ui-viewbinding:1.8.0-alpha07")
+    docs("androidx.concurrent:concurrent-futures:1.3.0-alpha01")
+    docs("androidx.concurrent:concurrent-futures-ktx:1.3.0-alpha01")
     docs("androidx.constraintlayout:constraintlayout:2.2.0")
     kmpDocs("androidx.constraintlayout:constraintlayout-compose:1.1.0")
     docs("androidx.constraintlayout:constraintlayout-core:1.1.0")
     docs("androidx.contentpager:contentpager:1.0.0")
     docs("androidx.coordinatorlayout:coordinatorlayout:1.3.0-alpha02")
-    docs("androidx.core:core:1.15.0")
+    docs("androidx.core:core:1.16.0-alpha01")
     // TODO(b/294531403): Turn on apiSince for core-animation when it releases as alpha
     docsWithoutApiSince("androidx.core:core-animation:1.0.0-rc01")
     docs("androidx.core:core-animation-testing:1.0.0")
     docs("androidx.core:core-google-shortcuts:1.2.0-alpha01")
     docs("androidx.core:core-i18n:1.0.0-alpha01")
-    docs("androidx.core:core-ktx:1.15.0")
+    docs("androidx.core:core-ktx:1.16.0-alpha01")
     docs("androidx.core:core-location-altitude:1.0.0-alpha03")
     docs("androidx.core:core-performance:1.0.0")
     docs("androidx.core:core-performance-play-services:1.0.0")
@@ -128,10 +129,11 @@
     docs("androidx.core:core-remoteviews:1.1.0")
     docs("androidx.core:core-role:1.2.0-alpha01")
     docs("androidx.core:core-splashscreen:1.2.0-alpha02")
-    docs("androidx.core:core-telecom:1.0.0-alpha03")
-    docs("androidx.core:core-testing:1.15.0")
-    docs("androidx.core.uwb:uwb:1.0.0-alpha09")
-    docs("androidx.core.uwb:uwb-rxjava3:1.0.0-alpha09")
+    docs("androidx.core:core-telecom:1.0.0-beta01")
+    docs("androidx.core:core-testing:1.16.0-alpha01")
+    docs("androidx.core.uwb:uwb:1.0.0-alpha10")
+    docs("androidx.core.uwb:uwb-rxjava3:1.0.0-alpha10")
+    docs("androidx.core:core-viewtree:1.0.0-alpha01")
     docs("androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha01")
     docs("androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha01")
     docs("androidx.credentials.registry:registry-provider:1.0.0-alpha01")
@@ -185,24 +187,24 @@
     docs("androidx.glance:glance-wear-tiles:1.0.0-alpha06")
     docs("androidx.graphics:graphics-core:1.0.2")
     docs("androidx.graphics:graphics-path:1.0.0")
-    kmpDocs("androidx.graphics:graphics-shapes:1.0.1")
+    kmpDocs("androidx.graphics:graphics-shapes:1.1.0-alpha01")
     docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
     docs("androidx.health.connect:connect-client:1.1.0-alpha10")
     samples("androidx.health.connect:connect-client-samples:1.1.0-alpha09")
     docs("androidx.health.connect:connect-testing:1.0.0-alpha01")
-    docs("androidx.health:health-services-client:1.1.0-alpha04")
+    docs("androidx.health:health-services-client:1.1.0-alpha05")
     docs("androidx.heifwriter:heifwriter:1.1.0-alpha03")
     docs("androidx.hilt:hilt-common:1.2.0")
     docs("androidx.hilt:hilt-navigation:1.2.0")
     docs("androidx.hilt:hilt-navigation-compose:1.2.0")
     docs("androidx.hilt:hilt-navigation-fragment:1.2.0")
     docs("androidx.hilt:hilt-work:1.2.0")
-    kmpDocs("androidx.ink:ink-authoring:1.0.0-alpha01")
-    kmpDocs("androidx.ink:ink-brush:1.0.0-alpha01")
-    kmpDocs("androidx.ink:ink-geometry:1.0.0-alpha01")
-    kmpDocs("androidx.ink:ink-nativeloader:1.0.0-alpha01")
-    kmpDocs("androidx.ink:ink-rendering:1.0.0-alpha01")
-    kmpDocs("androidx.ink:ink-strokes:1.0.0-alpha01")
+    kmpDocs("androidx.ink:ink-authoring:1.0.0-alpha02")
+    kmpDocs("androidx.ink:ink-brush:1.0.0-alpha02")
+    kmpDocs("androidx.ink:ink-geometry:1.0.0-alpha02")
+    kmpDocs("androidx.ink:ink-nativeloader:1.0.0-alpha02")
+    kmpDocs("androidx.ink:ink-rendering:1.0.0-alpha02")
+    kmpDocs("androidx.ink:ink-strokes:1.0.0-alpha02")
     docs("androidx.input:input-motionprediction:1.0.0-beta05")
     docs("androidx.interpolator:interpolator:1.0.0")
     docs("androidx.javascriptengine:javascriptengine:1.0.0-beta01")
@@ -211,26 +213,26 @@
     docs("androidx.leanback:leanback-paging:1.1.0-alpha11")
     docs("androidx.leanback:leanback-preference:1.2.0-alpha04")
     docs("androidx.leanback:leanback-tab:1.1.0-beta01")
-    kmpDocs("androidx.lifecycle:lifecycle-common:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-common-java8:2.9.0-alpha07")
+    kmpDocs("androidx.lifecycle:lifecycle-common:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-common-java8:2.9.0-alpha08")
     docs("androidx.lifecycle:lifecycle-extensions:2.2.0")
-    docs("androidx.lifecycle:lifecycle-livedata:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-livedata-core:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-process:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-reactivestreams:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-runtime:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-runtime-compose:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-runtime-ktx:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-runtime-testing:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-service:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-viewmodel:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0-alpha07")
-    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.0-alpha07")
-    kmpDocs("androidx.lifecycle:lifecycle-viewmodel-testing:2.9.0-alpha07")
+    docs("androidx.lifecycle:lifecycle-livedata:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-livedata-core:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-process:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-reactivestreams:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-runtime:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-runtime-compose:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-runtime-ktx:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-runtime-testing:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-service:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-viewmodel:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0-alpha08")
+    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.0-alpha08")
+    kmpDocs("androidx.lifecycle:lifecycle-viewmodel-testing:2.9.0-alpha08")
     docs("androidx.loader:loader:1.1.0")
     docs("androidx.media2:media2-common:1.3.0")
     docs("androidx.media2:media2-player:1.3.0")
@@ -264,36 +266,37 @@
     docsWithoutApiSince("androidx.media3:media3-transformer:1.5.0")
     docsWithoutApiSince("androidx.media3:media3-ui:1.5.0")
     docsWithoutApiSince("androidx.media3:media3-ui-leanback:1.5.0")
-    docs("androidx.mediarouter:mediarouter:1.7.0")
-    docs("androidx.mediarouter:mediarouter-testing:1.7.0")
+    docs("androidx.mediarouter:mediarouter:1.8.0-alpha01")
+    docs("androidx.mediarouter:mediarouter-testing:1.8.0-alpha01")
     docs("androidx.metrics:metrics-performance:1.0.0-beta01")
-    docs("androidx.navigation:navigation-common:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-common-ktx:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-compose:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-dynamic-features-fragment:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-dynamic-features-runtime:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-fragment:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-fragment-compose:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-fragment-ktx:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-runtime:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-runtime-ktx:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-testing:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-ui:2.9.0-alpha03")
-    docs("androidx.navigation:navigation-ui-ktx:2.9.0-alpha03")
-    kmpDocs("androidx.paging:paging-common:3.3.4")
-    docs("androidx.paging:paging-common-ktx:3.3.4")
-    kmpDocs("androidx.paging:paging-compose:3.3.4")
-    docs("androidx.paging:paging-guava:3.3.4")
-    docs("androidx.paging:paging-runtime:3.3.4")
-    docs("androidx.paging:paging-runtime-ktx:3.3.4")
-    docs("androidx.paging:paging-rxjava2:3.3.4")
-    docs("androidx.paging:paging-rxjava2-ktx:3.3.4")
-    docs("androidx.paging:paging-rxjava3:3.3.4")
-    kmpDocs("androidx.paging:paging-testing:3.3.4")
+    docs("androidx.navigation:navigation-common:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-common-ktx:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-compose:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-dynamic-features-fragment:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-dynamic-features-runtime:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-fragment:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-fragment-compose:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-fragment-ktx:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-runtime:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-runtime-ktx:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-testing:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-ui:2.9.0-alpha04")
+    docs("androidx.navigation:navigation-ui-ktx:2.9.0-alpha04")
+    kmpDocs("androidx.paging:paging-common:3.3.5")
+    docs("androidx.paging:paging-common-ktx:3.3.5")
+    kmpDocs("androidx.paging:paging-compose:3.3.5")
+    docs("androidx.paging:paging-guava:3.3.5")
+    docs("androidx.paging:paging-runtime:3.3.5")
+    docs("androidx.paging:paging-runtime-ktx:3.3.5")
+    docs("androidx.paging:paging-rxjava2:3.3.5")
+    docs("androidx.paging:paging-rxjava2-ktx:3.3.5")
+    docs("androidx.paging:paging-rxjava3:3.3.5")
+    kmpDocs("androidx.paging:paging-testing:3.3.5")
     docs("androidx.palette:palette:1.0.0")
     docs("androidx.palette:palette-ktx:1.0.0")
-    docs("androidx.pdf:pdf-viewer:1.0.0-alpha04")
-    docs("androidx.pdf:pdf-viewer-fragment:1.0.0-alpha04")
+    docs("androidx.pdf:pdf-document-service:1.0.0-alpha05")
+    docs("androidx.pdf:pdf-viewer:1.0.0-alpha05")
+    docs("androidx.pdf:pdf-viewer-fragment:1.0.0-alpha05")
     docs("androidx.percentlayout:percentlayout:1.0.1")
     docs("androidx.preference:preference:1.2.1")
     docs("androidx.preference:preference-ktx:1.2.1")
@@ -303,39 +306,39 @@
     docs("androidx.privacysandbox.activity:activity-provider:1.0.0-alpha01")
     docs("androidx.privacysandbox.ads:ads-adservices:1.1.0-beta11")
     docs("androidx.privacysandbox.ads:ads-adservices-java:1.1.0-beta11")
-    docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha15")
-    docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha15")
-    docs("androidx.privacysandbox.sdkruntime:sdkruntime-provider:1.0.0-alpha15")
+    docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha16")
+    docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha16")
+    docs("androidx.privacysandbox.sdkruntime:sdkruntime-provider:1.0.0-alpha16")
     docs("androidx.privacysandbox.tools:tools:1.0.0-alpha10")
-    docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha11")
-    docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha11")
-    docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha11")
+    docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha12")
+    docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha12")
+    docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha12")
     docs("androidx.profileinstaller:profileinstaller:1.4.1")
     docs("androidx.recommendation:recommendation:1.0.0")
     docs("androidx.recyclerview:recyclerview:1.4.0-rc01")
     docs("androidx.recyclerview:recyclerview-selection:2.0.0-alpha01")
     docs("androidx.remotecallback:remotecallback:1.0.0-alpha02")
     docs("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
-    kmpDocs("androidx.room:room-common:2.7.0-alpha11")
+    kmpDocs("androidx.room:room-common:2.7.0-alpha12")
     docs("androidx.room:room-external-antlr:2.7.0-alpha09")
-    docs("androidx.room:room-guava:2.7.0-alpha11")
-    docs("androidx.room:room-ktx:2.7.0-alpha11")
-    kmpDocs("androidx.room:room-migration:2.7.0-alpha11")
-    kmpDocs("androidx.room:room-paging:2.7.0-alpha11")
-    docs("androidx.room:room-paging-guava:2.7.0-alpha11")
-    docs("androidx.room:room-paging-rxjava2:2.7.0-alpha11")
-    docs("androidx.room:room-paging-rxjava3:2.7.0-alpha11")
-    kmpDocs("androidx.room:room-runtime:2.7.0-alpha11")
-    docs("androidx.room:room-rxjava2:2.7.0-alpha11")
-    docs("androidx.room:room-rxjava3:2.7.0-alpha11")
-    kmpDocs("androidx.room:room-testing:2.7.0-alpha11")
-    kmpDocs("androidx.savedstate:savedstate:1.3.0-alpha05")
-    docs("androidx.savedstate:savedstate-ktx:1.3.0-alpha05")
+    docs("androidx.room:room-guava:2.7.0-alpha12")
+    docs("androidx.room:room-ktx:2.7.0-alpha12")
+    kmpDocs("androidx.room:room-migration:2.7.0-alpha12")
+    kmpDocs("androidx.room:room-paging:2.7.0-alpha12")
+    docs("androidx.room:room-paging-guava:2.7.0-alpha12")
+    docs("androidx.room:room-paging-rxjava2:2.7.0-alpha12")
+    docs("androidx.room:room-paging-rxjava3:2.7.0-alpha12")
+    kmpDocs("androidx.room:room-runtime:2.7.0-alpha12")
+    docs("androidx.room:room-rxjava2:2.7.0-alpha12")
+    docs("androidx.room:room-rxjava3:2.7.0-alpha12")
+    kmpDocs("androidx.room:room-testing:2.7.0-alpha12")
+    kmpDocs("androidx.savedstate:savedstate:1.3.0-alpha06")
+    kmpDocs("androidx.savedstate:savedstate-compose:1.3.0-alpha06")
+    docs("androidx.savedstate:savedstate-ktx:1.3.0-alpha06")
     docs("androidx.security:security-app-authenticator:1.0.0-beta01")
     docs("androidx.security:security-app-authenticator-testing:1.0.0-beta01")
     docs("androidx.security:security-crypto:1.1.0-alpha06")
     docs("androidx.security:security-crypto-ktx:1.1.0-alpha06")
-    docs("androidx.security:security-identity-credential:1.0.0-alpha03")
     docs("androidx.security:security-state:1.0.0-alpha04")
     docs("androidx.sharetarget:sharetarget:1.2.0")
     docs("androidx.slice:slice-builders:1.1.0-alpha02")
@@ -343,10 +346,10 @@
     docs("androidx.slice:slice-core:1.1.0-alpha02")
     docs("androidx.slice:slice-view:1.1.0-alpha02")
     docs("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
-    kmpDocs("androidx.sqlite:sqlite:2.5.0-alpha11")
-    kmpDocs("androidx.sqlite:sqlite-bundled:2.5.0-alpha11")
-    kmpDocs("androidx.sqlite:sqlite-framework:2.5.0-alpha11")
-    docs("androidx.sqlite:sqlite-ktx:2.5.0-alpha11")
+    kmpDocs("androidx.sqlite:sqlite:2.5.0-alpha12")
+    kmpDocs("androidx.sqlite:sqlite-bundled:2.5.0-alpha12")
+    kmpDocs("androidx.sqlite:sqlite-framework:2.5.0-alpha12")
+    docs("androidx.sqlite:sqlite-ktx:2.5.0-alpha12")
     docs("androidx.startup:startup-runtime:1.2.0")
     docs("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
     // androidx.test is not hosted in androidx
@@ -376,8 +379,8 @@
     // TODO(243405142) clean-up
     docsWithoutApiSince("androidx.tracing:tracing-perfetto-common:1.0.0-alpha16")
     docs("androidx.tracing:tracing-perfetto-handshake:1.0.0")
-    docs("androidx.transition:transition:1.5.1")
-    docs("androidx.transition:transition-ktx:1.5.1")
+    docs("androidx.transition:transition:1.6.0-alpha01")
+    docs("androidx.transition:transition-ktx:1.6.0-alpha01")
     docs("androidx.tv:tv-foundation:1.0.0-alpha11")
     docs("androidx.tv:tv-material:1.0.0")
     docs("androidx.tvprovider:tvprovider:1.1.0-alpha01")
@@ -386,27 +389,27 @@
     docs("androidx.vectordrawable:vectordrawable-seekable:1.0.0")
     docs("androidx.versionedparcelable:versionedparcelable:1.2.0")
     docs("androidx.viewpager2:viewpager2:1.1.0")
-    docs("androidx.viewpager:viewpager:1.1.0-rc01")
-    docs("androidx.wear.compose:compose-foundation:1.5.0-alpha06")
-    docs("androidx.wear.compose:compose-material:1.5.0-alpha06")
-    docs("androidx.wear.compose:compose-material-core:1.5.0-alpha06")
-    docs("androidx.wear.compose:compose-material3:1.0.0-alpha29")
-    docs("androidx.wear.compose:compose-navigation:1.5.0-alpha06")
-    docs("androidx.wear.compose:compose-ui-tooling:1.5.0-alpha06")
-    docs("androidx.wear.protolayout:protolayout:1.3.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-expression:1.3.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.3.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-material:1.3.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-material-core:1.3.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-material3:1.3.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-renderer:1.3.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-testing:1.3.0-alpha04")
-    docs("androidx.wear.tiles:tiles:1.5.0-alpha04")
-    docs("androidx.wear.tiles:tiles-material:1.5.0-alpha04")
-    docs("androidx.wear.tiles:tiles-renderer:1.5.0-alpha04")
-    docs("androidx.wear.tiles:tiles-testing:1.5.0-alpha04")
-    docs("androidx.wear.tiles:tiles-tooling:1.5.0-alpha04")
-    docs("androidx.wear.tiles:tiles-tooling-preview:1.5.0-alpha04")
+    docs("androidx.viewpager:viewpager:1.1.0")
+    docs("androidx.wear.compose:compose-foundation:1.5.0-alpha07")
+    docs("androidx.wear.compose:compose-material:1.5.0-alpha07")
+    docs("androidx.wear.compose:compose-material-core:1.5.0-alpha07")
+    docs("androidx.wear.compose:compose-material3:1.0.0-alpha30")
+    docs("androidx.wear.compose:compose-navigation:1.5.0-alpha07")
+    docs("androidx.wear.compose:compose-ui-tooling:1.5.0-alpha07")
+    docs("androidx.wear.protolayout:protolayout:1.3.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-expression:1.3.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.3.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-material:1.3.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-material-core:1.3.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-material3:1.3.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-renderer:1.3.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-testing:1.3.0-alpha05")
+    docs("androidx.wear.tiles:tiles:1.5.0-alpha05")
+    docs("androidx.wear.tiles:tiles-material:1.5.0-alpha05")
+    docs("androidx.wear.tiles:tiles-renderer:1.5.0-alpha05")
+    docs("androidx.wear.tiles:tiles-testing:1.5.0-alpha05")
+    docs("androidx.wear.tiles:tiles-tooling:1.5.0-alpha05")
+    docs("androidx.wear.tiles:tiles-tooling-preview:1.5.0-alpha05")
     docs("androidx.wear.watchface:watchface:1.3.0-alpha04")
     docs("androidx.wear.watchface:watchface-client:1.3.0-alpha04")
     docs("androidx.wear.watchface:watchface-client-guava:1.3.0-alpha04")
@@ -427,12 +430,12 @@
     samples("androidx.wear:wear-input-samples:1.2.0-alpha01")
     docs("androidx.wear:wear-input-testing:1.2.0-alpha02")
     docs("androidx.wear:wear-ongoing:1.1.0-alpha01")
-    docs("androidx.wear:wear-phone-interactions:1.1.0-alpha04")
+    docs("androidx.wear:wear-phone-interactions:1.1.0-alpha05")
     samples("androidx.wear:wear-phone-interactions-samples:1.1.0-alpha04")
-    docs("androidx.wear:wear-remote-interactions:1.1.0-rc01")
+    docs("androidx.wear:wear-remote-interactions:1.1.0")
     samples("androidx.wear:wear-remote-interactions-samples:1.1.0-alpha02")
     docs("androidx.wear:wear-tooling-preview:1.0.0")
-    docs("androidx.webkit:webkit:1.13.0-alpha01")
+    docs("androidx.webkit:webkit:1.13.0-alpha02")
     docs("androidx.window.extensions.core:core:1.0.0")
     docs("androidx.window:window:1.4.0-alpha05")
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 5cac91c..3d5ecec 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -319,7 +319,6 @@
     docs(project(":security:security-biometric"))
     docs(project(":security:security-crypto"))
     docs(project(":security:security-crypto-ktx"))
-    docs(project(":security:security-identity-credential"))
     docs(project(":security:security-mls"))
     docs(project(":security:security-state"))
     docs(project(":security:security-state-provider"))
diff --git a/lifecycle/lifecycle-common/build.gradle b/lifecycle/lifecycle-common/build.gradle
index eb52f06..e163130 100644
--- a/lifecycle/lifecycle-common/build.gradle
+++ b/lifecycle/lifecycle-common/build.gradle
@@ -38,10 +38,6 @@
     linux()
     ios()
 
-    kotlin {
-        explicitApi = ExplicitApiMode.Strict
-    }
-
     defaultPlatform(PlatformIdentifier.JVM)
 
     sourceSets {
diff --git a/lifecycle/lifecycle-livedata-core-ktx/build.gradle b/lifecycle/lifecycle-livedata-core-ktx/build.gradle
index 0487afb..33eb8f8 100644
--- a/lifecycle/lifecycle-livedata-core-ktx/build.gradle
+++ b/lifecycle/lifecycle-livedata-core-ktx/build.gradle
@@ -39,7 +39,6 @@
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2018"
     description = "Kotlin extensions for 'livedata-core' artifact"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/lifecycle/lifecycle-livedata-core/build.gradle b/lifecycle/lifecycle-livedata-core/build.gradle
index b5508cc..d5f66e6 100644
--- a/lifecycle/lifecycle-livedata-core/build.gradle
+++ b/lifecycle/lifecycle-livedata-core/build.gradle
@@ -52,7 +52,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "Android Lifecycle LiveData Core"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/lifecycle/lifecycle-livedata-core/src/main/java/androidx/lifecycle/Observer.kt b/lifecycle/lifecycle-livedata-core/src/main/java/androidx/lifecycle/Observer.kt
index a245e92..a54003c 100644
--- a/lifecycle/lifecycle-livedata-core/src/main/java/androidx/lifecycle/Observer.kt
+++ b/lifecycle/lifecycle-livedata-core/src/main/java/androidx/lifecycle/Observer.kt
@@ -20,8 +20,8 @@
  *
  * @see LiveData LiveData - for a usage description.
  */
-fun interface Observer<T> {
+public fun interface Observer<T> {
 
     /** Called when the data is changed to [value]. */
-    fun onChanged(value: T)
+    public fun onChanged(value: T)
 }
diff --git a/lifecycle/lifecycle-livedata/build.gradle b/lifecycle/lifecycle-livedata/build.gradle
index aa95b79..669f9c2 100644
--- a/lifecycle/lifecycle-livedata/build.gradle
+++ b/lifecycle/lifecycle-livedata/build.gradle
@@ -60,7 +60,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "Android Lifecycle LiveData"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/ComputableLiveData.kt b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/ComputableLiveData.kt
index dce2d8c..397e28a 100644
--- a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/ComputableLiveData.kt
+++ b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/ComputableLiveData.kt
@@ -33,7 +33,7 @@
  * @param <T> The type of the live data
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-abstract class ComputableLiveData<T>
+public abstract class ComputableLiveData<T>
 @JvmOverloads
 /**
  * Creates a computable live data that computes values on the specified executor or the arch IO
@@ -50,7 +50,7 @@
             }
         }
     /** The LiveData managed by this class. */
-    open val liveData: LiveData<T?> = _liveData
+    public open val liveData: LiveData<T?> = _liveData
     internal val invalid = AtomicBoolean(true)
     internal val computing = AtomicBoolean(false)
 
@@ -104,7 +104,7 @@
      *
      * When there are active observers, this will trigger a call to [.compute].
      */
-    open fun invalidate() {
+    public open fun invalidate() {
         ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable)
     }
 
diff --git a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
index a0e5a64..4205090 100644
--- a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
+++ b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
@@ -46,7 +46,7 @@
 @MainThread
 @CheckResult
 @Suppress("UNCHECKED_CAST")
-fun <X, Y> LiveData<X>.map(
+public fun <X, Y> LiveData<X>.map(
     transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
 ): LiveData<Y> {
     val result =
@@ -66,7 +66,7 @@
 @JvmName("map")
 @MainThread
 @CheckResult
-fun <X, Y> LiveData<X>.map(mapFunction: Function<X, Y>): LiveData<Y> {
+public fun <X, Y> LiveData<X>.map(mapFunction: Function<X, Y>): LiveData<Y> {
     val result = MediatorLiveData<Y>()
     result.addSource(this) { x -> result.value = mapFunction.apply(x) }
     return result
@@ -119,7 +119,7 @@
 @MainThread
 @CheckResult
 @Suppress("UNCHECKED_CAST")
-fun <X, Y> LiveData<X>.switchMap(
+public fun <X, Y> LiveData<X>.switchMap(
     transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
 ): LiveData<Y> {
     var liveData: LiveData<Y>? = null
@@ -156,7 +156,7 @@
 @JvmName("switchMap")
 @MainThread
 @CheckResult
-fun <X, Y> LiveData<X>.switchMap(switchMapFunction: Function<X, LiveData<Y>>): LiveData<Y> {
+public fun <X, Y> LiveData<X>.switchMap(switchMapFunction: Function<X, LiveData<Y>>): LiveData<Y> {
     val result = MediatorLiveData<Y>()
     result.addSource(
         this,
@@ -190,7 +190,7 @@
 @JvmName("distinctUntilChanged")
 @MainThread
 @CheckResult
-fun <X> LiveData<X>.distinctUntilChanged(): LiveData<X> {
+public fun <X> LiveData<X>.distinctUntilChanged(): LiveData<X> {
     var firstTime = true
     val outputLiveData =
         if (isInitialized) {
diff --git a/lifecycle/lifecycle-process/build.gradle b/lifecycle/lifecycle-process/build.gradle
index 9aa412d..a134f79 100644
--- a/lifecycle/lifecycle-process/build.gradle
+++ b/lifecycle/lifecycle-process/build.gradle
@@ -51,5 +51,4 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2018"
     description = "Android Lifecycle Process"
-    legacyDisableKotlinStrictApiMode = true
 }
diff --git a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleInitializer.kt b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleInitializer.kt
index 8ef8fee..f572efa 100644
--- a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleInitializer.kt
+++ b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleInitializer.kt
@@ -20,7 +20,7 @@
 import androidx.startup.Initializer
 
 /** Initializes [ProcessLifecycleOwner] using `androidx.startup`. */
-class ProcessLifecycleInitializer : Initializer<LifecycleOwner> {
+public class ProcessLifecycleInitializer : Initializer<LifecycleOwner> {
     override fun create(context: Context): LifecycleOwner {
         val appInitializer = AppInitializer.getInstance(context)
         check(appInitializer.isEagerlyInitialized(javaClass)) {
diff --git a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
index 5193aa8..788bd01 100644
--- a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
+++ b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
@@ -40,7 +40,7 @@
  * It is useful for use cases where you would like to react on your app coming to the foreground or
  * going to the background and you don't need a milliseconds accuracy in receiving lifecycle events.
  */
-class ProcessLifecycleOwner private constructor() : LifecycleOwner {
+public class ProcessLifecycleOwner private constructor() : LifecycleOwner {
     // ground truth counters
     private var startedCounter = 0
     private var resumedCounter = 0
@@ -65,7 +65,7 @@
             }
         }
 
-    companion object {
+    public companion object {
         @VisibleForTesting internal const val TIMEOUT_MS: Long = 700 // mls
         private val newInstance = ProcessLifecycleOwner()
 
@@ -76,7 +76,7 @@
          * @return [LifecycleOwner] for the whole application.
          */
         @JvmStatic
-        fun get(): LifecycleOwner {
+        public fun get(): LifecycleOwner {
             return newInstance
         }
 
diff --git a/lifecycle/lifecycle-reactivestreams/build.gradle b/lifecycle/lifecycle-reactivestreams/build.gradle
index d125912..148bc02 100644
--- a/lifecycle/lifecycle-reactivestreams/build.gradle
+++ b/lifecycle/lifecycle-reactivestreams/build.gradle
@@ -53,7 +53,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "Android Lifecycle Reactivestreams"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/lifecycle/lifecycle-reactivestreams/src/main/java/androidx/lifecycle/LiveDataReactiveStreams.kt b/lifecycle/lifecycle-reactivestreams/src/main/java/androidx/lifecycle/LiveDataReactiveStreams.kt
index 8ad2c74..b194dd1 100644
--- a/lifecycle/lifecycle-reactivestreams/src/main/java/androidx/lifecycle/LiveDataReactiveStreams.kt
+++ b/lifecycle/lifecycle-reactivestreams/src/main/java/androidx/lifecycle/LiveDataReactiveStreams.kt
@@ -47,7 +47,7 @@
             imports = arrayOf("androidx.lifecycle.toPublisher"),
         )
 )
-fun <T> toPublisher(lifecycle: LifecycleOwner, liveData: LiveData<T>): Publisher<T> {
+public fun <T> toPublisher(lifecycle: LifecycleOwner, liveData: LiveData<T>): Publisher<T> {
     return LiveDataPublisher(lifecycle, liveData)
 }
 
@@ -67,7 +67,7 @@
  */
 @SuppressLint("LambdaLast")
 @JvmName("toPublisher")
-fun <T> LiveData<T>.toPublisher(lifecycle: LifecycleOwner): Publisher<T> {
+public fun <T> LiveData<T>.toPublisher(lifecycle: LifecycleOwner): Publisher<T> {
     return LiveDataPublisher(lifecycle, this)
 }
 
@@ -77,8 +77,7 @@
         subscriber.onSubscribe(LiveDataSubscription(subscriber, lifecycle, liveData))
     }
 
-    class LiveDataSubscription<T>
-    constructor(
+    class LiveDataSubscription<T>(
         val subscriber: Subscriber<in T>,
         val lifecycle: LifecycleOwner,
         val liveData: LiveData<T>
@@ -174,7 +173,8 @@
  * data that's held. In case of an error being emitted by the publisher, an error will be propagated
  * to the main thread and the app will crash.
  */
-@JvmName("fromPublisher") fun <T> Publisher<T>.toLiveData(): LiveData<T> = PublisherLiveData(this)
+@JvmName("fromPublisher")
+public fun <T> Publisher<T>.toLiveData(): LiveData<T> = PublisherLiveData(this)
 
 /**
  * Defines a [LiveData] object that wraps a [Publisher].
diff --git a/lifecycle/lifecycle-runtime-compose/build.gradle b/lifecycle/lifecycle-runtime-compose/build.gradle
index 7a93d89..543583e 100644
--- a/lifecycle/lifecycle-runtime-compose/build.gradle
+++ b/lifecycle/lifecycle-runtime-compose/build.gradle
@@ -108,7 +108,6 @@
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2021"
     description = "Compose integration with Lifecycle"
-    legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":lifecycle:lifecycle-runtime-compose:lifecycle-runtime-compose-samples"))
 }
diff --git a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/DropUnlessLifecycle.kt b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/DropUnlessLifecycle.kt
index f8fc429..259b677 100644
--- a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/DropUnlessLifecycle.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/DropUnlessLifecycle.kt
@@ -72,7 +72,7 @@
  */
 @CheckResult
 @Composable
-fun dropUnlessStarted(
+public fun dropUnlessStarted(
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     block: () -> Unit,
 ): () -> Unit = dropUnlessStateIsAtLeast(State.STARTED, lifecycleOwner, block)
@@ -94,7 +94,7 @@
  */
 @CheckResult
 @Composable
-fun dropUnlessResumed(
+public fun dropUnlessResumed(
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     block: () -> Unit,
 ): () -> Unit = dropUnlessStateIsAtLeast(State.RESUMED, lifecycleOwner, block)
diff --git a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/FlowExt.kt b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/FlowExt.kt
index 8ba0220..38d3ed2 100644
--- a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/FlowExt.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/FlowExt.kt
@@ -53,7 +53,8 @@
  * @param context [CoroutineContext] to use for collecting.
  */
 @Composable
-@Suppress("StateFlowValueCalledInComposition") // Initial value for an ongoing collect.
+@Suppress("StateFlowValueCalledInComposition")
+public // Initial value for an ongoing collect.
 fun <T> StateFlow<T>.collectAsStateWithLifecycle(
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
@@ -89,7 +90,8 @@
  * @param context [CoroutineContext] to use for collecting.
  */
 @Composable
-@Suppress("StateFlowValueCalledInComposition") // Initial value for an ongoing collect.
+@Suppress("StateFlowValueCalledInComposition")
+public // Initial value for an ongoing collect.
 fun <T> StateFlow<T>.collectAsStateWithLifecycle(
     lifecycle: Lifecycle,
     minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
@@ -128,7 +130,7 @@
  * @param context [CoroutineContext] to use for collecting.
  */
 @Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
+public fun <T> Flow<T>.collectAsStateWithLifecycle(
     initialValue: T,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
@@ -165,7 +167,7 @@
  * @param context [CoroutineContext] to use for collecting.
  */
 @Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
+public fun <T> Flow<T>.collectAsStateWithLifecycle(
     initialValue: T,
     lifecycle: Lifecycle,
     minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
diff --git a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleEffect.kt b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleEffect.kt
index ca52fb5..f7c1677 100644
--- a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleEffect.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleEffect.kt
@@ -49,7 +49,7 @@
  * @throws IllegalArgumentException if attempting to listen for [Lifecycle.Event.ON_DESTROY]
  */
 @Composable
-fun LifecycleEventEffect(
+public fun LifecycleEventEffect(
     event: Lifecycle.Event,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     onEvent: () -> Unit
@@ -123,7 +123,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleStartEffect(
+public fun LifecycleStartEffect(
     key1: Any?,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
@@ -180,7 +180,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleStartEffect(
+public fun LifecycleStartEffect(
     key1: Any?,
     key2: Any?,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
@@ -242,7 +242,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleStartEffect(
+public fun LifecycleStartEffect(
     key1: Any?,
     key2: Any?,
     key3: Any?,
@@ -302,7 +302,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleStartEffect(
+public fun LifecycleStartEffect(
     vararg keys: Any?,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
@@ -328,7 +328,7 @@
 @Deprecated(LifecycleStartEffectNoParamError, level = DeprecationLevel.ERROR)
 @Composable
 @Suppress("UNUSED_PARAMETER")
-fun LifecycleStartEffect(
+public fun LifecycleStartEffect(
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     effects: LifecycleStartStopEffectScope.() -> LifecycleStopOrDisposeEffectResult
 ): Unit = error(LifecycleStartEffectNoParamError)
@@ -366,8 +366,8 @@
  * when an (ON_STOP)[Lifecycle.Event.ON_STOP] event is received or when cleanup is needed for the
  * work that was kicked off in the ON_START effect.
  */
-interface LifecycleStopOrDisposeEffectResult {
-    fun runStopOrDisposeEffect()
+public interface LifecycleStopOrDisposeEffectResult {
+    public fun runStopOrDisposeEffect()
 }
 
 /**
@@ -379,12 +379,12 @@
  *
  * @param lifecycle The lifecycle being observed by this receiver scope
  */
-class LifecycleStartStopEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
+public class LifecycleStartStopEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
     /**
      * Provide the [onStopOrDisposeEffect] to the [LifecycleStartEffect] to run when the observer
      * receives an (ON_STOP)[Lifecycle.Event.ON_STOP] event or must undergo cleanup.
      */
-    inline fun onStopOrDispose(
+    public inline fun onStopOrDispose(
         crossinline onStopOrDisposeEffect: LifecycleOwner.() -> Unit
     ): LifecycleStopOrDisposeEffectResult =
         object : LifecycleStopOrDisposeEffectResult {
@@ -441,7 +441,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleResumeEffect(
+public fun LifecycleResumeEffect(
     key1: Any?,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
@@ -499,7 +499,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleResumeEffect(
+public fun LifecycleResumeEffect(
     key1: Any?,
     key2: Any?,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
@@ -562,7 +562,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleResumeEffect(
+public fun LifecycleResumeEffect(
     key1: Any?,
     key2: Any?,
     key3: Any?,
@@ -623,7 +623,7 @@
  * @param effects The effects to be launched when we receive the respective event callbacks
  */
 @Composable
-fun LifecycleResumeEffect(
+public fun LifecycleResumeEffect(
     vararg keys: Any?,
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
@@ -651,7 +651,7 @@
 @Deprecated(LifecycleResumeEffectNoParamError, level = DeprecationLevel.ERROR)
 @Composable
 @Suppress("UNUSED_PARAMETER")
-fun LifecycleResumeEffect(
+public fun LifecycleResumeEffect(
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseOrDisposeEffectResult
 ): Unit = error(LifecycleResumeEffectNoParamError)
@@ -689,8 +689,8 @@
  * when an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event is received or when cleanup is
  * * needed for the work that was kicked off in the ON_RESUME effect.
  */
-interface LifecyclePauseOrDisposeEffectResult {
-    fun runPauseOrOnDisposeEffect()
+public interface LifecyclePauseOrDisposeEffectResult {
+    public fun runPauseOrOnDisposeEffect()
 }
 
 /**
@@ -702,12 +702,12 @@
  *
  * @param lifecycle The lifecycle being observed by this receiver scope
  */
-class LifecycleResumePauseEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
+public class LifecycleResumePauseEffectScope(override val lifecycle: Lifecycle) : LifecycleOwner {
     /**
      * Provide the [onPauseOrDisposeEffect] to the [LifecycleResumeEffect] to run when the observer
      * receives an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event or must undergo cleanup.
      */
-    inline fun onPauseOrDispose(
+    public inline fun onPauseOrDispose(
         crossinline onPauseOrDisposeEffect: LifecycleOwner.() -> Unit
     ): LifecyclePauseOrDisposeEffectResult =
         object : LifecyclePauseOrDisposeEffectResult {
diff --git a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleExt.kt b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleExt.kt
index d7bc0ad..7444dda 100644
--- a/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleExt.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/commonMain/kotlin/androidx/lifecycle/compose/LifecycleExt.kt
@@ -29,4 +29,5 @@
  * [State.value] usage.
  */
 @Composable
-fun Lifecycle.currentStateAsState(): State<Lifecycle.State> = currentStateFlow.collectAsState()
+public fun Lifecycle.currentStateAsState(): State<Lifecycle.State> =
+    currentStateFlow.collectAsState()
diff --git a/lifecycle/lifecycle-runtime-testing/build.gradle b/lifecycle/lifecycle-runtime-testing/build.gradle
index 89a61a8..f0e1ac5 100644
--- a/lifecycle/lifecycle-runtime-testing/build.gradle
+++ b/lifecycle/lifecycle-runtime-testing/build.gradle
@@ -85,7 +85,6 @@
     type = LibraryType.PUBLISHED_TEST_LIBRARY
     inceptionYear = "2019"
     description = "Testing utilities for 'lifecycle' artifact"
-    legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
 }
 
diff --git a/lifecycle/lifecycle-runtime-testing/src/commonMain/kotlin/androidx/lifecycle/testing/TestLifecycleOwner.kt b/lifecycle/lifecycle-runtime-testing/src/commonMain/kotlin/androidx/lifecycle/testing/TestLifecycleOwner.kt
index 1a7b5f2..568da1c 100644
--- a/lifecycle/lifecycle-runtime-testing/src/commonMain/kotlin/androidx/lifecycle/testing/TestLifecycleOwner.kt
+++ b/lifecycle/lifecycle-runtime-testing/src/commonMain/kotlin/androidx/lifecycle/testing/TestLifecycleOwner.kt
@@ -74,7 +74,7 @@
      * not block that thread. If the state should be updated from outside of a suspending function,
      * use [currentState] property syntax instead.
      */
-    suspend fun setCurrentState(state: Lifecycle.State) {
+    public suspend fun setCurrentState(state: Lifecycle.State) {
         withContext(coroutineDispatcher) { lifecycleRegistry.currentState = state }
     }
 
diff --git a/lifecycle/lifecycle-runtime/build.gradle b/lifecycle/lifecycle-runtime/build.gradle
index 6a806cad..0879e20 100644
--- a/lifecycle/lifecycle-runtime/build.gradle
+++ b/lifecycle/lifecycle-runtime/build.gradle
@@ -23,10 +23,6 @@
     linux()
     ios()
 
-    kotlin {
-        explicitApi = ExplicitApiMode.Strict
-    }
-
     defaultPlatform(PlatformIdentifier.ANDROID)
 
     sourceSets {
diff --git a/lifecycle/lifecycle-service/build.gradle b/lifecycle/lifecycle-service/build.gradle
index 3561fde..2230f12 100644
--- a/lifecycle/lifecycle-service/build.gradle
+++ b/lifecycle/lifecycle-service/build.gradle
@@ -44,7 +44,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2018"
     description = "Android Lifecycle Service"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt
index ceadd79..d7b0e29 100644
--- a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt
+++ b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt
@@ -21,7 +21,7 @@
 import androidx.annotation.CallSuper
 
 /** A Service that is also a [LifecycleOwner]. */
-open class LifecycleService : Service(), LifecycleOwner {
+public open class LifecycleService : Service(), LifecycleOwner {
 
     private val dispatcher = ServiceLifecycleDispatcher(this)
 
diff --git a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.kt b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.kt
index 774925b..5f4d98a 100644
--- a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.kt
+++ b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.kt
@@ -24,7 +24,7 @@
  *
  * @param provider [LifecycleOwner] for a service, usually it is a service itself
  */
-open class ServiceLifecycleDispatcher(provider: LifecycleOwner) {
+public open class ServiceLifecycleDispatcher(provider: LifecycleOwner) {
 
     private val registry: LifecycleRegistry
     private val handler: Handler
@@ -43,12 +43,12 @@
     }
 
     /** Must be a first call in [Service.onCreate] method, even before super.onCreate call. */
-    open fun onServicePreSuperOnCreate() {
+    public open fun onServicePreSuperOnCreate() {
         postDispatchRunnable(Lifecycle.Event.ON_CREATE)
     }
 
     /** Must be a first call in [Service.onBind] method, even before super.onBind call. */
-    open fun onServicePreSuperOnBind() {
+    public open fun onServicePreSuperOnBind() {
         postDispatchRunnable(Lifecycle.Event.ON_START)
     }
 
@@ -56,18 +56,18 @@
      * Must be a first call in [Service.onStart] or [Service.onStartCommand] methods, even before a
      * corresponding super call.
      */
-    open fun onServicePreSuperOnStart() {
+    public open fun onServicePreSuperOnStart() {
         postDispatchRunnable(Lifecycle.Event.ON_START)
     }
 
     /** Must be a first call in [Service.onDestroy] method, even before super.OnDestroy call. */
-    open fun onServicePreSuperOnDestroy() {
+    public open fun onServicePreSuperOnDestroy() {
         postDispatchRunnable(Lifecycle.Event.ON_STOP)
         postDispatchRunnable(Lifecycle.Event.ON_DESTROY)
     }
 
     /** [Lifecycle] for the given [LifecycleOwner] */
-    open val lifecycle: Lifecycle
+    public open val lifecycle: Lifecycle
         get() = registry
 
     internal class DispatchRunnable(
diff --git a/lifecycle/lifecycle-viewmodel-compose/build.gradle b/lifecycle/lifecycle-viewmodel-compose/build.gradle
index 0731ab4..32b2226 100644
--- a/lifecycle/lifecycle-viewmodel-compose/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-compose/build.gradle
@@ -100,7 +100,6 @@
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2021"
     description = "Compose integration with Lifecycle ViewModel"
-    legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":lifecycle:lifecycle-viewmodel-compose:lifecycle-viewmodel-compose-samples"))
     kotlinTarget = KotlinTarget.KOTLIN_1_9
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaveableApi.android.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaveableApi.android.kt
index 6d0a772..d97ad79 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaveableApi.android.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaveableApi.android.kt
@@ -20,4 +20,4 @@
 @Retention(AnnotationRetention.BINARY)
 @Target(AnnotationTarget.FUNCTION)
 @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
-annotation class SavedStateHandleSaveableApi
+public annotation class SavedStateHandleSaveableApi
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
index 8fcad2f..9c4d14e 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
@@ -48,7 +48,7 @@
  * @sample androidx.lifecycle.viewmodel.compose.samples.SnapshotStateViewModel
  */
 @SavedStateHandleSaveableApi
-fun <T : Any> SavedStateHandle.saveable(
+public fun <T : Any> SavedStateHandle.saveable(
     key: String,
     saver: Saver<T, out Any> = autoSaver(),
     init: () -> T,
@@ -82,7 +82,7 @@
  * @sample androidx.lifecycle.viewmodel.compose.samples.SnapshotStateViewModel
  */
 @SavedStateHandleSaveableApi
-fun <T> SavedStateHandle.saveable(
+public fun <T> SavedStateHandle.saveable(
     key: String,
     stateSaver: Saver<T, out Any>,
     init: () -> MutableState<T>
@@ -104,7 +104,7 @@
  * @sample androidx.lifecycle.viewmodel.compose.samples.SnapshotStateViewModelWithDelegates
  */
 @SavedStateHandleSaveableApi
-fun <T : Any> SavedStateHandle.saveable(
+public fun <T : Any> SavedStateHandle.saveable(
     saver: Saver<T, out Any> = autoSaver(),
     init: () -> T,
 ): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
@@ -138,7 +138,7 @@
  */
 @SavedStateHandleSaveableApi
 @JvmName("saveableMutableState")
-fun <T, M : MutableState<T>> SavedStateHandle.saveable(
+public fun <T, M : MutableState<T>> SavedStateHandle.saveable(
     stateSaver: Saver<T, out Any> = autoSaver(),
     init: () -> M,
 ): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> =
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
index f6328b7..5a88f8c 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
@@ -182,7 +182,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2018"
     description = "Android Lifecycle ViewModel"
-    legacyDisableKotlinStrictApiMode = true
     samples(project(":lifecycle:lifecycle-viewmodel-savedstate-samples"))
     metalavaK2UastEnabled = false // TODO(b/324624680)
     kotlinTarget = KotlinTarget.KOTLIN_1_9
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
index 72d930e..8a228eb 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
@@ -42,7 +42,7 @@
      * must have called [enableSavedStateHandles]. See [CreationExtras.createSavedStateHandle] docs
      * for more details.
      */
-    constructor() {}
+    public constructor() {}
 
     /**
      * Constructs this factory.
@@ -53,7 +53,7 @@
      *   passed in [ViewModels][ViewModel] if there is no previously saved state or previously saved
      *   state misses a value by such key
      */
-    constructor(owner: SavedStateRegistryOwner, defaultArgs: Bundle?) {
+    public constructor(owner: SavedStateRegistryOwner, defaultArgs: Bundle?) {
         savedStateRegistry = owner.savedStateRegistry
         lifecycle = owner.lifecycle
         this.defaultArgs = defaultArgs
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandle.android.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandle.android.kt
index af667e0..1fb0a68 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandle.android.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandle.android.kt
@@ -27,23 +27,23 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-actual class SavedStateHandle {
+public actual class SavedStateHandle {
 
     private val liveDatas = mutableMapOf<String, SavingStateLiveData<*>>()
     private var impl: SavedStateHandleImpl
 
-    actual constructor(initialState: Map<String, Any?>) {
+    public actual constructor(initialState: Map<String, Any?>) {
         impl = SavedStateHandleImpl(initialState)
     }
 
-    actual constructor() {
+    public actual constructor() {
         impl = SavedStateHandleImpl()
     }
 
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    actual fun savedStateProvider(): SavedStateProvider = impl.savedStateProvider
+    public actual fun savedStateProvider(): SavedStateProvider = impl.savedStateProvider
 
-    @MainThread actual operator fun contains(key: String): Boolean = key in impl
+    @MainThread public actual operator fun contains(key: String): Boolean = key in impl
 
     /**
      * Returns a [androidx.lifecycle.LiveData] that access data associated with the given key.
@@ -52,7 +52,7 @@
      * @see getLiveData
      */
     @MainThread
-    fun <T> getLiveData(key: String): MutableLiveData<T> {
+    public fun <T> getLiveData(key: String): MutableLiveData<T> {
         @Suppress("UNCHECKED_CAST")
         return getLiveDataInternal(key, hasInitialValue = false, initialValue = null)
             as MutableLiveData<T>
@@ -100,7 +100,7 @@
      *   given `initialValue`. Note that passing `null` will create a [LiveData] with `null` value.
      */
     @MainThread
-    fun <T> getLiveData(key: String, initialValue: T): MutableLiveData<T> {
+    public fun <T> getLiveData(key: String, initialValue: T): MutableLiveData<T> {
         return getLiveDataInternal(key, hasInitialValue = true, initialValue)
     }
 
@@ -127,7 +127,7 @@
     }
 
     @MainThread
-    actual fun <T> getStateFlow(key: String, initialValue: T): StateFlow<T> {
+    public actual fun <T> getStateFlow(key: String, initialValue: T): StateFlow<T> {
         return if (key in impl.mutableFlows) {
             // Return existing 'MutableStateFlow' as 'StateFlow' to keep values synchronized.
             impl.getMutableStateFlow(key, initialValue).asStateFlow()
@@ -137,17 +137,17 @@
     }
 
     @MainThread
-    actual fun <T> getMutableStateFlow(key: String, initialValue: T): MutableStateFlow<T> {
+    public actual fun <T> getMutableStateFlow(key: String, initialValue: T): MutableStateFlow<T> {
         require(key !in liveDatas) { createMutuallyExclusiveErrorMessage(key) }
         return impl.getMutableStateFlow(key, initialValue)
     }
 
-    @MainThread actual fun keys(): Set<String> = impl.keys() + liveDatas.keys
+    @MainThread public actual fun keys(): Set<String> = impl.keys() + liveDatas.keys
 
-    @MainThread actual operator fun <T> get(key: String): T? = impl[key]
+    @MainThread public actual operator fun <T> get(key: String): T? = impl[key]
 
     @MainThread
-    actual operator fun <T> set(key: String, value: T?) {
+    public actual operator fun <T> set(key: String, value: T?) {
         require(validateValue(value)) {
             "Can't put value with type ${value!!::class.java} into saved state"
         }
@@ -157,16 +157,16 @@
     }
 
     @MainThread
-    actual fun <T> remove(key: String): T? =
+    public actual fun <T> remove(key: String): T? =
         impl.remove<T?>(key).also { liveDatas.remove(key)?.detach() }
 
     @MainThread
-    actual fun setSavedStateProvider(key: String, provider: SavedStateProvider) {
+    public actual fun setSavedStateProvider(key: String, provider: SavedStateProvider) {
         impl.setSavedStateProvider(key, provider)
     }
 
     @MainThread
-    actual fun clearSavedStateProvider(key: String) {
+    public actual fun clearSavedStateProvider(key: String) {
         impl.clearSavedStateProvider(key)
     }
 
@@ -194,12 +194,12 @@
         }
     }
 
-    actual companion object {
+    public actual companion object {
 
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
         @Suppress("DEPRECATION")
-        actual fun createHandle(
+        public actual fun createHandle(
             restoredState: SavedState?,
             defaultState: SavedState?,
         ): SavedStateHandle {
@@ -218,7 +218,7 @@
         }
 
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-        actual fun validateValue(value: Any?): Boolean = isAcceptableType(value)
+        public actual fun validateValue(value: Any?): Boolean = isAcceptableType(value)
     }
 }
 
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.android.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.android.kt
index e8a377e..3417839 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.android.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.android.kt
@@ -40,7 +40,7 @@
  * constructor that receives [SavedStateHandle] only. [androidx.lifecycle.AndroidViewModel] is only
  * supported if you pass a non-null [Application] instance.
  */
-actual class SavedStateViewModelFactory :
+public actual class SavedStateViewModelFactory :
     ViewModelProvider.OnRequeryFactory, ViewModelProvider.Factory {
     private var application: Application? = null
     private val factory: ViewModelProvider.Factory
@@ -56,7 +56,7 @@
      *
      * @see [createSavedStateHandle] docs for more details.
      */
-    actual constructor() {
+    public actual constructor() {
         factory = ViewModelProvider.AndroidViewModelFactory()
     }
 
@@ -71,7 +71,7 @@
      * @param owner [SavedStateRegistryOwner] that will provide restored state for created
      *   [ViewModels][androidx.lifecycle.ViewModel]
      */
-    constructor(
+    public constructor(
         application: Application?,
         owner: SavedStateRegistryOwner
     ) : this(application, owner, null)
@@ -94,7 +94,11 @@
      *   if there is no previously saved state or previously saved state misses a value by such key.
      */
     @SuppressLint("LambdaLast")
-    constructor(application: Application?, owner: SavedStateRegistryOwner, defaultArgs: Bundle?) {
+    public constructor(
+        application: Application?,
+        owner: SavedStateRegistryOwner,
+        defaultArgs: Bundle?
+    ) {
         savedStateRegistry = owner.savedStateRegistry
         lifecycle = owner.lifecycle
         this.defaultArgs = defaultArgs
@@ -172,7 +176,7 @@
      * @return a newly created ViewModel
      * @throws UnsupportedOperationException if the there is no lifecycle
      */
-    fun <T : ViewModel> create(key: String, modelClass: Class<T>): T {
+    public fun <T : ViewModel> create(key: String, modelClass: Class<T>): T {
         // empty constructor was called.
         val lifecycle =
             lifecycle
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandle.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandle.kt
index 57778fb..832df34 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandle.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandle.kt
@@ -37,25 +37,25 @@
  * You can write a value to it via [set] or setting a value to [androidx.lifecycle.MutableLiveData]
  * returned by [getLiveData].
  */
-expect class SavedStateHandle {
+public expect class SavedStateHandle {
 
     /**
      * Creates a handle with the given initial arguments.
      *
      * @param initialState initial arguments for the SavedStateHandle
      */
-    constructor(initialState: Map<String, Any?>)
+    public constructor(initialState: Map<String, Any?>)
 
     /** Creates a handle with the empty state. */
-    constructor()
+    public constructor()
 
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun savedStateProvider(): SavedStateProvider
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun savedStateProvider(): SavedStateProvider
 
     /**
      * @param key The identifier for the value
      * @return true if there is value associated with the given key.
      */
-    @MainThread operator fun contains(key: String): Boolean
+    @MainThread public operator fun contains(key: String): Boolean
 
     /**
      * Returns a [StateFlow] that will emit the currently active value associated with the given
@@ -90,7 +90,7 @@
      * @param initialValue If no value exists with the given `key`, a new one is created with the
      *   given `initialValue`.
      */
-    @MainThread fun <T> getStateFlow(key: String, initialValue: T): StateFlow<T>
+    @MainThread public fun <T> getStateFlow(key: String, initialValue: T): StateFlow<T>
 
     /**
      * Returns a [MutableStateFlow] that will emit the currently active value associated with the
@@ -130,7 +130,8 @@
      * @param initialValue If no value exists with the given `key`, a new one is created with the
      *   given `initialValue`.
      */
-    @MainThread fun <T> getMutableStateFlow(key: String, initialValue: T): MutableStateFlow<T>
+    @MainThread
+    public fun <T> getMutableStateFlow(key: String, initialValue: T): MutableStateFlow<T>
 
     /**
      * Returns all keys contained in this [SavedStateHandle]
@@ -138,7 +139,7 @@
      * Returned set contains all keys: keys used to get LiveData-s, to set SavedStateProviders and
      * keys used in regular [set].
      */
-    @MainThread fun keys(): Set<String>
+    @MainThread public fun keys(): Set<String>
 
     /**
      * Returns a value associated with the given key.
@@ -157,7 +158,7 @@
      *
      * @param key a key used to retrieve a value.
      */
-    @MainThread operator fun <T> get(key: String): T?
+    @MainThread public operator fun <T> get(key: String): T?
 
     /**
      * Associate the given value with the key. The value must have a type that could be stored in
@@ -169,7 +170,7 @@
      * @param value object of any type that can be accepted by Bundle.
      * @throws IllegalArgumentException value cannot be saved in saved state
      */
-    @MainThread operator fun <T> set(key: String, value: T?)
+    @MainThread public operator fun <T> set(key: String, value: T?)
 
     /**
      * Removes a value associated with the given key. If there is a [LiveData] and/or [StateFlow]
@@ -183,7 +184,7 @@
      * @param key a key
      * @return a value that was previously associated with the given key.
      */
-    @MainThread fun <T> remove(key: String): T?
+    @MainThread public fun <T> remove(key: String): T?
 
     /**
      * Set a [SavedStateProvider] that will have its state saved into this SavedStateHandle. This
@@ -211,7 +212,7 @@
      * @param provider a SavedStateProvider which will receive a callback to
      *   [SavedStateProvider.saveState] when the state should be saved
      */
-    @MainThread fun setSavedStateProvider(key: String, provider: SavedStateProvider)
+    @MainThread public fun setSavedStateProvider(key: String, provider: SavedStateProvider)
 
     /**
      * Clear any [SavedStateProvider] that was previously set via [setSavedStateProvider].
@@ -221,15 +222,18 @@
      *
      * @param key a key previously used with [setSavedStateProvider]
      */
-    @MainThread fun clearSavedStateProvider(key: String)
+    @MainThread public fun clearSavedStateProvider(key: String)
 
-    companion object {
+    public companion object {
 
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
         @Suppress("DEPRECATION")
-        fun createHandle(restoredState: SavedState?, defaultState: SavedState?): SavedStateHandle
+        public fun createHandle(
+            restoredState: SavedState?,
+            defaultState: SavedState?
+        ): SavedStateHandle
 
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun validateValue(value: Any?): Boolean
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun validateValue(value: Any?): Boolean
     }
 }
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.kt
index 5f73418..3a04978 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.kt
@@ -44,7 +44,9 @@
  * with [SavedStateHandle] is requested.
  */
 @MainThread
-fun <T> T.enableSavedStateHandles() where T : SavedStateRegistryOwner, T : ViewModelStoreOwner {
+public fun <T> T.enableSavedStateHandles() where
+T : SavedStateRegistryOwner,
+T : ViewModelStoreOwner {
     val currentState = lifecycle.currentState
     require(currentState == Lifecycle.State.INITIALIZED || currentState == Lifecycle.State.CREATED)
 
@@ -223,10 +225,15 @@
  * A key for [SavedStateRegistryOwner] that corresponds to [ViewModelStoreOwner] of a [ViewModel]
  * that is being created.
  */
-@JvmField val SAVED_STATE_REGISTRY_OWNER_KEY = CreationExtras.Key<SavedStateRegistryOwner>()
+@JvmField
+public val SAVED_STATE_REGISTRY_OWNER_KEY: CreationExtras.Key<SavedStateRegistryOwner> =
+    CreationExtras.Key<SavedStateRegistryOwner>()
 
 /** A key for [ViewModelStoreOwner] that is an owner of a [ViewModel] that is being created. */
-@JvmField val VIEW_MODEL_STORE_OWNER_KEY = CreationExtras.Key<ViewModelStoreOwner>()
+@JvmField
+public val VIEW_MODEL_STORE_OWNER_KEY: CreationExtras.Key<ViewModelStoreOwner> =
+    CreationExtras.Key<ViewModelStoreOwner>()
 
 /** A key for default arguments that should be passed to [SavedStateHandle] if needed. */
-@JvmField val DEFAULT_ARGS_KEY = CreationExtras.Key<SavedState>()
+@JvmField
+public val DEFAULT_ARGS_KEY: CreationExtras.Key<SavedState> = CreationExtras.Key<SavedState>()
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.kt
index bafff96..cea4959 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.kt
@@ -26,7 +26,7 @@
  * contributing to a saved state via [SavedStateHandle] received in a constructor. If `defaultArgs`
  * bundle was passed into the constructor, it will provide default values in `SavedStateHandle`.
  */
-expect class SavedStateViewModelFactory : ViewModelProvider.Factory {
+public expect class SavedStateViewModelFactory : ViewModelProvider.Factory {
 
     /**
      * Constructs this factory.
@@ -36,7 +36,7 @@
      *
      * @see [createSavedStateHandle] docs for more details.
      */
-    constructor()
+    public constructor()
 
     override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T
 }
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/serialization/SavedStateHandleDelegates.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/serialization/SavedStateHandleDelegates.kt
index 2602011..a964d34 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/serialization/SavedStateHandleDelegates.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/serialization/SavedStateHandleDelegates.kt
@@ -33,7 +33,7 @@
  * @param init The function to provide the initial value of the property.
  * @return A property delegate that manages the saving and restoring of the value.
  */
-inline fun <reified T : Any> SavedStateHandle.saved(
+public inline fun <reified T : Any> SavedStateHandle.saved(
     noinline init: () -> T,
 ): ReadWriteProperty<Any?, T> {
     return saved(serializer(), init)
@@ -48,7 +48,7 @@
  * @param init The function to provide the initial value of the property.
  * @return A property delegate that manages the saving and restoring of the value.
  */
-inline fun <reified T : Any> SavedStateHandle.saved(
+public inline fun <reified T : Any> SavedStateHandle.saved(
     key: String,
     noinline init: () -> T,
 ): ReadWriteProperty<Any?, T> {
@@ -64,7 +64,7 @@
  * @param init The function to provide the initial value of the property.
  * @return A property delegate that manages the saving and restoring of the value.
  */
-fun <T : Any> SavedStateHandle.saved(
+public fun <T : Any> SavedStateHandle.saved(
     serializer: KSerializer<T>,
     init: () -> T,
 ): ReadWriteProperty<Any?, T> {
@@ -85,7 +85,7 @@
  * @param init The function to provide the initial value of the property.
  * @return A property delegate that manages the saving and restoring of the value.
  */
-fun <T : Any> SavedStateHandle.saved(
+public fun <T : Any> SavedStateHandle.saved(
     key: String,
     serializer: KSerializer<T>,
     init: () -> T,
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateHandle.nonAndroid.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateHandle.nonAndroid.kt
index 6cc51d2..8f30dc3 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateHandle.nonAndroid.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateHandle.nonAndroid.kt
@@ -28,26 +28,26 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-actual class SavedStateHandle {
+public actual class SavedStateHandle {
 
     private var impl: SavedStateHandleImpl
 
-    actual constructor(initialState: Map<String, Any?>) {
+    public actual constructor(initialState: Map<String, Any?>) {
         impl = SavedStateHandleImpl(initialState)
     }
 
-    actual constructor() {
+    public actual constructor() {
         impl = SavedStateHandleImpl()
     }
 
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    actual fun savedStateProvider(): SavedStateRegistry.SavedStateProvider =
+    public actual fun savedStateProvider(): SavedStateRegistry.SavedStateProvider =
         impl.savedStateProvider()
 
-    @MainThread actual operator fun contains(key: String): Boolean = key in impl
+    @MainThread public actual operator fun contains(key: String): Boolean = key in impl
 
     @MainThread
-    actual fun <T> getStateFlow(key: String, initialValue: T): StateFlow<T> {
+    public actual fun <T> getStateFlow(key: String, initialValue: T): StateFlow<T> {
         // On platforms other than Android, LiveData is not available.
         // Therefore, there's no need to check for mutual exclusivity with LiveData.
         // We can directly use getMutableStateFlow and convert it to a StateFlow.
@@ -55,33 +55,36 @@
     }
 
     @MainThread
-    actual fun <T> getMutableStateFlow(key: String, initialValue: T): MutableStateFlow<T> {
+    public actual fun <T> getMutableStateFlow(key: String, initialValue: T): MutableStateFlow<T> {
         return impl.getMutableStateFlow(key, initialValue)
     }
 
-    @MainThread actual fun keys(): Set<String> = impl.keys()
+    @MainThread public actual fun keys(): Set<String> = impl.keys()
 
-    @MainThread actual operator fun <T> get(key: String): T? = impl.get(key)
-
-    @MainThread actual operator fun <T> set(key: String, value: T?) = impl.set(key, value)
-
-    @MainThread actual fun <T> remove(key: String): T? = impl.remove(key)
+    @MainThread public actual operator fun <T> get(key: String): T? = impl.get(key)
 
     @MainThread
-    actual fun setSavedStateProvider(key: String, provider: SavedStateRegistry.SavedStateProvider) =
-        impl.setSavedStateProvider(key, provider)
+    public actual operator fun <T> set(key: String, value: T?): Unit = impl.set(key, value)
+
+    @MainThread public actual fun <T> remove(key: String): T? = impl.remove(key)
 
     @MainThread
-    actual fun clearSavedStateProvider(key: String) {
+    public actual fun setSavedStateProvider(
+        key: String,
+        provider: SavedStateRegistry.SavedStateProvider
+    ): Unit = impl.setSavedStateProvider(key, provider)
+
+    @MainThread
+    public actual fun clearSavedStateProvider(key: String) {
         impl.clearSavedStateProvider(key)
     }
 
-    actual companion object {
+    public actual companion object {
 
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
         @Suppress("DEPRECATION")
-        actual fun createHandle(
+        public actual fun createHandle(
             restoredState: SavedState?,
             defaultState: SavedState?,
         ): SavedStateHandle {
@@ -94,6 +97,6 @@
         }
 
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-        actual fun validateValue(value: Any?): Boolean = isAcceptableType(value)
+        public actual fun validateValue(value: Any?): Boolean = isAcceptableType(value)
     }
 }
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.nonAndroid.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.nonAndroid.kt
index 9417e87..07287e2 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.nonAndroid.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/nonAndroidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.nonAndroid.kt
@@ -24,7 +24,8 @@
  * contributing to a saved state via [SavedStateHandle] received in a constructor. If `defaultArgs`
  * bundle was passed into the constructor, it will provide default values in `SavedStateHandle`.
  */
-actual class SavedStateViewModelFactory actual constructor() : ViewModelProvider.Factory {
+public actual class SavedStateViewModelFactory public actual constructor() :
+    ViewModelProvider.Factory {
 
     // TODO(b/334076622)
     actual override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T =
diff --git a/lifecycle/lifecycle-viewmodel/build.gradle b/lifecycle/lifecycle-viewmodel/build.gradle
index 8929915..89f024c 100644
--- a/lifecycle/lifecycle-viewmodel/build.gradle
+++ b/lifecycle/lifecycle-viewmodel/build.gradle
@@ -40,10 +40,6 @@
     linux()
     ios()
 
-    kotlin {
-        explicitApi = ExplicitApiMode.Strict
-    }
-
     defaultPlatform(PlatformIdentifier.ANDROID)
 
     sourceSets {
diff --git a/navigation/navigation-common-ktx/build.gradle b/navigation/navigation-common-ktx/build.gradle
index 2d7d658..92939a2 100644
--- a/navigation/navigation-common-ktx/build.gradle
+++ b/navigation/navigation-common-ktx/build.gradle
@@ -22,7 +22,6 @@
  * modifying its settings.
  */
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
 
 plugins {
     id("AndroidXPlugin")
@@ -30,10 +29,6 @@
     id("org.jetbrains.kotlin.android")
 }
 
-kotlin {
-    explicitApi = ExplicitApiMode.Strict
-}
-
 dependencies {
     api(project(":navigation:navigation-common"))
 }
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index 0d1e04d..e657ad5 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -81,6 +81,5 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "Android Navigation-Common"
-    legacyDisableKotlinStrictApiMode = true
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
index 7f98fd6..22ebc79 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
@@ -82,7 +82,7 @@
     SavedStateRegistryOwner {
 
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    constructor(
+    public constructor(
         entry: NavBackStackEntry,
         arguments: SavedState? = entry.arguments
     ) : this(
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDeepLinkDslBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDeepLinkDslBuilder.kt
index 7e40f3c..3739cf5 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDeepLinkDslBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDeepLinkDslBuilder.kt
@@ -84,7 +84,7 @@
     private var route: KClass<*>? = null
     private var typeMap: Map<KType, NavType<*>> = emptyMap()
 
-    constructor()
+    public constructor()
 
     /**
      * DSl for constructing a new [NavDeepLink] with a route
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index a1a579df..8b04620 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -844,7 +844,7 @@
          * @param T the route from KClass
          */
         @JvmStatic
-        public inline fun <reified T : Any> NavDestination.hasRoute() = hasRoute(T::class)
+        public inline fun <reified T : Any> NavDestination.hasRoute(): Boolean = hasRoute(T::class)
 
         /**
          * Checks if the NavDestination's route was generated from [T]
@@ -855,7 +855,7 @@
          */
         @OptIn(InternalSerializationApi::class)
         @JvmStatic
-        public fun <T : Any> NavDestination.hasRoute(route: KClass<T>) =
+        public fun <T : Any> NavDestination.hasRoute(route: KClass<T>): Boolean =
             route.serializer().generateHashCode() == id
     }
 }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
index a4b1886..9853bb3 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
@@ -83,7 +83,7 @@
      * @return combined parsed value of the type represented by this NavType
      * @throws IllegalArgumentException if value cannot be parsed into this type
      */
-    public open fun parseValue(value: String, previousValue: T) = parseValue(value)
+    public open fun parseValue(value: String, previousValue: T): T = parseValue(value)
 
     /**
      * Parse a value of this type from a String and put it in a `bundle`
@@ -1024,7 +1024,7 @@
         override fun valueEquals(
             @Suppress("ArrayReturn") value: Array<D>?,
             @Suppress("ArrayReturn") other: Array<D>?
-        ) = value.contentDeepEquals(other)
+        ): Boolean = value.contentDeepEquals(other)
 
         /** Constructs a NavType that supports arrays of a given Parcelable type. */
         init {
@@ -1186,7 +1186,7 @@
         override fun valueEquals(
             @Suppress("ArrayReturn") value: Array<D>?,
             @Suppress("ArrayReturn") other: Array<D>?
-        ) = value.contentDeepEquals(other)
+        ): Boolean = value.contentDeepEquals(other)
 
         /** Constructs a NavType that supports arrays of a given Serializable type. */
         init {
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt
index dfee818..7a51698 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorState.kt
@@ -37,7 +37,7 @@
 
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public var isNavigating = false
+    public var isNavigating: Boolean = false
 
     /**
      * While the [NavController] is responsible for the combined back stack across all Navigators,
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
index 8674a9f..0fb9aa8 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
@@ -48,7 +48,7 @@
      * to the default entry by directly calling [super.encodeSerializableValue].
      */
     @Suppress("UNCHECKED_CAST")
-    fun encodeToArgMap(value: Any): Map<String, List<String>> {
+    public fun encodeToArgMap(value: Any): Map<String, List<String>> {
         super.encodeSerializableValue(serializer, value as T)
         return map.toMap()
     }
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 22971db..c5f237a 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -92,9 +92,7 @@
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2020"
     description = "Compose integration with Navigation"
-    legacyDisableKotlinStrictApiMode = true
     samples(project(":navigation:navigation-compose:navigation-compose-samples"))
-    legacyDisableKotlinStrictApiMode = true
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt
index 51a127e..3f90b45 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt
@@ -28,6 +28,7 @@
 import androidx.navigation.NavOptions
 import androidx.navigation.Navigator
 import androidx.navigation.compose.ComposeNavigator.Destination
+import kotlinx.coroutines.flow.StateFlow
 
 /**
  * Navigator that navigates through [Composable]s. Every destination using this Navigator must set a
@@ -42,7 +43,7 @@
         get() = state.transitionsInProgress
 
     /** Get the back stack from the [state]. */
-    public val backStack
+    public val backStack: StateFlow<List<NavBackStackEntry>>
         get() = state.backStack
 
     internal val isPop = mutableStateOf(false)
@@ -102,7 +103,7 @@
             message = "Deprecated in favor of Destination that supports AnimatedContent",
             level = DeprecationLevel.HIDDEN,
         )
-        constructor(
+        public constructor(
             navigator: ComposeNavigator,
             content: @Composable (NavBackStackEntry) -> @JvmSuppressWildcards Unit
         ) : this(navigator, content = { entry -> content(entry) })
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigatorDestinationBuilder.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigatorDestinationBuilder.kt
index f27f4a69..7e1ee8d 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigatorDestinationBuilder.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigatorDestinationBuilder.kt
@@ -37,27 +37,27 @@
     private val composeNavigator: ComposeNavigator
     private val content: @Composable (AnimatedContentScope.(NavBackStackEntry) -> Unit)
 
-    var enterTransition:
+    public var enterTransition:
         (@JvmSuppressWildcards
         AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
         null
 
-    var exitTransition:
+    public var exitTransition:
         (@JvmSuppressWildcards
         AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
         null
 
-    var popEnterTransition:
+    public var popEnterTransition:
         (@JvmSuppressWildcards
         AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
         null
 
-    var popExitTransition:
+    public var popExitTransition:
         (@JvmSuppressWildcards
         AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
         null
 
-    var sizeTransform:
+    public var sizeTransform:
         (@JvmSuppressWildcards
         AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? =
         null
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
index 1bedd08..0a5ea75 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
@@ -390,7 +390,7 @@
     navController: NavHostController,
     graph: NavGraph,
     modifier: Modifier = Modifier
-) = NavHost(navController, graph, modifier)
+): Unit = NavHost(navController, graph, modifier)
 
 /**
  * Provides a place in the Compose hierarchy for self contained navigation to occur.
diff --git a/navigation/navigation-dynamic-features-fragment/build.gradle b/navigation/navigation-dynamic-features-fragment/build.gradle
index 3a85fc3..1cb737c 100644
--- a/navigation/navigation-dynamic-features-fragment/build.gradle
+++ b/navigation/navigation-dynamic-features-fragment/build.gradle
@@ -73,6 +73,5 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2019"
     description = "Android Dynamic Feature Navigation Fragment"
-    legacyDisableKotlinStrictApiMode = true
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/navigation/navigation-dynamic-features-runtime/build.gradle b/navigation/navigation-dynamic-features-runtime/build.gradle
index 7d941b4..7ee3ce5 100644
--- a/navigation/navigation-dynamic-features-runtime/build.gradle
+++ b/navigation/navigation-dynamic-features-runtime/build.gradle
@@ -73,5 +73,4 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2019"
     description = "Android Dynamic Feature Navigation Runtime"
-    legacyDisableKotlinStrictApiMode = true
 }
diff --git a/navigation/navigation-fragment-compose/build.gradle b/navigation/navigation-fragment-compose/build.gradle
index 5e73fd3..4390216 100644
--- a/navigation/navigation-fragment-compose/build.gradle
+++ b/navigation/navigation-fragment-compose/build.gradle
@@ -72,6 +72,5 @@
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2024"
     description = "Add Compose destinations to Navigation with Fragments"
-    legacyDisableKotlinStrictApiMode = true
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragment.kt b/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragment.kt
index a77097f..890b411 100644
--- a/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragment.kt
+++ b/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragment.kt
@@ -35,7 +35,7 @@
  * This class is constructed via a factory method: make sure you add `import
  * androidx.navigation.fragment.compose.ComposableFragment.Companion.ComposableFragment`
  */
-class ComposableFragment internal constructor() : Fragment() {
+public class ComposableFragment internal constructor() : Fragment() {
 
     private val composableMethod by lazy {
         val arguments = requireArguments()
@@ -65,7 +65,7 @@
         }
     }
 
-    companion object {
+    public companion object {
         internal const val FULLY_QUALIFIED_NAME =
             "androidx.navigation.fragment.compose.FULLY_QUALIFIED_NAME"
 
@@ -78,7 +78,7 @@
          *   `com.example.NameOfFileKt/$MethodName`.
          */
         @JvmStatic
-        fun ComposableFragment(fullyQualifiedName: String): ComposableFragment {
+        public fun ComposableFragment(fullyQualifiedName: String): ComposableFragment {
             return ComposableFragment().apply {
                 arguments = bundleOf(FULLY_QUALIFIED_NAME to fullyQualifiedName)
             }
diff --git a/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragmentNavigator.kt b/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragmentNavigator.kt
index e3de833..a92710a 100644
--- a/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragmentNavigator.kt
+++ b/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/ComposableFragmentNavigator.kt
@@ -35,14 +35,14 @@
  * Internally, this uses a [ComposableFragment] to implement the reflection call.
  */
 @Navigator.Name("composable")
-class ComposableFragmentNavigator(private val fragmentNavigator: FragmentNavigator) :
+public class ComposableFragmentNavigator(private val fragmentNavigator: FragmentNavigator) :
     Navigator<FragmentNavigator.Destination>() {
 
     /**
      * Construct a [ComposableFragmentNavigator] by retrieving the associated [FragmentNavigator]
      * from [provider].
      */
-    constructor(provider: NavigatorProvider) : this(provider[FragmentNavigator::class])
+    public constructor(provider: NavigatorProvider) : this(provider[FragmentNavigator::class])
 
     override fun createDestination(): FragmentNavigator.Destination {
         // Note how we associate the destination with the given
diff --git a/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/LocalFragment.kt b/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/LocalFragment.kt
index f743993..fb6df6d 100644
--- a/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/LocalFragment.kt
+++ b/navigation/navigation-fragment-compose/src/main/java/androidx/navigation/fragment/compose/LocalFragment.kt
@@ -16,6 +16,7 @@
 
 package androidx.navigation.fragment.compose
 
+import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.fragment.app.Fragment
 
@@ -23,7 +24,7 @@
  * The CompositionLocal containing the containing [Fragment]. This is sett by default for
  * composables created within a [ComposableFragment].
  */
-val LocalFragment =
+public val LocalFragment: ProvidableCompositionLocal<Fragment> =
     staticCompositionLocalOf<Fragment> {
         error(
             "CompositionLocal Fragment not present: are you sure your composable is within a " +
diff --git a/navigation/navigation-fragment/build.gradle b/navigation/navigation-fragment/build.gradle
index dbc7670..33c4ece 100644
--- a/navigation/navigation-fragment/build.gradle
+++ b/navigation/navigation-fragment/build.gradle
@@ -73,7 +73,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "Android Navigation-Fragment"
-    legacyDisableKotlinStrictApiMode = true
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
index 62563f3..85c1c1b 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
@@ -43,7 +43,7 @@
  * be overridden by [AbstractListDetailFragment.onCreateDetailPaneNavHostFragment] and provide
  * custom NavHostFragment.
  */
-abstract class AbstractListDetailFragment : Fragment() {
+public abstract class AbstractListDetailFragment : Fragment() {
     private var onBackPressedCallback: OnBackPressedCallback? = null
     private var _detailPaneNavHostFragment: NavHostFragment? = null
     private var graphId = 0
@@ -53,7 +53,7 @@
      *
      * @throws IllegalStateException if the SlidingPaneLayout has not been created by [onCreateView]
      */
-    val slidingPaneLayout: SlidingPaneLayout
+    public val slidingPaneLayout: SlidingPaneLayout
         get() = requireView() as SlidingPaneLayout
 
     /**
@@ -62,7 +62,7 @@
      * @throws IllegalStateException if the NavHostFragment has not been created by
      *   {@link #onCreateView}.
      */
-    val detailPaneNavHostFragment: NavHostFragment
+    public val detailPaneNavHostFragment: NavHostFragment
         get() {
             checkNotNull(_detailPaneNavHostFragment) {
                 "Fragment $this was called before onCreateView()."
@@ -180,7 +180,7 @@
      * @param savedInstanceState The previous saved state of the fragment.
      * @return Return the list pane view for the fragment.
      */
-    abstract fun onCreateListPaneView(
+    public abstract fun onCreateListPaneView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
@@ -190,7 +190,7 @@
      * Return an alternative [NavHostFragment] to swap the default NavHostFragment in the fragment.
      * This method get called when creating the view of the fragment.
      */
-    open fun onCreateDetailPaneNavHostFragment(): NavHostFragment {
+    public open fun onCreateDetailPaneNavHostFragment(): NavHostFragment {
         if (graphId != 0) {
             return NavHostFragment.create(graphId)
         }
@@ -219,7 +219,7 @@
      * @param view The list pane view created by [onCreateListPaneView] and added to view hierarchy
      * @param savedInstanceState The previous saved state of the fragment.
      */
-    open fun onListPaneViewCreated(view: View, savedInstanceState: Bundle?) {}
+    public open fun onListPaneViewCreated(view: View, savedInstanceState: Bundle?) {}
 
     @CallSuper
     override fun onViewStateRestored(savedInstanceState: Bundle?) {
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 7fa1524a..1ad4631 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -79,6 +79,5 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "Android Navigation-Runtime"
-    legacyDisableKotlinStrictApiMode = true
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/navigation/navigation-testing/build.gradle b/navigation/navigation-testing/build.gradle
index 465107f..973ac73 100644
--- a/navigation/navigation-testing/build.gradle
+++ b/navigation/navigation-testing/build.gradle
@@ -68,7 +68,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "Android Navigation-Testing"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/navigation/navigation-ui/build.gradle b/navigation/navigation-ui/build.gradle
index 243e779..5ea098e 100644
--- a/navigation/navigation-ui/build.gradle
+++ b/navigation/navigation-ui/build.gradle
@@ -66,5 +66,4 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2018"
     description = "Android Navigation-UI"
-    legacyDisableKotlinStrictApiMode = true
 }
diff --git a/samples/Support4Demos/build.gradle b/samples/Support4Demos/build.gradle
index e03d966..a168941 100644
--- a/samples/Support4Demos/build.gradle
+++ b/samples/Support4Demos/build.gradle
@@ -28,6 +28,8 @@
     implementation(project(":coordinatorlayout:coordinatorlayout"))
     implementation("com.google.android.material:material:1.6.0")
     implementation(project(":appcompat:appcompat"))
+    api(project(":preference:preference"))
+
 }
 
 android {
diff --git a/samples/Support4Demos/src/main/AndroidManifest.xml b/samples/Support4Demos/src/main/AndroidManifest.xml
index 6865741..c3cfdba 100644
--- a/samples/Support4Demos/src/main/AndroidManifest.xml
+++ b/samples/Support4Demos/src/main/AndroidManifest.xml
@@ -438,6 +438,28 @@
         </activity>
 
         <activity
+            android:name=".widget.PreferenceScreenWithCollapsingToolbarActivity"
+            android:exported="true"
+            android:theme="@style/Theme.AppCompat.Light"
+            android:label="@string/preference_screen_collapsing">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".widget.RecyclerViewWithCollapsingToolbarActivity"
+            android:exported="true"
+            android:theme="@style/Theme.AppCompat.Light"
+            android:label="@string/recycler_view_with_collapsing_toolbar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity
             android:name=".graphics.RoundedBitmapDrawableActivity"
             android:exported="true"
             android:label="Graphics/RoundedBitmapDrawable">
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/PreferenceScreenDetailsTemplateFragment.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/PreferenceScreenDetailsTemplateFragment.java
new file mode 100644
index 0000000..131dac5
--- /dev/null
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/PreferenceScreenDetailsTemplateFragment.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.supportv4.widget;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.example.android.supportv4.R;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Simple LinearLayout with ~50 TextViews.
+ */
+public class PreferenceScreenDetailsTemplateFragment extends Fragment {
+
+    @Override
+    public @NonNull View onCreateView(
+            @NonNull LayoutInflater inflater,
+            @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState
+    ) {
+        View view =  inflater.inflate(
+                R.layout.fragment_preference_screen_details_template,
+                container,
+                false
+        );
+
+        LinearLayout linearLayout = view.findViewById(R.id.linear_layout);
+
+        for (int index = 1; index <= 50; index++) {
+            TextView textView = new TextView(getContext());
+            textView.setText("TextView " + index);
+            textView.setFocusable(true);
+            textView.setFocusableInTouchMode(true); // For keyboard/touch based focus
+            textView.setPadding(16, 16, 16, 16);
+
+            linearLayout.addView(textView);
+        }
+        return view;
+    }
+}
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/PreferenceScreenWithCollapsingToolbarActivity.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/PreferenceScreenWithCollapsingToolbarActivity.java
new file mode 100644
index 0000000..676c9ee
--- /dev/null
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/PreferenceScreenWithCollapsingToolbarActivity.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.supportv4.widget;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.example.android.supportv4.R;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * This activity demonstrates the use of scrolling with a PreferenceScreen in the v4 support
+ * library along with a collapsing app bar (everything inside a CoordinatorLayout). See the
+ * associated layout file for details.
+ * Note: The PreferenceScreen fragment roughly demonstrates the Android Settings App (specifically
+ * the app screen).
+ */
+public class PreferenceScreenWithCollapsingToolbarActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_preference_screen_collapsing_toolbar);
+
+        CollapsingToolbarLayout collapsingToolbar = findViewById(R.id.collapsing_toolbar_layout);
+        collapsingToolbar.setTitle(
+                getResources().getString(R.string.preference_screen_collapsing_appbar_title)
+        );
+        collapsingToolbar.setContentScrimColor(getResources().getColor(R.color.color1));
+
+        MyPreferenceFragment fragment = new MyPreferenceFragment();
+        getSupportFragmentManager().beginTransaction()
+                .replace(R.id.main_content, fragment)
+                .commit();
+    }
+
+    public static class MyPreferenceFragment extends PreferenceFragmentCompat {
+        @Override
+        public void onCreatePreferences(
+                @Nullable Bundle savedInstanceState,
+                @Nullable String rootKey
+        ) {
+            setPreferencesFromResource(R.xml.demo_pref_screen, rootKey);
+        }
+    }
+}
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/RecyclerViewWithCollapsingToolbarActivity.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/RecyclerViewWithCollapsingToolbarActivity.java
new file mode 100644
index 0000000..dbcd566
--- /dev/null
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/RecyclerViewWithCollapsingToolbarActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.supportv4.widget;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.example.android.supportv4.R;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Demonstrates the use of recycler view in nested scrolling (testing scrolling with a keyboard).
+ * See the associated layout file for details.
+ */
+public class RecyclerViewWithCollapsingToolbarActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_recycler_view_with_collapsing_toolbar);
+
+        CollapsingToolbarLayout collapsingToolbar = findViewById(R.id.collapsing_toolbar_layout);
+        collapsingToolbar.setTitle(
+                getResources().getString(R.string.preference_screen_collapsing_appbar_title)
+        );
+        collapsingToolbar.setContentScrimColor(getResources().getColor(R.color.color1));
+
+        RecyclerView recyclerView = findViewById(R.id.recycler_view);
+        recyclerView.setLayoutManager(new LinearLayoutManager(this));
+
+        List<String> data = new ArrayList<String>();
+        for (int index = 0; index < 14; index++) {
+            data.add(String.valueOf(index));
+        }
+
+        MyAdapter adapter = new MyAdapter(data);
+        recyclerView.setAdapter(adapter);
+    }
+
+    public static class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
+        private final List<String> mDataForItems;
+
+        public MyAdapter(@NonNull List<String> items) {
+            this.mDataForItems = items;
+        }
+
+        public static class ViewHolder extends RecyclerView.ViewHolder {
+            @NonNull public TextView textViewHeader;
+            @NonNull public TextView textViewSubHeader;
+
+            public ViewHolder(@NonNull View itemView) {
+                super(itemView);
+                textViewHeader = itemView.findViewById(R.id.textViewHeader);
+                textViewSubHeader = itemView.findViewById(R.id.textViewSubHeader);
+            }
+        }
+
+        @Override
+        public MyAdapter.@NonNull ViewHolder onCreateViewHolder(
+                @NonNull ViewGroup parent,
+                int viewType
+        ) {
+            View itemView = LayoutInflater.from(parent.getContext()).inflate(
+                    R.layout.recycler_view_with_collapsing_toolbar_list_item,
+                    parent,
+                    false
+            );
+            return new ViewHolder(itemView);
+        }
+
+        @Override
+        public void onBindViewHolder(
+                MyAdapter.@NonNull ViewHolder holder,
+                int position
+        ) {
+            String number = mDataForItems.get(position);
+
+            holder.textViewHeader.setText(number);
+            holder.textViewSubHeader.setText("Sub Header for " + number);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mDataForItems.size();
+        }
+    }
+}
diff --git a/samples/Support4Demos/src/main/res/layout/activity_preference_screen_collapsing_toolbar.xml b/samples/Support4Demos/src/main/res/layout/activity_preference_screen_collapsing_toolbar.xml
new file mode 100644
index 0000000..4b2888f
--- /dev/null
+++ b/samples/Support4Demos/src/main/res/layout/activity_preference_screen_collapsing_toolbar.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:fitsSystemWindows="true"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="widget.PreferenceScreenWithCollapsingToolbarActivity">
+
+    <!-- App Bar -->
+    <com.google.android.material.appbar.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="200dp">
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:expandedTitleMarginStart="48dp"
+            app:expandedTitleMarginEnd="64dp"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
+
+            <View android:layout_width="match_parent"
+                android:layout_height="200dp"
+                android:background="@color/color2"/>
+
+            <androidx.appcompat.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin" />
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <!-- Content -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+        <FrameLayout
+            android:id="@+id/main_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/samples/Support4Demos/src/main/res/layout/activity_recycler_view_with_collapsing_toolbar.xml b/samples/Support4Demos/src/main/res/layout/activity_recycler_view_with_collapsing_toolbar.xml
new file mode 100644
index 0000000..7078a3d
--- /dev/null
+++ b/samples/Support4Demos/src/main/res/layout/activity_recycler_view_with_collapsing_toolbar.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:fitsSystemWindows="true"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".widget.RecyclerViewWithCollapsingToolbarActivity">
+
+    <!-- App Bar -->
+    <com.google.android.material.appbar.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="200dp">
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:expandedTitleMarginStart="48dp"
+            app:expandedTitleMarginEnd="64dp"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
+
+            <View android:layout_width="match_parent"
+                android:layout_height="200dp"
+                android:background="@color/color2"/>
+
+            <androidx.appcompat.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin" />
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <!-- Content -->
+    <FrameLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/recycler_view"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            </androidx.recyclerview.widget.RecyclerView>
+    </FrameLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/samples/Support4Demos/src/main/res/layout/fragment_preference_screen_details_template.xml b/samples/Support4Demos/src/main/res/layout/fragment_preference_screen_details_template.xml
new file mode 100644
index 0000000..b5f4441
--- /dev/null
+++ b/samples/Support4Demos/src/main/res/layout/fragment_preference_screen_details_template.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".widget.PreferenceScreenDetailsTemplateFragment">
+
+    <LinearLayout
+        android:id="@+id/linear_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical" />
+</FrameLayout>
diff --git a/samples/Support4Demos/src/main/res/layout/recycler_view_with_collapsing_toolbar_list_item.xml b/samples/Support4Demos/src/main/res/layout/recycler_view_with_collapsing_toolbar_list_item.xml
new file mode 100644
index 0000000..675980c
--- /dev/null
+++ b/samples/Support4Demos/src/main/res/layout/recycler_view_with_collapsing_toolbar_list_item.xml
@@ -0,0 +1,34 @@
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="16dp"
+    android:focusable="true">
+
+    <TextView
+        android:id="@+id/textViewHeader"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="24sp" />
+
+    <TextView
+        android:id="@+id/textViewSubHeader"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="14sp" />
+</LinearLayout>
diff --git a/samples/Support4Demos/src/main/res/values/strings.xml b/samples/Support4Demos/src/main/res/values/strings.xml
index 3ebe008..08b2848 100644
--- a/samples/Support4Demos/src/main/res/values/strings.xml
+++ b/samples/Support4Demos/src/main/res/values/strings.xml
@@ -13,7 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <resources>
     <string name="activity_sample_code">Support v4 Demos</string>
 
@@ -233,14 +232,21 @@
 
     <string name="nested_scroll">Widget/Nested Scrolling</string>
     <string name="nested_scroll_3_levels">Widget/Nested Scrolling (3 levels)</string>
+
     <string name="nested_scroll_3_levels_collapsing">Widget/Nested Scrolling (3 levels) with Collapsing Toolbar</string>
     <string name="nested_scroll_3_levels_collapsing_appbar_title">Collapsing Appbar</string>
 
+    <string name="preference_screen_collapsing">Widget/PreferenceScreen with Collapsing Appbar</string>
+    <string name="preference_screen_collapsing_appbar_title">Collapsing Appbar</string>
+
     <string name="nested_scroll_long_text">This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it.</string>
     <string name="nested_scroll_short_text">This is shorter text. In fact, it was designed to be half as long as the long text, it is very close. This is shorter text. In fact, it was designed to be half as long as the long text, it is very close. This is shorter text. In fact, it was designed to be half as long as the long text, it is very close. This is shorter text. In fact, it was designed to be half as long as the long text, it is very close. This is shorter text. In fact, it was designed to be half as long as the long text, it is very close.</string>
     <string name="regular">regular</string>
     <string name="round">round</string>
 
+    <string name="recycler_view_with_collapsing_toolbar">Widget/RecyclerView with Collapsing Appbar</string>
+    <string name="recycler_view_with_collapsing_toolbar_title">Header</string>
+
     <string name="drawable_compat_no_tint">Not tint</string>
     <string name="drawable_compat_color_tint">Color tint</string>
     <string name="drawable_compat_color_list_tint">Color state list</string>
diff --git a/samples/Support4Demos/src/main/res/xml/demo_pref_screen.xml b/samples/Support4Demos/src/main/res/xml/demo_pref_screen.xml
new file mode 100644
index 0000000..2f5f7791
--- /dev/null
+++ b/samples/Support4Demos/src/main/res/xml/demo_pref_screen.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<android:PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:key="apps_screen"
+    android:title="apps_dashboard_title">
+
+    <Preference
+        android:key="all_app_infos"
+        android:title="All Apps Demo"
+        android:summary="Summary Placeholder"
+        android:order="-999"
+        android:fragment="com.example.android.supportv4.widget.PreferenceScreenDetailsTemplateFragment"
+        />
+
+    <PreferenceCategory
+        android:key="recent_apps_category"
+        android:title="Recent App Category Title"
+        android:order="-998">
+        <!-- Placeholder for a list of recent apps -->
+        <Preference
+            android:key="see_all_apps"
+            android:title="See all Apps Title"
+            android:icon="@drawable/ic_star_on"
+            android:fragment="com.example.android.supportv4.widget.PreferenceScreenDetailsTemplateFragment"
+            android:order="5">
+        </Preference>
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="general_category"
+        android:title="Category Name General"
+        android:order="-997"
+        android:visibility="gone"/>
+
+    <Preference
+        android:key="default_apps"
+        android:title="App Default Dashboard"
+        android:order="-996"/>
+
+    <Preference
+        android:key="cloned_apps"
+        android:title="Cloned Apps Dashboard"
+        android:summary="Summary Placeholder"
+        android:order="-995"
+        android:fragment="com.example.android.supportv4.widget.PreferenceScreenDetailsTemplateFragment">
+    </Preference>
+
+    <PreferenceCategory
+        android:key="dashboard_tile_placeholder"
+        android:order="10"/>
+
+    <Preference
+        android:key="contacts_storage"
+        android:title="Contacts Storage Settings"
+        android:summary="Summary Placeholder"
+        android:order="13"
+        android:fragment="com.example.android.supportv4.widget.PreferenceScreenDetailsTemplateFragment"
+        >
+    </Preference>
+
+    <Preference
+        android:key="hibernated_apps"
+        android:title="Unused Apps"
+        android:summary="Summary Placeholder"
+        android:order="15"
+        />
+
+    <Preference
+        android:key="app_battery_usage"
+        android:order="17"
+        android:title="App Battery Usage"
+        android:summary="Summary Placeholder"
+        android:fragment="com.example.android.supportv4.widget.PreferenceScreenDetailsTemplateFragment">
+    </Preference>
+
+    <Preference
+        android:key="special_access"
+        android:fragment="com.example.android.supportv4.widget.PreferenceScreenDetailsTemplateFragment"
+        android:title="Special Access"
+        android:order="20"/>
+
+    <PreferenceCategory
+        android:key="advanced_category"
+        android:title="Advanced Apps"
+        android:order="21">
+
+        <Preference
+            android:key="aspect_ratio_apps"
+            android:title="Aspect Ratio Experimental"
+            android:summary="Summary Placeholder"
+            android:order="22"
+            android:fragment="com.example.android.supportv4.widget.PreferenceScreenDetailsTemplateFragment">
+        </Preference>
+    </PreferenceCategory>
+</android:PreferenceScreen>
diff --git a/savedstate/savedstate-compose/build.gradle b/savedstate/savedstate-compose/build.gradle
index fcb5297..729ff50 100644
--- a/savedstate/savedstate-compose/build.gradle
+++ b/savedstate/savedstate-compose/build.gradle
@@ -22,10 +22,6 @@
     jvmStubs()
     linuxX64Stubs()
 
-    kotlin {
-        explicitApi = ExplicitApiMode.Strict
-    }
-
     defaultPlatform(PlatformIdentifier.ANDROID)
 
     sourceSets {
@@ -99,7 +95,6 @@
     samples(project(":savedstate:savedstate-compose"))
     inceptionYear = "2018"
     description = "Compose integration with Saved State"
-    legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/savedstate/savedstate-compose/src/androidMain/kotlin/androidx/savedstate/compose/LocalSavedStateRegistryOwner.android.kt b/savedstate/savedstate-compose/src/androidMain/kotlin/androidx/savedstate/compose/LocalSavedStateRegistryOwner.android.kt
index 547f05e..fc94ea0 100644
--- a/savedstate/savedstate-compose/src/androidMain/kotlin/androidx/savedstate/compose/LocalSavedStateRegistryOwner.android.kt
+++ b/savedstate/savedstate-compose/src/androidMain/kotlin/androidx/savedstate/compose/LocalSavedStateRegistryOwner.android.kt
@@ -36,30 +36,32 @@
  * Please note that backward compatibility reflection may be removed once Compose >= 1.8 is stable.
  * A Gradle dependency constraint will be put in place to ensure smooth migration for clients.
  */
-actual val LocalSavedStateRegistryOwner: ProvidableCompositionLocal<SavedStateRegistryOwner> = run {
-    val compositionLocalFromComposeUi = runCatching {
-        // Use the CompositionLocal class to find the `classLoader` from the `Application`.
-        val classLoader = SavedStateRegistryOwner::class.java.classLoader!!
-        // Top-level class name from Compose UI 1.6.* that holds the old CompositionLocal.
-        val className = "androidx.compose.ui.platform.AndroidCompositionLocals_androidKt"
-        // The Java getter used when accessing the CompositionLocal property in Kotlin.
-        val methodName = "getLocalSavedStateRegistryOwner"
+public actual val LocalSavedStateRegistryOwner:
+    ProvidableCompositionLocal<SavedStateRegistryOwner> =
+    run {
+        val compositionLocalFromComposeUi = runCatching {
+            // Use the CompositionLocal class to find the `classLoader` from the `Application`.
+            val classLoader = SavedStateRegistryOwner::class.java.classLoader!!
+            // Top-level class name from Compose UI 1.6.* that holds the old CompositionLocal.
+            val className = "androidx.compose.ui.platform.AndroidCompositionLocals_androidKt"
+            // The Java getter used when accessing the CompositionLocal property in Kotlin.
+            val methodName = "getLocalSavedStateRegistryOwner"
 
-        val methodRef = classLoader.loadClass(className).getMethod(methodName)
-        if (methodRef.annotations.none { it is Deprecated }) {
-            // If the method IS NOT deprecated, we are running with Compose 1.6.*.
-            // We use reflection to access the older CompositionLocal from `compose-ui`.
-            @Suppress("UNCHECKED_CAST", "BanUncheckedReflection")
-            methodRef.invoke(null) as? ProvidableCompositionLocal<SavedStateRegistryOwner>
-        } else {
-            // If the method IS deprecated, we are running with Compose 1.7.*.
-            // The new CompositionLocal is available, no reflection needed.
-            null
+            val methodRef = classLoader.loadClass(className).getMethod(methodName)
+            if (methodRef.annotations.none { it is Deprecated }) {
+                // If the method IS NOT deprecated, we are running with Compose 1.6.*.
+                // We use reflection to access the older CompositionLocal from `compose-ui`.
+                @Suppress("UNCHECKED_CAST", "BanUncheckedReflection")
+                methodRef.invoke(null) as? ProvidableCompositionLocal<SavedStateRegistryOwner>
+            } else {
+                // If the method IS deprecated, we are running with Compose 1.7.*.
+                // The new CompositionLocal is available, no reflection needed.
+                null
+            }
         }
+
+        return@run compositionLocalFromComposeUi.getOrNull()
+            ?: staticCompositionLocalOf<SavedStateRegistryOwner> {
+                error("CompositionLocal LocalSavedStateRegistryOwner not present")
+            }
     }
-
-    return@run compositionLocalFromComposeUi.getOrNull()
-        ?: staticCompositionLocalOf<SavedStateRegistryOwner> {
-            error("CompositionLocal LocalSavedStateRegistryOwner not present")
-        }
-}
diff --git a/savedstate/savedstate/build.gradle b/savedstate/savedstate/build.gradle
index 91ce8e0..e74d23c 100644
--- a/savedstate/savedstate/build.gradle
+++ b/savedstate/savedstate/build.gradle
@@ -25,10 +25,6 @@
     linux()
     mac()
 
-    kotlin {
-        explicitApi = ExplicitApiMode.Strict
-    }
-
     defaultPlatform(PlatformIdentifier.ANDROID)
 
     sourceSets {
@@ -174,7 +170,6 @@
     samples(project(":savedstate:savedstate-samples"))
     inceptionYear = "2018"
     description = "Android Lifecycle Saved State"
-    legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
index f12b21c..7e7452f 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
@@ -34,7 +34,7 @@
 
 @Suppress("ValueClassDefinition")
 @JvmInline
-actual value class SavedStateReader
+public actual value class SavedStateReader
 @PublishedApi
 internal actual constructor(
     @PublishedApi internal actual val source: SavedState,
@@ -47,7 +47,7 @@
      * @return The value associated with the [key].
      * @throws IllegalStateException If the key is not found.
      */
-    inline fun getBinder(key: String): IBinder {
+    public inline fun getBinder(key: String): IBinder {
         if (key !in this) keyNotFoundError(key)
         return source.getBinder(key) ?: valueNotFoundError(key)
     }
@@ -62,32 +62,32 @@
      * @return The value associated with the [key], or the result of [defaultValue] if the key is
      *   not found or the associated value has the wrong type.
      */
-    inline fun getBinderOrElse(key: String, defaultValue: () -> IBinder): IBinder {
+    public inline fun getBinderOrElse(key: String, defaultValue: () -> IBinder): IBinder {
         if (key !in this) defaultValue()
         return source.getBinder(key) ?: defaultValue()
     }
 
-    actual inline fun getBoolean(key: String): Boolean {
+    public actual inline fun getBoolean(key: String): Boolean {
         if (key !in this) keyNotFoundError(key)
         return source.getBoolean(key, DEFAULT_BOOLEAN)
     }
 
-    actual inline fun getBooleanOrElse(key: String, defaultValue: () -> Boolean): Boolean {
+    public actual inline fun getBooleanOrElse(key: String, defaultValue: () -> Boolean): Boolean {
         if (key !in this) defaultValue()
         return source.getBoolean(key, defaultValue())
     }
 
-    actual inline fun getChar(key: String): Char {
+    public actual inline fun getChar(key: String): Char {
         if (key !in this) keyNotFoundError(key)
         return source.getChar(key, DEFAULT_CHAR)
     }
 
-    actual inline fun getCharSequence(key: String): CharSequence {
+    public actual inline fun getCharSequence(key: String): CharSequence {
         if (key !in this) keyNotFoundError(key)
         return source.getCharSequence(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getCharSequenceOrElse(
+    public actual inline fun getCharSequenceOrElse(
         key: String,
         defaultValue: () -> CharSequence
     ): CharSequence {
@@ -95,47 +95,47 @@
         return source.getCharSequence(key) ?: defaultValue()
     }
 
-    actual inline fun getCharOrElse(key: String, defaultValue: () -> Char): Char {
+    public actual inline fun getCharOrElse(key: String, defaultValue: () -> Char): Char {
         if (key !in this) defaultValue()
         return source.getChar(key, defaultValue())
     }
 
-    actual inline fun getDouble(key: String): Double {
+    public actual inline fun getDouble(key: String): Double {
         if (key !in this) keyNotFoundError(key)
         return source.getDouble(key, DEFAULT_DOUBLE)
     }
 
-    actual inline fun getDoubleOrElse(key: String, defaultValue: () -> Double): Double {
+    public actual inline fun getDoubleOrElse(key: String, defaultValue: () -> Double): Double {
         if (key !in this) defaultValue()
         return source.getDouble(key, defaultValue())
     }
 
-    actual inline fun getFloat(key: String): Float {
+    public actual inline fun getFloat(key: String): Float {
         if (key !in this) keyNotFoundError(key)
         return source.getFloat(key, DEFAULT_FLOAT)
     }
 
-    actual inline fun getFloatOrElse(key: String, defaultValue: () -> Float): Float {
+    public actual inline fun getFloatOrElse(key: String, defaultValue: () -> Float): Float {
         if (key !in this) defaultValue()
         return source.getFloat(key, defaultValue())
     }
 
-    actual inline fun getInt(key: String): Int {
+    public actual inline fun getInt(key: String): Int {
         if (key !in this) keyNotFoundError(key)
         return source.getInt(key, DEFAULT_INT)
     }
 
-    actual inline fun getIntOrElse(key: String, defaultValue: () -> Int): Int {
+    public actual inline fun getIntOrElse(key: String, defaultValue: () -> Int): Int {
         if (key !in this) defaultValue()
         return source.getInt(key, defaultValue())
     }
 
-    actual inline fun getLong(key: String): Long {
+    public actual inline fun getLong(key: String): Long {
         if (key !in this) keyNotFoundError(key)
         return source.getLong(key, DEFAULT_LONG)
     }
 
-    actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long {
+    public actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long {
         if (key !in this) defaultValue()
         return source.getLong(key, defaultValue())
     }
@@ -147,7 +147,7 @@
      * @return The value associated with the [key].
      * @throws IllegalStateException If the key is not found.
      */
-    inline fun <reified T : Parcelable> getParcelable(key: String): T {
+    public inline fun <reified T : Parcelable> getParcelable(key: String): T {
         if (key !in this) keyNotFoundError(key)
         return getParcelable(source, key, T::class.java) ?: valueNotFoundError(key)
     }
@@ -162,7 +162,10 @@
      * @return The value associated with the [key], or the result of [defaultValue] if the key is
      *   not found or the associated value has the wrong type.
      */
-    inline fun <reified T : Parcelable> getParcelableOrElse(key: String, defaultValue: () -> T): T {
+    public inline fun <reified T : Parcelable> getParcelableOrElse(
+        key: String,
+        defaultValue: () -> T
+    ): T {
         if (key !in this) defaultValue()
         return getParcelable(source, key, T::class.java) ?: defaultValue()
     }
@@ -174,7 +177,7 @@
      * @return The value associated with the [key].
      * @throws IllegalStateException If the key is not found.
      */
-    inline fun <reified T : Serializable> getJavaSerializable(key: String): T {
+    public inline fun <reified T : Serializable> getJavaSerializable(key: String): T {
         if (key !in this) keyNotFoundError(key)
         return getSerializable(source, key, T::class.java) ?: valueNotFoundError(key)
     }
@@ -189,7 +192,7 @@
      * @return The value associated with the [key], or the result of [defaultValue] if the key is
      *   not found or the associated value has the wrong type.
      */
-    inline fun <reified T : Serializable> getJavaSerializableOrElse(
+    public inline fun <reified T : Serializable> getJavaSerializableOrElse(
         key: String,
         defaultValue: () -> T
     ): T {
@@ -204,7 +207,7 @@
      * @return The value associated with the [key].
      * @throws IllegalStateException If the key is not found.
      */
-    inline fun getSize(key: String): Size {
+    public inline fun getSize(key: String): Size {
         if (key !in this) keyNotFoundError(key)
         return source.getSize(key) ?: valueNotFoundError(key)
     }
@@ -219,7 +222,7 @@
      * @return The value associated with the [key], or the result of [defaultValue] if the key is
      *   not found or the associated value has the wrong type.
      */
-    inline fun getSizeOrElse(key: String, defaultValue: () -> Size): Size {
+    public inline fun getSizeOrElse(key: String, defaultValue: () -> Size): Size {
         if (key !in this) defaultValue()
         return source.getSize(key) ?: defaultValue()
     }
@@ -231,7 +234,7 @@
      * @return The value associated with the [key].
      * @throws IllegalStateException If the key is not found.
      */
-    inline fun getSizeF(key: String): SizeF {
+    public inline fun getSizeF(key: String): SizeF {
         if (key !in this) keyNotFoundError(key)
         return source.getSizeF(key) ?: valueNotFoundError(key)
     }
@@ -246,37 +249,40 @@
      * @return The value associated with the [key], or the result of [defaultValue] if the key is
      *   not found or the associated value has the wrong type.
      */
-    inline fun getSizeFOrElse(key: String, defaultValue: () -> SizeF): SizeF {
+    public inline fun getSizeFOrElse(key: String, defaultValue: () -> SizeF): SizeF {
         if (key !in this) defaultValue()
         return source.getSizeF(key) ?: defaultValue()
     }
 
-    actual inline fun getString(key: String): String {
+    public actual inline fun getString(key: String): String {
         if (key !in this) keyNotFoundError(key)
         return source.getString(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getStringOrElse(key: String, defaultValue: () -> String): String {
+    public actual inline fun getStringOrElse(key: String, defaultValue: () -> String): String {
         if (key !in this) defaultValue()
         return source.getString(key, defaultValue())
     }
 
-    actual inline fun getIntList(key: String): List<Int> {
+    public actual inline fun getIntList(key: String): List<Int> {
         if (key !in this) keyNotFoundError(key)
         return source.getIntegerArrayList(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getIntListOrElse(key: String, defaultValue: () -> List<Int>): List<Int> {
+    public actual inline fun getIntListOrElse(
+        key: String,
+        defaultValue: () -> List<Int>
+    ): List<Int> {
         if (key !in this) defaultValue()
         return source.getIntegerArrayList(key) ?: defaultValue()
     }
 
-    actual inline fun getCharSequenceList(key: String): List<CharSequence> {
+    public actual inline fun getCharSequenceList(key: String): List<CharSequence> {
         if (key !in this) keyNotFoundError(key)
         return source.getCharSequenceArrayList(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getCharSequenceListOrElse(
+    public actual inline fun getCharSequenceListOrElse(
         key: String,
         defaultValue: () -> List<CharSequence>
     ): List<CharSequence> {
@@ -284,12 +290,12 @@
         return source.getCharSequenceArrayList(key) ?: defaultValue()
     }
 
-    actual inline fun getStringList(key: String): List<String> {
+    public actual inline fun getStringList(key: String): List<String> {
         if (key !in this) keyNotFoundError(key)
         return source.getStringArrayList(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getStringListOrElse(
+    public actual inline fun getStringListOrElse(
         key: String,
         defaultValue: () -> List<String>
     ): List<String> {
@@ -305,7 +311,7 @@
      * @throws IllegalArgumentException If the [key] is not found.
      * @throws IllegalStateException if associated value has wrong type.
      */
-    inline fun <reified T : Parcelable> getParcelableList(key: String): List<T> {
+    public inline fun <reified T : Parcelable> getParcelableList(key: String): List<T> {
         if (key !in this) keyNotFoundError(key)
         return getParcelableArrayList(source, key, T::class.java) ?: valueNotFoundError(key)
     }
@@ -320,7 +326,7 @@
      * @return The value associated with the [key], or the result of [defaultValue] if the key is
      *   not found or the associated value has the wrong type.
      */
-    inline fun <reified T : Parcelable> getParcelableListOrElse(
+    public inline fun <reified T : Parcelable> getParcelableListOrElse(
         key: String,
         defaultValue: () -> List<T>
     ): List<T> {
@@ -328,12 +334,12 @@
         return getParcelableArrayList(source, key, T::class.java) ?: defaultValue()
     }
 
-    actual inline fun getBooleanArray(key: String): BooleanArray {
+    public actual inline fun getBooleanArray(key: String): BooleanArray {
         if (key !in this) keyNotFoundError(key)
         return source.getBooleanArray(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getBooleanArrayOrElse(
+    public actual inline fun getBooleanArrayOrElse(
         key: String,
         defaultValue: () -> BooleanArray
     ): BooleanArray {
@@ -341,24 +347,27 @@
         return source.getBooleanArray(key) ?: defaultValue()
     }
 
-    actual inline fun getCharArray(key: String): CharArray {
+    public actual inline fun getCharArray(key: String): CharArray {
         if (key !in this) keyNotFoundError(key)
         return source.getCharArray(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getCharArrayOrElse(key: String, defaultValue: () -> CharArray): CharArray {
+    public actual inline fun getCharArrayOrElse(
+        key: String,
+        defaultValue: () -> CharArray
+    ): CharArray {
         if (key !in this) defaultValue()
         return source.getCharArray(key) ?: defaultValue()
     }
 
     @Suppress("ArrayReturn")
-    actual inline fun getCharSequenceArray(key: String): Array<CharSequence> {
+    public actual inline fun getCharSequenceArray(key: String): Array<CharSequence> {
         if (key !in this) keyNotFoundError(key)
         return source.getCharSequenceArray(key) ?: valueNotFoundError(key)
     }
 
     @Suppress("ArrayReturn")
-    actual inline fun getCharSequenceArrayOrElse(
+    public actual inline fun getCharSequenceArrayOrElse(
         key: String,
         defaultValue: () -> Array<CharSequence>
     ): Array<CharSequence> {
@@ -366,12 +375,12 @@
         return source.getCharSequenceArray(key) ?: defaultValue()
     }
 
-    actual inline fun getDoubleArray(key: String): DoubleArray {
+    public actual inline fun getDoubleArray(key: String): DoubleArray {
         if (key !in this) keyNotFoundError(key)
         return source.getDoubleArray(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getDoubleArrayOrElse(
+    public actual inline fun getDoubleArrayOrElse(
         key: String,
         defaultValue: () -> DoubleArray
     ): DoubleArray {
@@ -379,42 +388,51 @@
         return source.getDoubleArray(key) ?: defaultValue()
     }
 
-    actual inline fun getFloatArray(key: String): FloatArray {
+    public actual inline fun getFloatArray(key: String): FloatArray {
         if (key !in this) keyNotFoundError(key)
         return source.getFloatArray(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getFloatArrayOrElse(key: String, defaultValue: () -> FloatArray): FloatArray {
+    public actual inline fun getFloatArrayOrElse(
+        key: String,
+        defaultValue: () -> FloatArray
+    ): FloatArray {
         if (key !in this) defaultValue()
         return source.getFloatArray(key) ?: defaultValue()
     }
 
-    actual inline fun getIntArray(key: String): IntArray {
+    public actual inline fun getIntArray(key: String): IntArray {
         if (key !in this) keyNotFoundError(key)
         return source.getIntArray(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getIntArrayOrElse(key: String, defaultValue: () -> IntArray): IntArray {
+    public actual inline fun getIntArrayOrElse(
+        key: String,
+        defaultValue: () -> IntArray
+    ): IntArray {
         if (key !in this) defaultValue()
         return source.getIntArray(key) ?: defaultValue()
     }
 
-    actual inline fun getLongArray(key: String): LongArray {
+    public actual inline fun getLongArray(key: String): LongArray {
         if (key !in this) keyNotFoundError(key)
         return source.getLongArray(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getLongArrayOrElse(key: String, defaultValue: () -> LongArray): LongArray {
+    public actual inline fun getLongArrayOrElse(
+        key: String,
+        defaultValue: () -> LongArray
+    ): LongArray {
         if (key !in this) defaultValue()
         return source.getLongArray(key) ?: defaultValue()
     }
 
-    actual inline fun getStringArray(key: String): Array<String> {
+    public actual inline fun getStringArray(key: String): Array<String> {
         if (key !in this) keyNotFoundError(key)
         return source.getStringArray(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getStringArrayOrElse(
+    public actual inline fun getStringArrayOrElse(
         key: String,
         defaultValue: () -> Array<String>
     ): Array<String> {
@@ -431,7 +449,7 @@
      * @throws IllegalStateException if associated value has wrong type.
      */
     @Suppress("ArrayReturn")
-    inline fun <reified T : Parcelable> getParcelableArray(key: String): Array<T> {
+    public inline fun <reified T : Parcelable> getParcelableArray(key: String): Array<T> {
         if (key !in this) keyNotFoundError(key)
         @Suppress("UNCHECKED_CAST")
         return getParcelableArray(source, key, T::class.java) as? Array<T>
@@ -449,7 +467,7 @@
      *   not found or the associated value has the wrong type.
      */
     @Suppress("ArrayReturn")
-    inline fun <reified T : Parcelable> getParcelableArrayOrElse(
+    public inline fun <reified T : Parcelable> getParcelableArrayOrElse(
         key: String,
         defaultValue: () -> Array<T>
     ): Array<T> {
@@ -466,7 +484,9 @@
      * @throws IllegalArgumentException If the [key] is not found.
      * @throws IllegalStateException if associated value has wrong type.
      */
-    inline fun <reified T : Parcelable> getSparseParcelableArray(key: String): SparseArray<T> {
+    public inline fun <reified T : Parcelable> getSparseParcelableArray(
+        key: String
+    ): SparseArray<T> {
         if (key !in this) keyNotFoundError(key)
         return getSparseParcelableArray(source, key, T::class.java) as? SparseArray<T>
             ?: valueNotFoundError(key)
@@ -482,7 +502,7 @@
      * @return The value associated with the [key], or the result of [defaultValue] if the key is
      *   not found or the associated value has the wrong type.
      */
-    inline fun <reified T : Parcelable> getSparseParcelableArrayOrElse(
+    public inline fun <reified T : Parcelable> getSparseParcelableArrayOrElse(
         key: String,
         defaultValue: () -> SparseArray<T>
     ): SparseArray<T> {
@@ -491,34 +511,38 @@
             ?: defaultValue()
     }
 
-    actual inline fun getSavedState(key: String): SavedState {
+    public actual inline fun getSavedState(key: String): SavedState {
         if (key !in this) keyNotFoundError(key)
         return source.getBundle(key) ?: valueNotFoundError(key)
     }
 
-    actual inline fun getSavedStateOrElse(key: String, defaultValue: () -> SavedState): SavedState {
+    public actual inline fun getSavedStateOrElse(
+        key: String,
+        defaultValue: () -> SavedState
+    ): SavedState {
         if (key !in this) defaultValue()
         return source.getBundle(key) ?: defaultValue()
     }
 
-    actual inline fun size(): Int = source.size()
+    public actual inline fun size(): Int = source.size()
 
-    actual inline fun isEmpty(): Boolean = source.isEmpty
+    public actual inline fun isEmpty(): Boolean = source.isEmpty
 
-    actual inline fun isNull(key: String): Boolean {
+    public actual inline fun isNull(key: String): Boolean {
         // Using `getString` to check for `null` is unreliable as it returns null for type
         // mismatches. To reliably determine if the value is actually `null`, we use the
         // deprecated `Bundle.get`.
         @Suppress("DEPRECATION") return contains(key) && source[key] == null
     }
 
-    actual inline operator fun contains(key: String): Boolean = source.containsKey(key)
+    public actual inline operator fun contains(key: String): Boolean = source.containsKey(key)
 
-    actual fun contentDeepEquals(other: SavedState): Boolean = source.contentDeepEquals(other)
+    public actual fun contentDeepEquals(other: SavedState): Boolean =
+        source.contentDeepEquals(other)
 
-    actual fun contentDeepHashCode(): Int = source.contentDeepHashCode()
+    public actual fun contentDeepHashCode(): Int = source.contentDeepHashCode()
 
-    actual fun toMap(): Map<String, Any?> {
+    public actual fun toMap(): Map<String, Any?> {
         return buildMap(capacity = source.size()) {
             for (key in source.keySet()) {
                 @Suppress("DEPRECATION") put(key, source[key])
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistry.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistry.android.kt
index 5d0676e..09a65b0 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistry.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistry.android.kt
@@ -19,34 +19,34 @@
 import androidx.lifecycle.Lifecycle
 import androidx.savedstate.internal.SavedStateRegistryImpl
 
-actual class SavedStateRegistry
+public actual class SavedStateRegistry
 internal actual constructor(
     private val impl: SavedStateRegistryImpl,
 ) {
 
     @get:MainThread
-    actual val isRestored: Boolean
+    public actual val isRestored: Boolean
         get() = impl.isRestored
 
     @MainThread
-    actual fun consumeRestoredStateForKey(key: String): SavedState? =
+    public actual fun consumeRestoredStateForKey(key: String): SavedState? =
         impl.consumeRestoredStateForKey(key)
 
     @MainThread
-    actual fun registerSavedStateProvider(key: String, provider: SavedStateProvider) {
+    public actual fun registerSavedStateProvider(key: String, provider: SavedStateProvider) {
         impl.registerSavedStateProvider(key, provider)
     }
 
-    actual fun getSavedStateProvider(key: String): SavedStateProvider? =
+    public actual fun getSavedStateProvider(key: String): SavedStateProvider? =
         impl.getSavedStateProvider(key)
 
     @MainThread
-    actual fun unregisterSavedStateProvider(key: String) {
+    public actual fun unregisterSavedStateProvider(key: String) {
         impl.unregisterSavedStateProvider(key)
     }
 
-    actual fun interface SavedStateProvider {
-        actual fun saveState(): SavedState
+    public actual fun interface SavedStateProvider {
+        public actual fun saveState(): SavedState
     }
 
     /**
@@ -55,14 +55,14 @@
      *
      * Subclasses must have a default constructor
      */
-    interface AutoRecreated {
+    public interface AutoRecreated {
         /**
          * This method will be called during dispatching of
          * [androidx.lifecycle.Lifecycle.Event.ON_CREATE] of owning component which was restarted
          *
          * @param owner a component that was restarted
          */
-        fun onRecreated(owner: SavedStateRegistryOwner)
+        public fun onRecreated(owner: SavedStateRegistryOwner)
     }
 
     private var recreatorProvider: Recreator.SavedStateProvider? = null
@@ -79,7 +79,7 @@
      *   dispatched
      */
     @MainThread
-    fun runOnNextRecreation(clazz: Class<out AutoRecreated>) {
+    public fun runOnNextRecreation(clazz: Class<out AutoRecreated>) {
         check(impl.isAllowingSavingState) {
             "Can not perform this action after onSaveInstanceState"
         }
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistryController.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistryController.android.kt
index 15b6fc9..6059739 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistryController.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateRegistryController.android.kt
@@ -23,27 +23,27 @@
     private val impl: SavedStateRegistryImpl,
 ) {
 
-    actual val savedStateRegistry: SavedStateRegistry = SavedStateRegistry(impl)
+    public actual val savedStateRegistry: SavedStateRegistry = SavedStateRegistry(impl)
 
     @MainThread
-    actual fun performAttach() {
+    public actual fun performAttach() {
         impl.performAttach()
     }
 
     @MainThread
-    actual fun performRestore(savedState: SavedState?) {
+    public actual fun performRestore(savedState: SavedState?) {
         impl.performRestore(savedState)
     }
 
     @MainThread
-    actual fun performSave(outBundle: SavedState) {
+    public actual fun performSave(outBundle: SavedState) {
         impl.performSave(outBundle)
     }
 
-    actual companion object {
+    public actual companion object {
 
         @JvmStatic
-        actual fun create(owner: SavedStateRegistryOwner): SavedStateRegistryController {
+        public actual fun create(owner: SavedStateRegistryOwner): SavedStateRegistryController {
             val impl =
                 SavedStateRegistryImpl(
                     owner = owner,
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
index 762fa1a..42c8295 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
@@ -29,7 +29,7 @@
 
 @Suppress("ValueClassDefinition")
 @JvmInline
-actual value class SavedStateWriter
+public actual value class SavedStateWriter
 @PublishedApi
 internal actual constructor(
     @PublishedApi internal actual val source: SavedState,
@@ -41,39 +41,39 @@
      * @param key The key to associate the value with.
      * @param value The [IBinder] value to store.
      */
-    inline fun putBinder(key: String, value: IBinder) {
+    public inline fun putBinder(key: String, value: IBinder) {
         source.putBinder(key, value)
     }
 
-    actual inline fun putBoolean(key: String, value: Boolean) {
+    public actual inline fun putBoolean(key: String, value: Boolean) {
         source.putBoolean(key, value)
     }
 
-    actual inline fun putChar(key: String, value: Char) {
+    public actual inline fun putChar(key: String, value: Char) {
         source.putChar(key, value)
     }
 
-    actual inline fun putCharSequence(key: String, value: CharSequence) {
+    public actual inline fun putCharSequence(key: String, value: CharSequence) {
         source.putCharSequence(key, value)
     }
 
-    actual inline fun putDouble(key: String, value: Double) {
+    public actual inline fun putDouble(key: String, value: Double) {
         source.putDouble(key, value)
     }
 
-    actual inline fun putFloat(key: String, value: Float) {
+    public actual inline fun putFloat(key: String, value: Float) {
         source.putFloat(key, value)
     }
 
-    actual inline fun putInt(key: String, value: Int) {
+    public actual inline fun putInt(key: String, value: Int) {
         source.putInt(key, value)
     }
 
-    actual inline fun putLong(key: String, value: Long) {
+    public actual inline fun putLong(key: String, value: Long) {
         source.putLong(key, value)
     }
 
-    actual inline fun putNull(key: String) {
+    public actual inline fun putNull(key: String) {
         source.putString(key, null)
     }
 
@@ -83,7 +83,7 @@
      * @param key The key to associate the value with.
      * @param value The [Parcelable] value to store.
      */
-    inline fun <reified T : Parcelable> putParcelable(key: String, value: T) {
+    public inline fun <reified T : Parcelable> putParcelable(key: String, value: T) {
         source.putParcelable(key, value)
     }
 
@@ -93,7 +93,7 @@
      * @param key The key to associate the value with.
      * @param value The [Serializable] value to store.
      */
-    inline fun <reified T : Serializable> putJavaSerializable(key: String, value: T) {
+    public inline fun <reified T : Serializable> putJavaSerializable(key: String, value: T) {
         source.putSerializable(key, value)
     }
 
@@ -103,7 +103,7 @@
      * @param key The key to associate the value with.
      * @param value The [Size] value to store.
      */
-    inline fun putSize(key: String, value: Size) {
+    public inline fun putSize(key: String, value: Size) {
         source.putSize(key, value)
     }
 
@@ -113,23 +113,23 @@
      * @param key The key to associate the value with.
      * @param value The [SizeF] value to store.
      */
-    inline fun putSizeF(key: String, value: SizeF) {
+    public inline fun putSizeF(key: String, value: SizeF) {
         source.putSizeF(key, value)
     }
 
-    actual inline fun putString(key: String, value: String) {
+    public actual inline fun putString(key: String, value: String) {
         source.putString(key, value)
     }
 
-    actual inline fun putIntList(key: String, value: List<Int>) {
+    public actual inline fun putIntList(key: String, value: List<Int>) {
         source.putIntegerArrayList(key, value.toArrayListUnsafe())
     }
 
-    actual inline fun putCharSequenceList(key: String, value: List<CharSequence>) {
+    public actual inline fun putCharSequenceList(key: String, value: List<CharSequence>) {
         source.putCharSequenceArrayList(key, value.toArrayListUnsafe())
     }
 
-    actual inline fun putStringList(key: String, value: List<String>) {
+    public actual inline fun putStringList(key: String, value: List<String>) {
         source.putStringArrayList(key, value.toArrayListUnsafe())
     }
 
@@ -140,42 +140,42 @@
      * @param key The key to associate the value with.
      * @param value The [List] of elements to store.
      */
-    inline fun <reified T : Parcelable> putParcelableList(key: String, value: List<T>) {
+    public inline fun <reified T : Parcelable> putParcelableList(key: String, value: List<T>) {
         source.putParcelableArrayList(key, value.toArrayListUnsafe())
     }
 
-    actual inline fun putBooleanArray(key: String, value: BooleanArray) {
+    public actual inline fun putBooleanArray(key: String, value: BooleanArray) {
         source.putBooleanArray(key, value)
     }
 
-    actual inline fun putCharArray(key: String, value: CharArray) {
+    public actual inline fun putCharArray(key: String, value: CharArray) {
         source.putCharArray(key, value)
     }
 
-    actual inline fun putCharSequenceArray(
+    public actual inline fun putCharSequenceArray(
         key: String,
         @Suppress("ArrayReturn") value: Array<CharSequence>
     ) {
         source.putCharSequenceArray(key, value)
     }
 
-    actual inline fun putDoubleArray(key: String, value: DoubleArray) {
+    public actual inline fun putDoubleArray(key: String, value: DoubleArray) {
         source.putDoubleArray(key, value)
     }
 
-    actual inline fun putFloatArray(key: String, value: FloatArray) {
+    public actual inline fun putFloatArray(key: String, value: FloatArray) {
         source.putFloatArray(key, value)
     }
 
-    actual inline fun putIntArray(key: String, value: IntArray) {
+    public actual inline fun putIntArray(key: String, value: IntArray) {
         source.putIntArray(key, value)
     }
 
-    actual inline fun putLongArray(key: String, value: LongArray) {
+    public actual inline fun putLongArray(key: String, value: LongArray) {
         source.putLongArray(key, value)
     }
 
-    actual inline fun putStringArray(key: String, value: Array<String>) {
+    public actual inline fun putStringArray(key: String, value: Array<String>) {
         source.putStringArray(key, value)
     }
 
@@ -186,7 +186,7 @@
      * @param key The key to associate the value with.
      * @param value The [Array] of elements to store.
      */
-    inline fun <reified T : Parcelable> putParcelableArray(
+    public inline fun <reified T : Parcelable> putParcelableArray(
         key: String,
         @Suppress("ArrayReturn") value: Array<T>
     ) {
@@ -200,26 +200,26 @@
      * @param key The key to associate the value with.
      * @param value The [SparseArray] of elements to store.
      */
-    inline fun <reified T : Parcelable> putSparseParcelableArray(
+    public inline fun <reified T : Parcelable> putSparseParcelableArray(
         key: String,
         value: SparseArray<T>
     ) {
         source.putSparseParcelableArray(key, value)
     }
 
-    actual inline fun putSavedState(key: String, value: SavedState) {
+    public actual inline fun putSavedState(key: String, value: SavedState) {
         source.putBundle(key, value)
     }
 
-    actual inline fun putAll(from: SavedState) {
+    public actual inline fun putAll(from: SavedState) {
         source.putAll(from)
     }
 
-    actual inline fun remove(key: String) {
+    public actual inline fun remove(key: String) {
         source.remove(key)
     }
 
-    actual inline fun clear() {
+    public actual inline fun clear() {
         source.clear()
     }
 }
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/ViewTreeSavedStateRegistryOwner.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/ViewTreeSavedStateRegistryOwner.android.kt
index 670caee..e6b7502 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/ViewTreeSavedStateRegistryOwner.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/ViewTreeSavedStateRegistryOwner.android.kt
@@ -35,7 +35,7 @@
  *   view
  */
 @JvmName("set")
-fun View.setViewTreeSavedStateRegistryOwner(owner: SavedStateRegistryOwner?) {
+public fun View.setViewTreeSavedStateRegistryOwner(owner: SavedStateRegistryOwner?) {
     setTag(R.id.view_tree_saved_state_registry_owner, owner)
 }
 
@@ -50,7 +50,7 @@
  *   and/or some subset of its ancestors
  */
 @JvmName("get")
-fun View.findViewTreeSavedStateRegistryOwner(): SavedStateRegistryOwner? {
+public fun View.findViewTreeSavedStateRegistryOwner(): SavedStateRegistryOwner? {
     var currentView: View? = this
     while (currentView != null) {
         val registryOwner =
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt
index 4be08b3..f0dacc5 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt
@@ -47,7 +47,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class SizeSerializer : KSerializer<Size> {
+public class SizeSerializer : KSerializer<Size> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Size")
 
     override fun serialize(encoder: Encoder, value: Size) {
@@ -77,7 +77,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class SizeFSerializer : KSerializer<SizeF> {
+public class SizeFSerializer : KSerializer<SizeF> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SizeF")
 
     override fun serialize(encoder: Encoder, value: SizeF) {
@@ -107,7 +107,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-open class CharSequenceSerializer<T : CharSequence> : KSerializer<T> {
+public open class CharSequenceSerializer<T : CharSequence> : KSerializer<T> {
     final override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CharSequence")
 
     final override fun serialize(encoder: Encoder, value: T) {
@@ -138,7 +138,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-open class JavaSerializableSerializer<T : JavaSerializable> : KSerializer<T> {
+public open class JavaSerializableSerializer<T : JavaSerializable> : KSerializer<T> {
     final override val descriptor: SerialDescriptor = buildClassSerialDescriptor("JavaSerializable")
 
     final override fun serialize(encoder: Encoder, value: T) {
@@ -169,7 +169,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-open class ParcelableSerializer<T : Parcelable> : KSerializer<T> {
+public open class ParcelableSerializer<T : Parcelable> : KSerializer<T> {
     final override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Parcelable")
 
     final override fun serialize(encoder: Encoder, value: T) {
@@ -200,7 +200,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class IBinderSerializer : KSerializer<IBinder> {
+public class IBinderSerializer : KSerializer<IBinder> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("IBinder")
 
     override fun serialize(encoder: Encoder, value: IBinder) {
@@ -230,7 +230,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class CharSequenceArraySerializer : KSerializer<Array<CharSequence>> {
+public class CharSequenceArraySerializer : KSerializer<Array<CharSequence>> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CharSequenceArray")
 
     override fun serialize(encoder: Encoder, @Suppress("ArrayReturn") value: Array<CharSequence>) {
@@ -261,7 +261,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class ParcelableArraySerializer : KSerializer<Array<Parcelable>> {
+public class ParcelableArraySerializer : KSerializer<Array<Parcelable>> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ParcelableArray")
 
     override fun serialize(encoder: Encoder, @Suppress("ArrayReturn") value: Array<Parcelable>) {
@@ -292,7 +292,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class CharSequenceListSerializer : KSerializer<List<CharSequence>> {
+public class CharSequenceListSerializer : KSerializer<List<CharSequence>> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CharSequenceList")
 
     override fun serialize(encoder: Encoder, value: List<CharSequence>) {
@@ -322,7 +322,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class ParcelableListSerializer : KSerializer<List<Parcelable>> {
+public class ParcelableListSerializer : KSerializer<List<Parcelable>> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ParcelableList")
 
     override fun serialize(encoder: Encoder, value: List<Parcelable>) {
@@ -352,7 +352,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class SparseParcelableArraySerializer : KSerializer<SparseArray<Parcelable>> {
+public class SparseParcelableArraySerializer : KSerializer<SparseArray<Parcelable>> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SparseParcelableArray")
 
     override fun serialize(encoder: Encoder, value: SparseArray<Parcelable>) {
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateRegistry.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateRegistry.kt
index 80716ea..fdd98dd6 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateRegistry.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateRegistry.kt
@@ -32,14 +32,14 @@
 ) {
 
     /** This interface marks a component that contributes to saved state. */
-    fun interface SavedStateProvider {
+    public fun interface SavedStateProvider {
         /**
          * Called to retrieve a state from a component before being killed so later the state can be
          * received from [consumeRestoredStateForKey]
          *
          * Returns `S` with your saved state.
          */
-        fun saveState(): SavedState
+        public fun saveState(): SavedState
     }
 
     /**
@@ -48,7 +48,7 @@
      *
      * [isRestored] == true if state was restored
      */
-    val isRestored: Boolean
+    public val isRestored: Boolean
 
     /**
      * Consumes saved state previously supplied by [SavedStateProvider] registered via
@@ -67,7 +67,7 @@
      * @param key a key with which [SavedStateProvider] was previously registered.
      * @return `S` with the previously saved state or {@code null}
      */
-    @MainThread fun consumeRestoredStateForKey(key: String): SavedState?
+    @MainThread public fun consumeRestoredStateForKey(key: String): SavedState?
 
     /**
      * Registers a [SavedStateProvider] by the given `key`. This `savedStateProvider` will be called
@@ -83,7 +83,7 @@
      * @param key a key with which returned saved state will be associated
      * @param provider savedStateProvider to get saved state.
      */
-    @MainThread fun registerSavedStateProvider(key: String, provider: SavedStateProvider)
+    @MainThread public fun registerSavedStateProvider(key: String, provider: SavedStateProvider)
 
     /**
      * Get a previously registered [SavedStateProvider].
@@ -94,12 +94,12 @@
      * Returns the [SavedStateProvider] previously registered with [registerSavedStateProvider] or
      * null if no provider has been registered with the given key.
      */
-    fun getSavedStateProvider(key: String): SavedStateProvider?
+    public fun getSavedStateProvider(key: String): SavedStateProvider?
 
     /**
      * Unregisters a component previously registered by the given `key`
      *
      * @param key a key with which a component was previously registered.
      */
-    @MainThread fun unregisterSavedStateProvider(key: String)
+    @MainThread public fun unregisterSavedStateProvider(key: String)
 }
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateDecoder.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateDecoder.kt
index 755e034..65ccd1f 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateDecoder.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateDecoder.kt
@@ -39,7 +39,7 @@
  * @throws SerializationException for any deserialization error.
  * @throws IllegalArgumentException if [savedState] is not valid.
  */
-fun <T : Any> decodeFromSavedState(
+public fun <T : Any> decodeFromSavedState(
     deserializer: DeserializationStrategy<T>,
     savedState: SavedState
 ): T {
@@ -55,7 +55,7 @@
  * @throws SerializationException for any deserialization error.
  * @throws IllegalArgumentException if [savedState] is not valid.
  */
-inline fun <reified T : Any> decodeFromSavedState(savedState: SavedState): T =
+public inline fun <reified T : Any> decodeFromSavedState(savedState: SavedState): T =
     decodeFromSavedState(serializer<T>(), savedState)
 
 /**
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateEncoder.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateEncoder.kt
index 9625885..7d89c34 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateEncoder.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/SavedStateEncoder.kt
@@ -39,7 +39,10 @@
  * @return The encoded [SavedState].
  * @throws SerializationException if [value] cannot be serialized.
  */
-fun <T : Any> encodeToSavedState(serializer: SerializationStrategy<T>, value: T): SavedState =
+public fun <T : Any> encodeToSavedState(
+    serializer: SerializationStrategy<T>,
+    value: T
+): SavedState =
     savedState().apply { SavedStateEncoder(this).encodeSerializableValue(serializer, value) }
 
 /**
@@ -50,7 +53,7 @@
  * @return The encoded [SavedState].
  * @throws SerializationException if [value] cannot be serialized.
  */
-inline fun <reified T : Any> encodeToSavedState(value: T): SavedState {
+public inline fun <reified T : Any> encodeToSavedState(value: T): SavedState {
     return encodeToSavedState(serializer<T>(), value)
 }
 
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.kt
index 51411f3..14d6036 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.kt
@@ -42,7 +42,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-class SavedStateSerializer : KSerializer<SavedState> {
+public class SavedStateSerializer : KSerializer<SavedState> {
     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SavedState")
 
     override fun serialize(encoder: Encoder, value: SavedState) {
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt
index 03b06aa..1b98e67 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt
@@ -27,7 +27,7 @@
 @PublishedApi
 internal constructor(@PublishedApi internal val map: MutableMap<String, Any?> = mutableMapOf())
 
-actual inline fun savedState(
+public actual inline fun savedState(
     initialState: Map<String, Any?>,
     builderAction: SavedStateWriter.() -> Unit,
 ): SavedState = SavedState(initialState.toMutableMap()).apply { write(builderAction) }
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
index b18561a..8f0d715e 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
@@ -31,32 +31,32 @@
     @PublishedApi internal actual val source: SavedState,
 ) {
 
-    actual inline fun getBoolean(key: String): Boolean {
+    public actual inline fun getBoolean(key: String): Boolean {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Boolean ?: DEFAULT_BOOLEAN
     }
 
-    actual inline fun getBooleanOrElse(key: String, defaultValue: () -> Boolean): Boolean {
+    public actual inline fun getBooleanOrElse(key: String, defaultValue: () -> Boolean): Boolean {
         if (key !in this) defaultValue()
         return source.map[key] as? Boolean ?: defaultValue()
     }
 
-    actual inline fun getChar(key: String): Char {
+    public actual inline fun getChar(key: String): Char {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Char ?: DEFAULT_CHAR
     }
 
-    actual inline fun getCharOrElse(key: String, defaultValue: () -> Char): Char {
+    public actual inline fun getCharOrElse(key: String, defaultValue: () -> Char): Char {
         if (key !in this) defaultValue()
         return source.map[key] as? Char ?: defaultValue()
     }
 
-    actual inline fun getCharSequence(key: String): CharSequence {
+    public actual inline fun getCharSequence(key: String): CharSequence {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? CharSequence ?: valueNotFoundError(key)
     }
 
-    actual inline fun getCharSequenceOrElse(
+    public actual inline fun getCharSequenceOrElse(
         key: String,
         defaultValue: () -> CharSequence
     ): CharSequence {
@@ -64,63 +64,63 @@
         return source.map[key] as? CharSequence ?: defaultValue()
     }
 
-    actual inline fun getDouble(key: String): Double {
+    public actual inline fun getDouble(key: String): Double {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Double ?: DEFAULT_DOUBLE
     }
 
-    actual inline fun getDoubleOrElse(key: String, defaultValue: () -> Double): Double {
+    public actual inline fun getDoubleOrElse(key: String, defaultValue: () -> Double): Double {
         if (key !in this) defaultValue()
         return source.map[key] as? Double ?: defaultValue()
     }
 
-    actual inline fun getFloat(key: String): Float {
+    public actual inline fun getFloat(key: String): Float {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Float ?: DEFAULT_FLOAT
     }
 
-    actual inline fun getFloatOrElse(key: String, defaultValue: () -> Float): Float {
+    public actual inline fun getFloatOrElse(key: String, defaultValue: () -> Float): Float {
         if (key !in this) defaultValue()
         return source.map[key] as? Float ?: defaultValue()
     }
 
-    actual inline fun getInt(key: String): Int {
+    public actual inline fun getInt(key: String): Int {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Int ?: DEFAULT_INT
     }
 
-    actual inline fun getIntOrElse(key: String, defaultValue: () -> Int): Int {
+    public actual inline fun getIntOrElse(key: String, defaultValue: () -> Int): Int {
         if (key !in this) defaultValue()
         return source.map[key] as? Int ?: defaultValue()
     }
 
-    actual inline fun getLong(key: String): Long {
+    public actual inline fun getLong(key: String): Long {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Long ?: DEFAULT_LONG
     }
 
-    actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long {
+    public actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long {
         if (key !in this) defaultValue()
         return source.map[key] as? Long ?: defaultValue()
     }
 
-    actual inline fun getString(key: String): String {
+    public actual inline fun getString(key: String): String {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? String ?: valueNotFoundError(key)
     }
 
-    actual inline fun getStringOrElse(key: String, defaultValue: () -> String): String {
+    public actual inline fun getStringOrElse(key: String, defaultValue: () -> String): String {
         if (key !in this) defaultValue()
         return source.map[key] as? String ?: defaultValue()
     }
 
-    actual inline fun getCharSequenceList(key: String): List<CharSequence> {
+    public actual inline fun getCharSequenceList(key: String): List<CharSequence> {
         if (key !in this) keyNotFoundError(key)
         @Suppress("UNCHECKED_CAST")
         return source.map[key] as? List<CharSequence> ?: valueNotFoundError(key)
     }
 
-    actual inline fun getCharSequenceListOrElse(
+    public actual inline fun getCharSequenceListOrElse(
         key: String,
         defaultValue: () -> List<CharSequence>
     ): List<CharSequence> {
@@ -128,23 +128,26 @@
         @Suppress("UNCHECKED_CAST") return source.map[key] as? List<CharSequence> ?: defaultValue()
     }
 
-    actual inline fun getIntList(key: String): List<Int> {
+    public actual inline fun getIntList(key: String): List<Int> {
         if (key !in this) keyNotFoundError(key)
         @Suppress("UNCHECKED_CAST") return source.map[key] as? List<Int> ?: valueNotFoundError(key)
     }
 
-    actual inline fun getIntListOrElse(key: String, defaultValue: () -> List<Int>): List<Int> {
+    public actual inline fun getIntListOrElse(
+        key: String,
+        defaultValue: () -> List<Int>
+    ): List<Int> {
         if (key !in this) defaultValue()
         @Suppress("UNCHECKED_CAST") return source.map[key] as? List<Int> ?: defaultValue()
     }
 
-    actual inline fun getStringList(key: String): List<String> {
+    public actual inline fun getStringList(key: String): List<String> {
         if (key !in this) keyNotFoundError(key)
         @Suppress("UNCHECKED_CAST")
         return source.map[key] as? List<String> ?: valueNotFoundError(key)
     }
 
-    actual inline fun getStringListOrElse(
+    public actual inline fun getStringListOrElse(
         key: String,
         defaultValue: () -> List<String>
     ): List<String> {
@@ -152,23 +155,26 @@
         @Suppress("UNCHECKED_CAST") return source.map[key] as? List<String> ?: defaultValue()
     }
 
-    actual inline fun getCharArray(key: String): CharArray {
+    public actual inline fun getCharArray(key: String): CharArray {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? CharArray ?: valueNotFoundError(key)
     }
 
-    actual inline fun getCharArrayOrElse(key: String, defaultValue: () -> CharArray): CharArray {
+    public actual inline fun getCharArrayOrElse(
+        key: String,
+        defaultValue: () -> CharArray
+    ): CharArray {
         if (key !in this) defaultValue()
         return source.map[key] as? CharArray ?: defaultValue()
     }
 
-    actual inline fun getCharSequenceArray(key: String): Array<CharSequence> {
+    public actual inline fun getCharSequenceArray(key: String): Array<CharSequence> {
         if (key !in this) keyNotFoundError(key)
         @Suppress("UNCHECKED_CAST")
         return source.map[key] as? Array<CharSequence> ?: valueNotFoundError(key)
     }
 
-    actual inline fun getCharSequenceArrayOrElse(
+    public actual inline fun getCharSequenceArrayOrElse(
         key: String,
         defaultValue: () -> Array<CharSequence>
     ): Array<CharSequence> {
@@ -176,12 +182,12 @@
         @Suppress("UNCHECKED_CAST") return source.map[key] as? Array<CharSequence> ?: defaultValue()
     }
 
-    actual inline fun getBooleanArray(key: String): BooleanArray {
+    public actual inline fun getBooleanArray(key: String): BooleanArray {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? BooleanArray ?: valueNotFoundError(key)
     }
 
-    actual inline fun getBooleanArrayOrElse(
+    public actual inline fun getBooleanArrayOrElse(
         key: String,
         defaultValue: () -> BooleanArray
     ): BooleanArray {
@@ -189,12 +195,12 @@
         return source.map[key] as? BooleanArray ?: defaultValue()
     }
 
-    actual inline fun getDoubleArray(key: String): DoubleArray {
+    public actual inline fun getDoubleArray(key: String): DoubleArray {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? DoubleArray ?: valueNotFoundError(key)
     }
 
-    actual inline fun getDoubleArrayOrElse(
+    public actual inline fun getDoubleArrayOrElse(
         key: String,
         defaultValue: () -> DoubleArray,
     ): DoubleArray {
@@ -202,44 +208,53 @@
         return source.map[key] as? DoubleArray ?: defaultValue()
     }
 
-    actual inline fun getFloatArray(key: String): FloatArray {
+    public actual inline fun getFloatArray(key: String): FloatArray {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? FloatArray ?: valueNotFoundError(key)
     }
 
-    actual inline fun getFloatArrayOrElse(key: String, defaultValue: () -> FloatArray): FloatArray {
+    public actual inline fun getFloatArrayOrElse(
+        key: String,
+        defaultValue: () -> FloatArray
+    ): FloatArray {
         if (key !in this) defaultValue()
         return source.map[key] as? FloatArray ?: defaultValue()
     }
 
-    actual inline fun getIntArray(key: String): IntArray {
+    public actual inline fun getIntArray(key: String): IntArray {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? IntArray ?: valueNotFoundError(key)
     }
 
-    actual inline fun getIntArrayOrElse(key: String, defaultValue: () -> IntArray): IntArray {
+    public actual inline fun getIntArrayOrElse(
+        key: String,
+        defaultValue: () -> IntArray
+    ): IntArray {
         if (key !in this) defaultValue()
         return source.map[key] as? IntArray ?: defaultValue()
     }
 
-    actual inline fun getLongArray(key: String): LongArray {
+    public actual inline fun getLongArray(key: String): LongArray {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? LongArray ?: valueNotFoundError(key)
     }
 
-    actual inline fun getLongArrayOrElse(key: String, defaultValue: () -> LongArray): LongArray {
+    public actual inline fun getLongArrayOrElse(
+        key: String,
+        defaultValue: () -> LongArray
+    ): LongArray {
         if (key !in this) defaultValue()
         return source.map[key] as? LongArray ?: defaultValue()
     }
 
     @Suppress("UNCHECKED_CAST")
-    actual inline fun getStringArray(key: String): Array<String> {
+    public actual inline fun getStringArray(key: String): Array<String> {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Array<String> ?: valueNotFoundError(key)
     }
 
     @Suppress("UNCHECKED_CAST")
-    actual inline fun getStringArrayOrElse(
+    public actual inline fun getStringArrayOrElse(
         key: String,
         defaultValue: () -> Array<String>
     ): Array<String> {
@@ -247,29 +262,33 @@
         return source.map[key] as? Array<String> ?: defaultValue()
     }
 
-    actual inline fun getSavedState(key: String): SavedState {
+    public actual inline fun getSavedState(key: String): SavedState {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? SavedState ?: valueNotFoundError(key)
     }
 
-    actual inline fun getSavedStateOrElse(key: String, defaultValue: () -> SavedState): SavedState {
+    public actual inline fun getSavedStateOrElse(
+        key: String,
+        defaultValue: () -> SavedState
+    ): SavedState {
         if (key !in this) defaultValue()
         return source.map[key] as? SavedState ?: defaultValue()
     }
 
-    actual inline fun size(): Int = source.map.size
+    public actual inline fun size(): Int = source.map.size
 
-    actual inline fun isEmpty(): Boolean = source.map.isEmpty()
+    public actual inline fun isEmpty(): Boolean = source.map.isEmpty()
 
-    actual inline fun isNull(key: String): Boolean = contains(key) && source.map[key] == null
+    public actual inline fun isNull(key: String): Boolean = contains(key) && source.map[key] == null
 
-    actual inline operator fun contains(key: String): Boolean = source.map.containsKey(key)
+    public actual inline operator fun contains(key: String): Boolean = source.map.containsKey(key)
 
-    actual fun contentDeepEquals(other: SavedState): Boolean = source.contentDeepEquals(other)
+    public actual fun contentDeepEquals(other: SavedState): Boolean =
+        source.contentDeepEquals(other)
 
-    actual fun contentDeepHashCode(): Int = source.contentDeepHashCode()
+    public actual fun contentDeepHashCode(): Int = source.contentDeepHashCode()
 
-    actual fun toMap(): Map<String, Any?> {
+    public actual fun toMap(): Map<String, Any?> {
         return buildMap(capacity = source.map.size) {
             for (key in source.map.keys) {
                 put(key, source.map[key])
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistry.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistry.nonAndroid.kt
index 19f8801..1f5bfb8 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistry.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistry.nonAndroid.kt
@@ -18,33 +18,33 @@
 import androidx.annotation.MainThread
 import androidx.savedstate.internal.SavedStateRegistryImpl
 
-actual class SavedStateRegistry
+public actual class SavedStateRegistry
 internal actual constructor(
     private val impl: SavedStateRegistryImpl,
 ) {
 
     @get:MainThread
-    actual val isRestored: Boolean
+    public actual val isRestored: Boolean
         get() = impl.isRestored
 
     @MainThread
-    actual fun consumeRestoredStateForKey(key: String): SavedState? =
+    public actual fun consumeRestoredStateForKey(key: String): SavedState? =
         impl.consumeRestoredStateForKey(key)
 
     @MainThread
-    actual fun registerSavedStateProvider(key: String, provider: SavedStateProvider) {
+    public actual fun registerSavedStateProvider(key: String, provider: SavedStateProvider) {
         impl.registerSavedStateProvider(key, provider)
     }
 
-    actual fun getSavedStateProvider(key: String): SavedStateProvider? =
+    public actual fun getSavedStateProvider(key: String): SavedStateProvider? =
         impl.getSavedStateProvider(key)
 
     @MainThread
-    actual fun unregisterSavedStateProvider(key: String) {
+    public actual fun unregisterSavedStateProvider(key: String) {
         impl.unregisterSavedStateProvider(key)
     }
 
-    actual fun interface SavedStateProvider {
-        actual fun saveState(): SavedState
+    public actual fun interface SavedStateProvider {
+        public actual fun saveState(): SavedState
     }
 }
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistryController.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistryController.nonAndroid.kt
index acfe7f4..a720c44 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistryController.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateRegistryController.nonAndroid.kt
@@ -24,27 +24,27 @@
     private val impl: SavedStateRegistryImpl,
 ) {
 
-    actual val savedStateRegistry: SavedStateRegistry = SavedStateRegistry(impl)
+    public actual val savedStateRegistry: SavedStateRegistry = SavedStateRegistry(impl)
 
     @MainThread
-    actual fun performAttach() {
+    public actual fun performAttach() {
         impl.performAttach()
     }
 
     @MainThread
-    actual fun performRestore(savedState: SavedState?) {
+    public actual fun performRestore(savedState: SavedState?) {
         impl.performRestore(savedState)
     }
 
     @MainThread
-    actual fun performSave(outBundle: SavedState) {
+    public actual fun performSave(outBundle: SavedState) {
         impl.performSave(outBundle)
     }
 
-    actual companion object {
+    public actual companion object {
 
         @JvmStatic
-        actual fun create(owner: SavedStateRegistryOwner): SavedStateRegistryController {
+        public actual fun create(owner: SavedStateRegistryOwner): SavedStateRegistryController {
             return SavedStateRegistryController(SavedStateRegistryImpl(owner))
         }
     }
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
index bfa30cd..a0980f2 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
@@ -25,105 +25,105 @@
 import kotlin.jvm.JvmName
 
 @JvmInline
-actual value class SavedStateWriter
+public actual value class SavedStateWriter
 @PublishedApi
 internal actual constructor(
     @PublishedApi internal actual val source: SavedState,
 ) {
 
-    actual inline fun putBoolean(key: String, value: Boolean) {
+    public actual inline fun putBoolean(key: String, value: Boolean) {
         source.map[key] = value
     }
 
-    actual inline fun putChar(key: String, value: Char) {
+    public actual inline fun putChar(key: String, value: Char) {
         source.map[key] = value
     }
 
-    actual inline fun putCharSequence(key: String, value: CharSequence) {
+    public actual inline fun putCharSequence(key: String, value: CharSequence) {
         source.map[key] = value
     }
 
-    actual inline fun putDouble(key: String, value: Double) {
+    public actual inline fun putDouble(key: String, value: Double) {
         source.map[key] = value
     }
 
-    actual inline fun putFloat(key: String, value: Float) {
+    public actual inline fun putFloat(key: String, value: Float) {
         source.map[key] = value
     }
 
-    actual inline fun putInt(key: String, value: Int) {
+    public actual inline fun putInt(key: String, value: Int) {
         source.map[key] = value
     }
 
-    actual inline fun putLong(key: String, value: Long) {
+    public actual inline fun putLong(key: String, value: Long) {
         source.map[key] = value
     }
 
-    actual inline fun putNull(key: String) {
+    public actual inline fun putNull(key: String) {
         source.map[key] = null
     }
 
-    actual inline fun putString(key: String, value: String) {
+    public actual inline fun putString(key: String, value: String) {
         source.map[key] = value
     }
 
-    actual inline fun putCharSequenceList(key: String, value: List<CharSequence>) {
+    public actual inline fun putCharSequenceList(key: String, value: List<CharSequence>) {
         source.map[key] = value
     }
 
-    actual inline fun putIntList(key: String, value: List<Int>) {
+    public actual inline fun putIntList(key: String, value: List<Int>) {
         source.map[key] = value
     }
 
-    actual inline fun putStringList(key: String, value: List<String>) {
+    public actual inline fun putStringList(key: String, value: List<String>) {
         source.map[key] = value
     }
 
-    actual inline fun putBooleanArray(key: String, value: BooleanArray) {
+    public actual inline fun putBooleanArray(key: String, value: BooleanArray) {
         source.map[key] = value
     }
 
-    actual inline fun putCharArray(key: String, value: CharArray) {
+    public actual inline fun putCharArray(key: String, value: CharArray) {
         source.map[key] = value
     }
 
-    actual inline fun putCharSequenceArray(key: String, value: Array<CharSequence>) {
+    public actual inline fun putCharSequenceArray(key: String, value: Array<CharSequence>) {
         source.map[key] = value
     }
 
-    actual inline fun putDoubleArray(key: String, value: DoubleArray) {
+    public actual inline fun putDoubleArray(key: String, value: DoubleArray) {
         source.map[key] = value
     }
 
-    actual inline fun putFloatArray(key: String, value: FloatArray) {
+    public actual inline fun putFloatArray(key: String, value: FloatArray) {
         source.map[key] = value
     }
 
-    actual inline fun putIntArray(key: String, value: IntArray) {
+    public actual inline fun putIntArray(key: String, value: IntArray) {
         source.map[key] = value
     }
 
-    actual inline fun putLongArray(key: String, value: LongArray) {
+    public actual inline fun putLongArray(key: String, value: LongArray) {
         source.map[key] = value
     }
 
-    actual inline fun putStringArray(key: String, value: Array<String>) {
+    public actual inline fun putStringArray(key: String, value: Array<String>) {
         source.map[key] = value
     }
 
-    actual inline fun putSavedState(key: String, value: SavedState) {
+    public actual inline fun putSavedState(key: String, value: SavedState) {
         source.map[key] = value
     }
 
-    actual inline fun putAll(from: SavedState) {
+    public actual inline fun putAll(from: SavedState) {
         source.map.putAll(from.map)
     }
 
-    actual inline fun remove(key: String) {
+    public actual inline fun remove(key: String) {
         source.map.remove(key)
     }
 
-    actual inline fun clear() {
+    public actual inline fun clear() {
         source.map.clear()
     }
 }
diff --git a/security/security-identity-credential/OWNERS b/security/security-identity-credential/OWNERS
deleted file mode 100644
index 92af54e..0000000
--- a/security/security-identity-credential/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 558417
[email protected]
[email protected]
diff --git a/security/security-identity-credential/api/api_lint.ignore b/security/security-identity-credential/api/api_lint.ignore
deleted file mode 100644
index eb7c43a..0000000
--- a/security/security-identity-credential/api/api_lint.ignore
+++ /dev/null
@@ -1,15 +0,0 @@
-// Baseline format: 1.0
-MissingGetterMatchingBuilder: androidx.security.identity.AccessControlProfile.Builder#setReaderCertificate(java.security.cert.X509Certificate):
-    androidx.security.identity.AccessControlProfile does not declare a `getReaderCertificate()` method matching method androidx.security.identity.AccessControlProfile.Builder.setReaderCertificate(java.security.cert.X509Certificate)
-MissingGetterMatchingBuilder: androidx.security.identity.AccessControlProfile.Builder#setUserAuthenticationRequired(boolean):
-    androidx.security.identity.AccessControlProfile does not declare a `isUserAuthenticationRequired()` method matching method androidx.security.identity.AccessControlProfile.Builder.setUserAuthenticationRequired(boolean)
-MissingGetterMatchingBuilder: androidx.security.identity.AccessControlProfile.Builder#setUserAuthenticationTimeout(long):
-    androidx.security.identity.AccessControlProfile does not declare a `getUserAuthenticationTimeout()` method matching method androidx.security.identity.AccessControlProfile.Builder.setUserAuthenticationTimeout(long)
-MissingGetterMatchingBuilder: androidx.security.identity.PersonalizationData.Builder#addAccessControlProfile(androidx.security.identity.AccessControlProfile):
-    androidx.security.identity.PersonalizationData does not declare a `getAccessControlProfiles()` method matching method androidx.security.identity.PersonalizationData.Builder.addAccessControlProfile(androidx.security.identity.AccessControlProfile)
-
-
-NullableCollection: androidx.security.identity.ResultData#getEntryNames(String):
-    Return type of method androidx.security.identity.ResultData.getEntryNames(String) is a nullable collection (`java.util.Collection`); must be non-null
-NullableCollection: androidx.security.identity.ResultData#getRetrievedEntryNames(String):
-    Return type of method androidx.security.identity.ResultData.getRetrievedEntryNames(String) is a nullable collection (`java.util.Collection`); must be non-null
diff --git a/security/security-identity-credential/api/current.txt b/security/security-identity-credential/api/current.txt
deleted file mode 100644
index 1bae701..0000000
--- a/security/security-identity-credential/api/current.txt
+++ /dev/null
@@ -1,163 +0,0 @@
-// Signature format: 4.0
-package androidx.security.identity {
-
-  public class AccessControlProfile {
-  }
-
-  public static final class AccessControlProfile.Builder {
-    ctor public AccessControlProfile.Builder(androidx.security.identity.AccessControlProfileId);
-    method public androidx.security.identity.AccessControlProfile build();
-    method public androidx.security.identity.AccessControlProfile.Builder setReaderCertificate(java.security.cert.X509Certificate);
-    method public androidx.security.identity.AccessControlProfile.Builder setUserAuthenticationRequired(boolean);
-    method public androidx.security.identity.AccessControlProfile.Builder setUserAuthenticationTimeout(long);
-  }
-
-  public class AccessControlProfileId {
-    ctor public AccessControlProfileId(int);
-    method public int getId();
-  }
-
-  public class AlreadyPersonalizedException extends androidx.security.identity.IdentityCredentialException {
-    ctor public AlreadyPersonalizedException(String);
-    ctor public AlreadyPersonalizedException(String, Throwable);
-  }
-
-  public class CipherSuiteNotSupportedException extends androidx.security.identity.IdentityCredentialException {
-    ctor public CipherSuiteNotSupportedException(String);
-    ctor public CipherSuiteNotSupportedException(String, Throwable);
-  }
-
-  public class DocTypeNotSupportedException extends androidx.security.identity.IdentityCredentialException {
-    ctor public DocTypeNotSupportedException(String);
-    ctor public DocTypeNotSupportedException(String, Throwable);
-  }
-
-  public class EphemeralPublicKeyNotFoundException extends androidx.security.identity.IdentityCredentialException {
-    ctor public EphemeralPublicKeyNotFoundException(String);
-    ctor public EphemeralPublicKeyNotFoundException(String, Throwable);
-  }
-
-  public abstract class IdentityCredential {
-    method public abstract java.security.KeyPair createEphemeralKeyPair();
-    method public abstract byte[] decryptMessageFromReader(byte[]) throws androidx.security.identity.MessageDecryptionException;
-    method public byte[] delete(byte[]);
-    method public abstract byte[] encryptMessageToReader(byte[]);
-    method public abstract java.util.Collection<java.security.cert.X509Certificate!> getAuthKeysNeedingCertification();
-    method public abstract int[] getAuthenticationDataUsageCount();
-    method public abstract java.util.Collection<java.security.cert.X509Certificate!> getCredentialKeyCertificateChain();
-    method public abstract androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
-    method public abstract androidx.security.identity.ResultData getEntries(byte[]?, java.util.Map<java.lang.String!,java.util.Collection<java.lang.String!>!>, byte[]?) throws androidx.security.identity.EphemeralPublicKeyNotFoundException, androidx.security.identity.InvalidReaderSignatureException, androidx.security.identity.InvalidRequestMessageException, androidx.security.identity.NoAuthenticationKeyAvailableException;
-    method public byte[] proveOwnership(byte[]);
-    method public abstract void setAllowUsingExhaustedKeys(boolean);
-    method public void setAllowUsingExpiredKeys(boolean);
-    method public abstract void setAvailableAuthenticationKeys(int, int);
-    method public abstract void setReaderEphemeralPublicKey(java.security.PublicKey) throws java.security.InvalidKeyException;
-    method public abstract void setSessionTranscript(byte[]);
-    method public void storeStaticAuthenticationData(java.security.cert.X509Certificate, android.icu.util.Calendar, byte[]) throws androidx.security.identity.UnknownAuthenticationKeyException;
-    method @Deprecated public abstract void storeStaticAuthenticationData(java.security.cert.X509Certificate, byte[]) throws androidx.security.identity.UnknownAuthenticationKeyException;
-    method public byte[] update(androidx.security.identity.PersonalizationData);
-  }
-
-  public class IdentityCredentialException extends java.lang.Exception {
-    ctor public IdentityCredentialException(String);
-    ctor public IdentityCredentialException(String, Throwable);
-  }
-
-  public abstract class IdentityCredentialStore {
-    method public abstract androidx.security.identity.WritableIdentityCredential createCredential(String, String) throws androidx.security.identity.AlreadyPersonalizedException, androidx.security.identity.DocTypeNotSupportedException;
-    method @Deprecated public abstract byte[]? deleteCredentialByName(String);
-    method public androidx.security.identity.IdentityCredentialStoreCapabilities getCapabilities();
-    method public abstract androidx.security.identity.IdentityCredential? getCredentialByName(String, int) throws androidx.security.identity.CipherSuiteNotSupportedException;
-    method public static androidx.security.identity.IdentityCredentialStore getDirectAccessInstance(android.content.Context);
-    method public static androidx.security.identity.IdentityCredentialStore? getHardwareInstance(android.content.Context);
-    method public static androidx.security.identity.IdentityCredentialStore getInstance(android.content.Context);
-    method public static androidx.security.identity.IdentityCredentialStore getSoftwareInstance(android.content.Context);
-    method @Deprecated public abstract String![] getSupportedDocTypes();
-    method public static boolean isDirectAccessSupported(android.content.Context);
-    field public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; // 0x1
-  }
-
-  public class IdentityCredentialStoreCapabilities {
-    method public int getFeatureVersion();
-    method public java.util.Set<java.lang.String!> getSupportedDocTypes();
-    method public boolean isDeleteSupported();
-    method public boolean isDirectAccess();
-    method public boolean isHardwareBacked();
-    method public boolean isProveOwnershipSupported();
-    method public boolean isStaticAuthenticationDataExpirationSupported();
-    method public boolean isUpdateSupported();
-    field public static final int FEATURE_VERSION_202009 = 202009; // 0x31519
-    field public static final int FEATURE_VERSION_202101 = 202101; // 0x31575
-  }
-
-  public class InvalidReaderSignatureException extends androidx.security.identity.IdentityCredentialException {
-    ctor public InvalidReaderSignatureException(String);
-    ctor public InvalidReaderSignatureException(String, Throwable);
-  }
-
-  public class InvalidRequestMessageException extends androidx.security.identity.IdentityCredentialException {
-    ctor public InvalidRequestMessageException(String);
-    ctor public InvalidRequestMessageException(String, Throwable);
-  }
-
-  public class MessageDecryptionException extends androidx.security.identity.IdentityCredentialException {
-    ctor public MessageDecryptionException(String);
-    ctor public MessageDecryptionException(String, Throwable);
-  }
-
-  public class NoAuthenticationKeyAvailableException extends androidx.security.identity.IdentityCredentialException {
-    ctor public NoAuthenticationKeyAvailableException(String);
-    ctor public NoAuthenticationKeyAvailableException(String, Throwable);
-  }
-
-  public class PersonalizationData {
-  }
-
-  public static final class PersonalizationData.Builder {
-    ctor public PersonalizationData.Builder();
-    method public androidx.security.identity.PersonalizationData.Builder addAccessControlProfile(androidx.security.identity.AccessControlProfile);
-    method public androidx.security.identity.PersonalizationData build();
-    method public androidx.security.identity.PersonalizationData.Builder putEntry(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, byte[]);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryBoolean(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, boolean);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryBytestring(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, byte[]);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryCalendar(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, android.icu.util.Calendar);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryInteger(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, long);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryString(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, String);
-  }
-
-  public abstract class ResultData {
-    method public abstract byte[] getAuthenticatedData();
-    method public abstract byte[]? getEcdsaSignature();
-    method public abstract byte[]? getEntry(String, String);
-    method public boolean getEntryBoolean(String, String);
-    method public byte[]? getEntryBytestring(String, String);
-    method public android.icu.util.Calendar? getEntryCalendar(String, String);
-    method public long getEntryInteger(String, String);
-    method public abstract java.util.Collection<java.lang.String!>? getEntryNames(String);
-    method public String? getEntryString(String, String);
-    method public abstract byte[]? getMessageAuthenticationCode();
-    method public abstract java.util.Collection<java.lang.String!> getNamespaces();
-    method public abstract java.util.Collection<java.lang.String!>? getRetrievedEntryNames(String);
-    method public abstract byte[] getStaticAuthenticationData();
-    method public abstract int getStatus(String, String);
-    field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
-    field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
-    field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
-    field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
-    field public static final int STATUS_OK = 0; // 0x0
-    field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
-    field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
-  }
-
-  public class UnknownAuthenticationKeyException extends androidx.security.identity.IdentityCredentialException {
-    ctor public UnknownAuthenticationKeyException(String);
-    ctor public UnknownAuthenticationKeyException(String, Throwable);
-  }
-
-  public abstract class WritableIdentityCredential {
-    method public abstract java.util.Collection<java.security.cert.X509Certificate!> getCredentialKeyCertificateChain(byte[]);
-    method public abstract byte[] personalize(androidx.security.identity.PersonalizationData);
-  }
-
-}
-
diff --git a/security/security-identity-credential/api/res-current.txt b/security/security-identity-credential/api/res-current.txt
deleted file mode 100644
index e69de29..0000000
--- a/security/security-identity-credential/api/res-current.txt
+++ /dev/null
diff --git a/security/security-identity-credential/api/restricted_current.txt b/security/security-identity-credential/api/restricted_current.txt
deleted file mode 100644
index 1bae701..0000000
--- a/security/security-identity-credential/api/restricted_current.txt
+++ /dev/null
@@ -1,163 +0,0 @@
-// Signature format: 4.0
-package androidx.security.identity {
-
-  public class AccessControlProfile {
-  }
-
-  public static final class AccessControlProfile.Builder {
-    ctor public AccessControlProfile.Builder(androidx.security.identity.AccessControlProfileId);
-    method public androidx.security.identity.AccessControlProfile build();
-    method public androidx.security.identity.AccessControlProfile.Builder setReaderCertificate(java.security.cert.X509Certificate);
-    method public androidx.security.identity.AccessControlProfile.Builder setUserAuthenticationRequired(boolean);
-    method public androidx.security.identity.AccessControlProfile.Builder setUserAuthenticationTimeout(long);
-  }
-
-  public class AccessControlProfileId {
-    ctor public AccessControlProfileId(int);
-    method public int getId();
-  }
-
-  public class AlreadyPersonalizedException extends androidx.security.identity.IdentityCredentialException {
-    ctor public AlreadyPersonalizedException(String);
-    ctor public AlreadyPersonalizedException(String, Throwable);
-  }
-
-  public class CipherSuiteNotSupportedException extends androidx.security.identity.IdentityCredentialException {
-    ctor public CipherSuiteNotSupportedException(String);
-    ctor public CipherSuiteNotSupportedException(String, Throwable);
-  }
-
-  public class DocTypeNotSupportedException extends androidx.security.identity.IdentityCredentialException {
-    ctor public DocTypeNotSupportedException(String);
-    ctor public DocTypeNotSupportedException(String, Throwable);
-  }
-
-  public class EphemeralPublicKeyNotFoundException extends androidx.security.identity.IdentityCredentialException {
-    ctor public EphemeralPublicKeyNotFoundException(String);
-    ctor public EphemeralPublicKeyNotFoundException(String, Throwable);
-  }
-
-  public abstract class IdentityCredential {
-    method public abstract java.security.KeyPair createEphemeralKeyPair();
-    method public abstract byte[] decryptMessageFromReader(byte[]) throws androidx.security.identity.MessageDecryptionException;
-    method public byte[] delete(byte[]);
-    method public abstract byte[] encryptMessageToReader(byte[]);
-    method public abstract java.util.Collection<java.security.cert.X509Certificate!> getAuthKeysNeedingCertification();
-    method public abstract int[] getAuthenticationDataUsageCount();
-    method public abstract java.util.Collection<java.security.cert.X509Certificate!> getCredentialKeyCertificateChain();
-    method public abstract androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
-    method public abstract androidx.security.identity.ResultData getEntries(byte[]?, java.util.Map<java.lang.String!,java.util.Collection<java.lang.String!>!>, byte[]?) throws androidx.security.identity.EphemeralPublicKeyNotFoundException, androidx.security.identity.InvalidReaderSignatureException, androidx.security.identity.InvalidRequestMessageException, androidx.security.identity.NoAuthenticationKeyAvailableException;
-    method public byte[] proveOwnership(byte[]);
-    method public abstract void setAllowUsingExhaustedKeys(boolean);
-    method public void setAllowUsingExpiredKeys(boolean);
-    method public abstract void setAvailableAuthenticationKeys(int, int);
-    method public abstract void setReaderEphemeralPublicKey(java.security.PublicKey) throws java.security.InvalidKeyException;
-    method public abstract void setSessionTranscript(byte[]);
-    method public void storeStaticAuthenticationData(java.security.cert.X509Certificate, android.icu.util.Calendar, byte[]) throws androidx.security.identity.UnknownAuthenticationKeyException;
-    method @Deprecated public abstract void storeStaticAuthenticationData(java.security.cert.X509Certificate, byte[]) throws androidx.security.identity.UnknownAuthenticationKeyException;
-    method public byte[] update(androidx.security.identity.PersonalizationData);
-  }
-
-  public class IdentityCredentialException extends java.lang.Exception {
-    ctor public IdentityCredentialException(String);
-    ctor public IdentityCredentialException(String, Throwable);
-  }
-
-  public abstract class IdentityCredentialStore {
-    method public abstract androidx.security.identity.WritableIdentityCredential createCredential(String, String) throws androidx.security.identity.AlreadyPersonalizedException, androidx.security.identity.DocTypeNotSupportedException;
-    method @Deprecated public abstract byte[]? deleteCredentialByName(String);
-    method public androidx.security.identity.IdentityCredentialStoreCapabilities getCapabilities();
-    method public abstract androidx.security.identity.IdentityCredential? getCredentialByName(String, int) throws androidx.security.identity.CipherSuiteNotSupportedException;
-    method public static androidx.security.identity.IdentityCredentialStore getDirectAccessInstance(android.content.Context);
-    method public static androidx.security.identity.IdentityCredentialStore? getHardwareInstance(android.content.Context);
-    method public static androidx.security.identity.IdentityCredentialStore getInstance(android.content.Context);
-    method public static androidx.security.identity.IdentityCredentialStore getSoftwareInstance(android.content.Context);
-    method @Deprecated public abstract String![] getSupportedDocTypes();
-    method public static boolean isDirectAccessSupported(android.content.Context);
-    field public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; // 0x1
-  }
-
-  public class IdentityCredentialStoreCapabilities {
-    method public int getFeatureVersion();
-    method public java.util.Set<java.lang.String!> getSupportedDocTypes();
-    method public boolean isDeleteSupported();
-    method public boolean isDirectAccess();
-    method public boolean isHardwareBacked();
-    method public boolean isProveOwnershipSupported();
-    method public boolean isStaticAuthenticationDataExpirationSupported();
-    method public boolean isUpdateSupported();
-    field public static final int FEATURE_VERSION_202009 = 202009; // 0x31519
-    field public static final int FEATURE_VERSION_202101 = 202101; // 0x31575
-  }
-
-  public class InvalidReaderSignatureException extends androidx.security.identity.IdentityCredentialException {
-    ctor public InvalidReaderSignatureException(String);
-    ctor public InvalidReaderSignatureException(String, Throwable);
-  }
-
-  public class InvalidRequestMessageException extends androidx.security.identity.IdentityCredentialException {
-    ctor public InvalidRequestMessageException(String);
-    ctor public InvalidRequestMessageException(String, Throwable);
-  }
-
-  public class MessageDecryptionException extends androidx.security.identity.IdentityCredentialException {
-    ctor public MessageDecryptionException(String);
-    ctor public MessageDecryptionException(String, Throwable);
-  }
-
-  public class NoAuthenticationKeyAvailableException extends androidx.security.identity.IdentityCredentialException {
-    ctor public NoAuthenticationKeyAvailableException(String);
-    ctor public NoAuthenticationKeyAvailableException(String, Throwable);
-  }
-
-  public class PersonalizationData {
-  }
-
-  public static final class PersonalizationData.Builder {
-    ctor public PersonalizationData.Builder();
-    method public androidx.security.identity.PersonalizationData.Builder addAccessControlProfile(androidx.security.identity.AccessControlProfile);
-    method public androidx.security.identity.PersonalizationData build();
-    method public androidx.security.identity.PersonalizationData.Builder putEntry(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, byte[]);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryBoolean(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, boolean);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryBytestring(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, byte[]);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryCalendar(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, android.icu.util.Calendar);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryInteger(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, long);
-    method public androidx.security.identity.PersonalizationData.Builder putEntryString(String, String, java.util.Collection<androidx.security.identity.AccessControlProfileId!>, String);
-  }
-
-  public abstract class ResultData {
-    method public abstract byte[] getAuthenticatedData();
-    method public abstract byte[]? getEcdsaSignature();
-    method public abstract byte[]? getEntry(String, String);
-    method public boolean getEntryBoolean(String, String);
-    method public byte[]? getEntryBytestring(String, String);
-    method public android.icu.util.Calendar? getEntryCalendar(String, String);
-    method public long getEntryInteger(String, String);
-    method public abstract java.util.Collection<java.lang.String!>? getEntryNames(String);
-    method public String? getEntryString(String, String);
-    method public abstract byte[]? getMessageAuthenticationCode();
-    method public abstract java.util.Collection<java.lang.String!> getNamespaces();
-    method public abstract java.util.Collection<java.lang.String!>? getRetrievedEntryNames(String);
-    method public abstract byte[] getStaticAuthenticationData();
-    method public abstract int getStatus(String, String);
-    field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
-    field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
-    field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
-    field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
-    field public static final int STATUS_OK = 0; // 0x0
-    field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
-    field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
-  }
-
-  public class UnknownAuthenticationKeyException extends androidx.security.identity.IdentityCredentialException {
-    ctor public UnknownAuthenticationKeyException(String);
-    ctor public UnknownAuthenticationKeyException(String, Throwable);
-  }
-
-  public abstract class WritableIdentityCredential {
-    method public abstract java.util.Collection<java.security.cert.X509Certificate!> getCredentialKeyCertificateChain(byte[]);
-    method public abstract byte[] personalize(androidx.security.identity.PersonalizationData);
-  }
-
-}
-
diff --git a/security/security-identity-credential/build.gradle b/security/security-identity-credential/build.gradle
deleted file mode 100644
index 4a02e1a..0000000
--- a/security/security-identity-credential/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-/**
- * This file was created using the `create_project.py` script located in the
- * `<AndroidX root>/development/project-creator` directory.
- *
- * Please use that script when creating a new project, rather than copying an existing project and
- * modifying its settings.
- */
-import androidx.build.LibraryType
-import androidx.build.RunApiTasks
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("kotlin-android")
-}
-
-dependencies {
-    api(libs.jspecify)
-    implementation("androidx.annotation:annotation:1.8.1")
-    implementation("androidx.biometric:biometric:1.1.0")
-    implementation("co.nstant.in:cbor:0.8")
-    implementation("org.bouncycastle:bcprov-jdk15on:1.65")
-    implementation("org.bouncycastle:bcpkix-jdk15on:1.56")
-
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.mockitoCore)
-}
-
-android {
-    defaultConfig {
-        minSdk = 24
-    }
-    namespace = "androidx.security.identity.credential"
-}
-
-androidx {
-    name = "Security"
-    type = LibraryType.PUBLISHED_LIBRARY
-    runApiTasks = new RunApiTasks.Yes("Need to track API surface before moving to publish")
-    mavenVersion = LibraryVersions.SECURITY_IDENTITY_CREDENTIAL
-    inceptionYear = "2019"
-    description = "AndroidX Security"
-    legacyDisableKotlinStrictApiMode = true
-}
diff --git a/security/security-identity-credential/lint-baseline.xml b/security/security-identity-credential/lint-baseline.xml
deleted file mode 100644
index 99c3f99..0000000
--- a/security/security-identity-credential/lint-baseline.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ResultData.STATUS_OK, ResultData.STATUS_NO_SUCH_ENTRY, ResultData.STATUS_NOT_REQUESTED, ResultData.STATUS_NOT_IN_REQUEST_MESSAGE, ResultData.STATUS_USER_AUTHENTICATION_FAILED, ResultData.STATUS_READER_AUTHENTICATION_FAILED, ResultData.STATUS_NO_ACCESS_CONTROL_PROFILES"
-        errorLine1="                    builder.addErrorStatus(namespaceName, entryName, status);"
-        errorLine2="                                                                     ~~~~~~">
-        <location
-            file="src/main/java/androidx/security/identity/HardwareIdentityCredential.java"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256"
-        errorLine1="                    mStore.getCredentialByName(credentialName, cipherSuite);"
-        errorLine2="                                                               ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/security/identity/HardwareIdentityCredentialStore.java"/>
-    </issue>
-
-</issues>
diff --git a/security/security-identity-credential/src/androidTest/AndroidManifest.xml b/security/security-identity-credential/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 074be6b..0000000
--- a/security/security-identity-credential/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<!--
-  Copyright (C) 2019 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
-  -->
-
-<manifest>
-</manifest>
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/CreateItemsRequestTest.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/CreateItemsRequestTest.java
deleted file mode 100644
index e298b98..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/CreateItemsRequestTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import co.nstant.in.cbor.CborException;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class CreateItemsRequestTest {
-    @Test
-    public void basicRequest() throws CborException {
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.test.ns", Arrays.asList("xyz", "abc"));
-
-        String docType = "org.test.ns";
-        assertEquals("{\n"
-                        + "  'docType' : 'org.test.ns',\n"
-                        + "  'nameSpaces' : {\n"
-                        + "    'org.test.ns' : {\n"
-                        + "      'abc' : false,\n"
-                        + "      'xyz' : false\n"
-                        + "    }\n"
-                        + "  }\n"
-                        + "}",
-                Util.cborPrettyPrint(Util.createItemsRequest(entriesToRequest, docType)));
-    }
-
-    @Test
-    public void multipleNamespaces() throws CborException {
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.test.ns1", Arrays.asList("foo", "bar"));
-        entriesToRequest.put("org.test.ns2", Arrays.asList("xyz", "abc"));
-        String docType = "org.test.ns";
-        assertEquals("{\n"
-                        + "  'docType' : 'org.test.ns',\n"
-                        + "  'nameSpaces' : {\n"
-                        + "    'org.test.ns1' : {\n"
-                        + "      'bar' : false,\n"
-                        + "      'foo' : false\n"
-                        + "    },\n"
-                        + "    'org.test.ns2' : {\n"
-                        + "      'abc' : false,\n"
-                        + "      'xyz' : false\n"
-                        + "    }\n"
-                        + "  }\n"
-                        + "}",
-                Util.cborPrettyPrint(Util.createItemsRequest(entriesToRequest, docType)));
-    }
-
-    @Test
-    public void noDocType() throws CborException {
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.test.ns1", Arrays.asList("foo", "bar"));
-        assertEquals("{\n"
-                        + "  'nameSpaces' : {\n"
-                        + "    'org.test.ns1' : {\n"
-                        + "      'bar' : false,\n"
-                        + "      'foo' : false\n"
-                        + "    }\n"
-                        + "  }\n"
-                        + "}",
-                Util.cborPrettyPrint(Util.createItemsRequest(entriesToRequest, null)));
-    }
-
-    @Test
-    public void empty() throws CborException {
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        assertEquals("{\n"
-                        + "  'nameSpaces' : {}\n"
-                        + "}",
-                Util.cborPrettyPrint(Util.createItemsRequest(entriesToRequest, null)));
-    }
-}
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/DynamicAuthTest.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/DynamicAuthTest.java
deleted file mode 100644
index 60c8f80d..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/DynamicAuthTest.java
+++ /dev/null
@@ -1,730 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.icu.util.Calendar;
-import android.os.SystemClock;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import javax.crypto.SecretKey;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class DynamicAuthTest {
-    private static final String TAG = "DynamicAuthTest";
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void checkAuthKey() throws Exception {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        String credentialName = "test";
-
-        store.deleteCredentialByName(credentialName);
-
-        WritableIdentityCredential wc = store.createCredential(credentialName,
-                "org.iso.18013-5.2019.mdl");
-
-        byte[] challenge = "TheChallenge".getBytes();
-        Collection<X509Certificate> certChain = wc.getCredentialKeyCertificateChain(challenge);
-        // Profile 0 (no authentication)
-        AccessControlProfile noAuthProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(0))
-                        .setUserAuthenticationRequired(false)
-                        .build();
-        Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
-        idsNoAuth.add(new AccessControlProfileId(0));
-        String mdlNs = "org.iso.18013-5.2019";
-        PersonalizationData personalizationData =
-                new PersonalizationData.Builder()
-                        .addAccessControlProfile(noAuthProfile)
-                        .putEntry(mdlNs, "First name", idsNoAuth, Util.cborEncodeString("Alan"))
-                        .putEntry(mdlNs, "Last name", idsNoAuth, Util.cborEncodeString("Turing"))
-                        .build();
-        byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
-        byte[] proofOfProvisioning =
-                Util.coseSign1GetData(Util.cborDecode(proofOfProvisioningSignature));
-        byte[] proofOfProvisioningSha256 =
-                MessageDigest.getInstance("SHA256").digest(proofOfProvisioning);
-
-        IdentityCredential credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNotNull(credential);
-        credential.setAvailableAuthenticationKeys(5, 3);
-        assertArrayEquals(
-                new int[]{0, 0, 0, 0, 0},
-                credential.getAuthenticationDataUsageCount());
-
-        Collection<X509Certificate> certificates = null;
-        certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(5, certificates.size());
-
-        X509Certificate cert = (X509Certificate) certificates.toArray()[0];
-
-        //  - serialNumber: INTEGER 1 (fixed value: same on all certs).
-        assertEquals(1, cert.getSerialNumber().intValue());
-
-        //  - issuer: CN shall be set to "Android Identity Credential Key". (fixed value:
-        //    same on all certs)
-        assertEquals("CN=Android Identity Credential Key",
-                cert.getIssuerX500Principal().getName());
-
-        //  - subject: CN shall be set to "Android Identity Credential Authentication Key". (fixed
-        //    value: same on all certs)
-        assertEquals("CN=Android Identity Credential Authentication Key",
-                cert.getSubjectX500Principal().getName());
-
-        //  - validity: should be from current time and one year in the future (365 days).
-        Date now = new Date();
-
-        // Allow for 10 seconds drift to account for the time drift and loss of precision
-        // when encoding into ASN.1
-        //
-        long diffMilliSecs = now.getTime() - cert.getNotBefore().getTime();
-        final long allowDriftMilliSecs = 10 * 1000;
-        assertTrue(-allowDriftMilliSecs <= diffMilliSecs && diffMilliSecs <= allowDriftMilliSecs);
-
-        final long kMilliSecsInOneYear = 365L * 24 * 60 * 60 * 1000;
-        diffMilliSecs =
-                cert.getNotBefore().getTime() + kMilliSecsInOneYear - cert.getNotAfter().getTime();
-        assertTrue(-allowDriftMilliSecs <= diffMilliSecs && diffMilliSecs <= allowDriftMilliSecs);
-
-        // The extension must be there if the underlying hardware says it
-        // supports updating the credential.
-        //
-        if (store.getCapabilities().isUpdateSupported()) {
-            byte[] icExtension = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26");
-            assertNotNull(icExtension);
-            assertArrayEquals(proofOfProvisioningSha256, Util.getPopSha256FromAuthKeyCert(cert));
-        }
-
-        // ... and we're done. Clean up after ourselves.
-        store.deleteCredentialByName(credentialName);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void dynamicAuthTest() throws Exception {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        String credentialName = "test";
-
-        store.deleteCredentialByName(credentialName);
-        Collection<X509Certificate> certChain = ProvisioningTest.createCredential(store,
-                credentialName);
-
-        IdentityCredential credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNotNull(credential);
-        assertArrayEquals(new int[0], credential.getAuthenticationDataUsageCount());
-
-        credential.setAvailableAuthenticationKeys(5, 3);
-        assertArrayEquals(
-                new int[]{0, 0, 0, 0, 0},
-                credential.getAuthenticationDataUsageCount());
-
-        // Getting data without device authentication should work even in the case where we haven't
-        // provisioned any authentication keys. Check that.
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
-        // no setSessionTranscript() call indicates Device Authentication not requested.
-        ResultData rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-        byte[] resultCbor = rd.getAuthenticatedData();
-        try {
-            String pretty = Util.cborPrettyPrint(Util.canonicalizeCbor(resultCbor));
-            assertEquals("{\n"
-                            + "  'org.iso.18013-5.2019' : {\n"
-                            + "    'Last name' : 'Turing',\n"
-                            + "    'First name' : 'Alan'\n"
-                            + "  }\n"
-                            + "}",
-                    pretty);
-        } catch (CborException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair();
-
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
-        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-
-        byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-        // Then check that getEntries() throw NoAuthenticationKeyAvailableException (_even_ when
-        // allowing using exhausted keys).
-        try {
-            credential.setSessionTranscript(sessionTranscript);
-            rd = credential.getEntries(
-                    Util.createItemsRequest(entriesToRequest, null),
-                    entriesToRequest,
-                    null);
-            assertTrue(false);
-        } catch (NoAuthenticationKeyAvailableException e) {
-            // This is the expected path...
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        // Get auth keys needing certification. This should be all of them. Note that
-        // this forces the creation of the authentication keys in the HAL.
-        Collection<X509Certificate> certificates = null;
-        certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(5, certificates.size());
-
-        // Do it one more time to check that an auth key is still pending even
-        // when the corresponding key has been created.
-        Collection<X509Certificate> certificates2 = null;
-        certificates2 = credential.getAuthKeysNeedingCertification();
-        assertArrayEquals(certificates.toArray(), certificates2.toArray());
-
-        // Now set auth data for the *first* key (this is the act of certifying the key) and check
-        // that one less key now needs certification.
-        X509Certificate key0Cert = certificates.iterator().next();
-
-        // Check key0Cert is signed by CredentialKey.
-        try {
-            key0Cert.verify(certChain.iterator().next().getPublicKey());
-        } catch (CertificateException
-                | InvalidKeyException
-                | NoSuchAlgorithmException
-                | NoSuchProviderException
-                | SignatureException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        try {
-            credential.storeStaticAuthenticationData(key0Cert, new byte[]{42, 43, 44});
-            certificates = credential.getAuthKeysNeedingCertification();
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        assertEquals(4, certificates.size());
-
-        // Now certify the *last* key.
-        X509Certificate key4Cert = new ArrayList<X509Certificate>(certificates).get(
-                certificates.size() - 1);
-        try {
-            key4Cert.verify(certChain.iterator().next().getPublicKey());
-        } catch (CertificateException
-                | InvalidKeyException
-                | NoSuchAlgorithmException
-                | NoSuchProviderException
-                | SignatureException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        try {
-            credential.storeStaticAuthenticationData(key4Cert, new byte[]{43, 44, 45});
-            certificates = credential.getAuthKeysNeedingCertification();
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        assertEquals(3, certificates.size());
-
-        // Now that we've provisioned authentication keys, presentation will no longer fail with
-        // NoAuthenticationKeyAvailableException ... So now we can try a sessionTranscript without
-        // the ephemeral public key that was created in the Secure Area and check it fails with
-        // EphemeralPublicKeyNotFoundException instead...
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        ByteArrayOutputStream stBaos = new ByteArrayOutputStream();
-        try {
-            new CborEncoder(stBaos).encode(new CborBuilder()
-                    .addArray()
-                    .add(new byte[]{0x01, 0x02})  // The device engagement structure, encoded
-                    .add(new byte[]{0x03, 0x04})  // Reader ephemeral public key, encoded
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        /* TODO
-        byte[] wrongSessionTranscript = stBaos.toByteArray();
-        try {
-            credential.setSessionTranscript(wrongSessionTranscript);
-            rd = credential.getEntries(
-                    Util.createItemsRequest(entriesToRequest, null),
-                    entriesToRequest,
-                    null);
-            assertTrue(false);
-        } catch (EphemeralPublicKeyNotFoundException e) {
-            // This is the expected path...
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        */
-
-        // Now use one of the keys...
-        entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019",
-                Arrays.asList("First name",
-                        "Last name",
-                        "Home address",
-                        "Birth date",
-                        "Cryptanalyst",
-                        "Portrait image",
-                        "Height"));
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        ephemeralKeyPair = credential.createEphemeralKeyPair();
-        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-        credential.setSessionTranscript(sessionTranscript);
-        rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-        resultCbor = rd.getAuthenticatedData();
-        try {
-            String pretty = Util.cborPrettyPrint(Util.canonicalizeCbor(resultCbor));
-            assertEquals("{\n"
-                            + "  'org.iso.18013-5.2019' : {\n"
-                            + "    'Height' : 180,\n"
-                            + "    'Last name' : 'Turing',\n"
-                            + "    'Birth date' : '19120623',\n"
-                            + "    'First name' : 'Alan',\n"
-                            + "    'Cryptanalyst' : true,\n"
-                            + "    'Home address' : 'Maida Vale, London, England',\n"
-                            + "    'Portrait image' : [0x01, 0x02]\n"
-                            + "  }\n"
-                            + "}",
-                    pretty);
-        } catch (CborException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        byte[] deviceAuthentication = Util.cborEncode(new CborBuilder()
-                .addArray()
-                .add("DeviceAuthentication")
-                .add(Util.cborDecode(sessionTranscript))
-                .add("org.iso.18013-5.2019.mdl")
-                .add(Util.cborBuildTaggedByteString(resultCbor))
-                .end()
-                .build().get(0));
-
-        byte[] deviceAuthenticationBytes =
-                Util.cborEncode(Util.cborBuildTaggedByteString((deviceAuthentication)));
-
-        byte[] mac = rd.getMessageAuthenticationCode();
-        byte[] signature = rd.getEcdsaSignature();
-        assertTrue(mac != null || signature != null);
-        if (mac != null) {
-            // Calculate the MAC by deriving the key using ECDH and HKDF.
-            SecretKey eMacKey = Util.calcEMacKeyForReader(
-                    key0Cert.getPublicKey(),
-                    readerEphemeralKeyPair.getPrivate(),
-                    sessionTranscript);
-            byte[] expectedMac = Util.cborEncode(Util.coseMac0(
-                    eMacKey,
-                    new byte[0],                  // payload
-                    deviceAuthenticationBytes));  // detached content
-
-            // Then compare it with what the TA produced.
-            assertArrayEquals(expectedMac, mac);
-        } else {
-            assertTrue(Util.coseSign1CheckSignature(
-                    Util.cborDecode(signature),
-                    deviceAuthenticationBytes,
-                    key0Cert.getPublicKey()));
-        }
-
-        // Check that key0's static auth data is returned and that this
-        // key has an increased use-count.
-        assertArrayEquals(new byte[]{42, 43, 44}, rd.getStaticAuthenticationData());
-        assertArrayEquals(new int[]{1, 0, 0, 0, 0}, credential.getAuthenticationDataUsageCount());
-
-
-        // Now do this one more time.... this time key4 should have been used. Check this by
-        // inspecting use-counts and the static authentication data.
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        ephemeralKeyPair = credential.createEphemeralKeyPair();
-        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-        credential.setSessionTranscript(sessionTranscript);
-        rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-        assertArrayEquals(new byte[]{43, 44, 45}, rd.getStaticAuthenticationData());
-        assertArrayEquals(new int[]{1, 0, 0, 0, 1}, credential.getAuthenticationDataUsageCount());
-
-        deviceAuthentication = Util.cborEncode(new CborBuilder()
-                .addArray()
-                .add("DeviceAuthentication")
-                .add(Util.cborDecode(sessionTranscript))
-                .add("org.iso.18013-5.2019.mdl")
-                .add(Util.cborBuildTaggedByteString(resultCbor))
-                .end()
-                .build().get(0));
-
-        deviceAuthenticationBytes =
-                Util.cborEncode(Util.cborBuildTaggedByteString((deviceAuthentication)));
-
-        // Verify Signature or MAC was made with key4.
-        mac = rd.getMessageAuthenticationCode();
-        signature = rd.getEcdsaSignature();
-        assertTrue(mac != null || signature != null);
-        if (mac != null) {
-            SecretKey eMacKey = Util.calcEMacKeyForReader(
-                    key4Cert.getPublicKey(),
-                    readerEphemeralKeyPair.getPrivate(),
-                    sessionTranscript);
-            byte[] expectedMac = Util.cborEncode(Util.coseMac0(eMacKey,
-                    new byte[0],                 // payload
-                    deviceAuthenticationBytes));  // detached content
-            assertArrayEquals(expectedMac, mac);
-        } else {
-            assertTrue(Util.coseSign1CheckSignature(
-                    Util.cborDecode(signature),
-                    deviceAuthenticationBytes,
-                    key4Cert.getPublicKey()));
-        }
-
-        // And again.... this time key0 should have been used. Check it.
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        ephemeralKeyPair = credential.createEphemeralKeyPair();
-        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-        credential.setSessionTranscript(sessionTranscript);
-        rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-        assertArrayEquals(new byte[]{42, 43, 44}, rd.getStaticAuthenticationData());
-        assertArrayEquals(new int[]{2, 0, 0, 0, 1}, credential.getAuthenticationDataUsageCount());
-
-        // And again.... this time key4 should have been used. Check it.
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        ephemeralKeyPair = credential.createEphemeralKeyPair();
-        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-        credential.setSessionTranscript(sessionTranscript);
-        rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-        assertArrayEquals(new byte[]{43, 44, 45}, rd.getStaticAuthenticationData());
-        assertArrayEquals(new int[]{2, 0, 0, 0, 2}, credential.getAuthenticationDataUsageCount());
-
-        // We configured each key to have three uses only. So we have two more presentations
-        // to go until we run out... first, check that only three keys need certifications
-        certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(3, certificates.size());
-
-        // Then exhaust the two we've already configured.
-        for (int n = 0; n < 2; n++) {
-            credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-            ephemeralKeyPair = credential.createEphemeralKeyPair();
-            credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-            sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-            credential.setSessionTranscript(sessionTranscript);
-            rd = credential.getEntries(
-                    Util.createItemsRequest(entriesToRequest, null),
-                    entriesToRequest,
-                    null);
-            assertNotNull(rd);
-        }
-        assertArrayEquals(new int[]{3, 0, 0, 0, 3}, credential.getAuthenticationDataUsageCount());
-
-        // Now we should have five certs needing certification.
-        certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(5, certificates.size());
-
-        // We still have the two keys which have been exhausted.
-        assertArrayEquals(new int[]{3, 0, 0, 0, 3}, credential.getAuthenticationDataUsageCount());
-
-        // Check that we fail when running out of presentations (and explicitly don't allow
-        // exhausting keys).
-        try {
-            credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-            ephemeralKeyPair = credential.createEphemeralKeyPair();
-            credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-            sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-            credential.setAllowUsingExhaustedKeys(false);
-            credential.setSessionTranscript(sessionTranscript);
-            rd = credential.getEntries(
-                    Util.createItemsRequest(entriesToRequest, null),
-                    entriesToRequest,
-                    null);
-            assertTrue(false);
-        } catch (IdentityCredentialException e) {
-            assertTrue(e instanceof NoAuthenticationKeyAvailableException);
-        }
-        assertArrayEquals(new int[]{3, 0, 0, 0, 3}, credential.getAuthenticationDataUsageCount());
-
-        // Now try with allowing using auth keys already exhausted... this should work!
-        try {
-            credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-            ephemeralKeyPair = credential.createEphemeralKeyPair();
-            credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-            sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-            credential.setSessionTranscript(sessionTranscript);
-            rd = credential.getEntries(
-                    Util.createItemsRequest(entriesToRequest, null),
-                    entriesToRequest,
-                    null);
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        assertArrayEquals(new int[]{4, 0, 0, 0, 3}, credential.getAuthenticationDataUsageCount());
-
-        // Check that replenishing works...
-        certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(5, certificates.size());
-        X509Certificate keyNewCert = certificates.iterator().next();
-        try {
-            credential.storeStaticAuthenticationData(keyNewCert, new byte[]{10, 11, 12});
-            certificates = credential.getAuthKeysNeedingCertification();
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        assertEquals(4, certificates.size());
-        assertArrayEquals(new int[]{0, 0, 0, 0, 3}, credential.getAuthenticationDataUsageCount());
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        ephemeralKeyPair = credential.createEphemeralKeyPair();
-        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-        credential.setSessionTranscript(sessionTranscript);
-        rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-        assertArrayEquals(new byte[]{10, 11, 12}, rd.getStaticAuthenticationData());
-        assertArrayEquals(new int[]{1, 0, 0, 0, 3}, credential.getAuthenticationDataUsageCount());
-
-        deviceAuthentication = Util.cborEncode(new CborBuilder()
-                .addArray()
-                .add("DeviceAuthentication")
-                .add(Util.cborDecode(sessionTranscript))
-                .add("org.iso.18013-5.2019.mdl")
-                .add(Util.cborBuildTaggedByteString(resultCbor))
-                .end()
-                .build().get(0));
-
-        deviceAuthenticationBytes =
-                Util.cborEncode(Util.cborBuildTaggedByteString((deviceAuthentication)));
-
-        mac = rd.getMessageAuthenticationCode();
-        signature = rd.getEcdsaSignature();
-        assertTrue(mac != null || signature != null);
-        if (mac != null) {
-            SecretKey eMacKey = Util.calcEMacKeyForReader(
-                    keyNewCert.getPublicKey(),
-                    readerEphemeralKeyPair.getPrivate(),
-                    sessionTranscript);
-            byte[] expectedMac = Util.cborEncode(Util.coseMac0(eMacKey,
-                    new byte[0],                  // payload
-                    deviceAuthenticationBytes));  // detached content
-            assertArrayEquals(expectedMac, rd.getMessageAuthenticationCode());
-        } else {
-            assertTrue(Util.coseSign1CheckSignature(
-                    Util.cborDecode(signature),
-                    deviceAuthenticationBytes,
-                    keyNewCert.getPublicKey()));
-        }
-
-        // ... and we're done. Clean up after ourselves.
-        store.deleteCredentialByName(credentialName);
-    }
-
-    // TODO: test storeStaticAuthenticationData() throwing UnknownAuthenticationKeyException
-    // on an unknown auth key
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void dynamicAuthWithExpirationTest() throws Exception {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-        assumeTrue(store.getCapabilities().isStaticAuthenticationDataExpirationSupported());
-
-        String credentialName = "test";
-
-        store.deleteCredentialByName(credentialName);
-        Collection<X509Certificate> certChain = ProvisioningTest.createCredential(store,
-                credentialName);
-
-        IdentityCredential credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNotNull(credential);
-
-        credential.setAvailableAuthenticationKeys(3, 5);
-
-        Collection<X509Certificate> certificates = null;
-        certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(3, certificates.size());
-
-        // Endorse an auth-key but set expiration to 10 seconds in the future.
-        //
-        Calendar now = Calendar.getInstance();
-        Calendar tenSecondsFromNow = Calendar.getInstance();
-        tenSecondsFromNow.add(Calendar.SECOND, 10);
-        try {
-            X509Certificate key0Cert = certificates.iterator().next();
-            credential.storeStaticAuthenticationData(key0Cert,
-                    tenSecondsFromNow,
-                    new byte[]{52, 53, 44});
-            certificates = credential.getAuthKeysNeedingCertification();
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        assertEquals(2, certificates.size());
-        assertArrayEquals(
-                new int[]{0, 0, 0},
-                credential.getAuthenticationDataUsageCount());
-        // Check that presentation works.
-        try {
-            IdentityCredential tc = store.getCredentialByName(credentialName,
-                    IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-            KeyPair ekp = tc.createEphemeralKeyPair();
-            KeyPair rekp = Util.createEphemeralKeyPair();
-            tc.setReaderEphemeralPublicKey(rekp.getPublic());
-            tc.setSessionTranscript(Util.buildSessionTranscript(ekp));
-            Map<String, Collection<String>> etr = new LinkedHashMap<>();
-            etr.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
-            ResultData rd = tc.getEntries(
-                    Util.createItemsRequest(etr, null),
-                    etr,
-                    null);
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertArrayEquals(
-                new int[]{1, 0, 0},
-                credential.getAuthenticationDataUsageCount());
-
-        SystemClock.sleep(11 * 1000);
-
-        certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(3, certificates.size());
-
-        // Check that presentation now fails..
-        try {
-            IdentityCredential tc = store.getCredentialByName(credentialName,
-                    IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-            KeyPair ekp = tc.createEphemeralKeyPair();
-            KeyPair rekp = Util.createEphemeralKeyPair();
-            tc.setReaderEphemeralPublicKey(rekp.getPublic());
-            tc.setSessionTranscript(Util.buildSessionTranscript(ekp));
-            Map<String, Collection<String>> etr = new LinkedHashMap<>();
-            etr.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
-            ResultData rd = tc.getEntries(
-                    Util.createItemsRequest(etr, null),
-                    etr,
-                    null);
-            assertTrue(false);
-        } catch (NoAuthenticationKeyAvailableException e) {
-            // This is the expected path...
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertArrayEquals(
-                new int[]{1, 0, 0},
-                credential.getAuthenticationDataUsageCount());
-
-        // Check that it works if we use setAllowUsingExpiredKeys(true)
-        try {
-            IdentityCredential tc = store.getCredentialByName(credentialName,
-                    IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-            tc.setAllowUsingExpiredKeys(true);   // <-- this is the call that makes the difference!
-            KeyPair ekp = tc.createEphemeralKeyPair();
-            KeyPair rekp = Util.createEphemeralKeyPair();
-            tc.setReaderEphemeralPublicKey(rekp.getPublic());
-            tc.setSessionTranscript(Util.buildSessionTranscript(ekp));
-            Map<String, Collection<String>> etr = new LinkedHashMap<>();
-            etr.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
-            ResultData rd = tc.getEntries(
-                    Util.createItemsRequest(etr, null),
-                    etr,
-                    null);
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertArrayEquals(
-                new int[]{2, 0, 0},
-                credential.getAuthenticationDataUsageCount());
-
-        // ... and we're done. Clean up after ourselves.
-        store.deleteCredentialByName(credentialName);
-    }
-
-}
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/EphemeralKeyTest.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/EphemeralKeyTest.java
deleted file mode 100644
index e2288a8..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/EphemeralKeyTest.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static junit.framework.TestCase.assertTrue;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.Context;
-import android.security.keystore.KeyProperties;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.ByteBuffer;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import java.security.spec.ECGenParameterSpec;
-import java.util.Collection;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyAgreement;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-// TODO: For better coverage, use different ECDH and HKDF implementations in test code.
-@SuppressWarnings("deprecation")
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class EphemeralKeyTest {
-    private static final String TAG = "EphemeralKeyTest";
-
-    @Test
-    public void createEphemeralKey() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        String credentialName = "ephemeralKeyTest";
-        byte[] sessionTranscript = {0x01, 0x02, 0x03};
-
-        store.deleteCredentialByName(credentialName);
-        Collection<X509Certificate> certChain = ProvisioningTest.createCredential(store,
-                credentialName);
-        IdentityCredential credential = store.getCredentialByName(credentialName,
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNotNull(credential);
-
-        // Check we can get both the public and private keys.
-        KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
-        assertNotNull(ephemeralKeyPair);
-        assertTrue(ephemeralKeyPair.getPublic().getEncoded().length > 0);
-        assertTrue(ephemeralKeyPair.getPrivate().getEncoded().length > 0);
-
-        TestReader reader = new TestReader(
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
-                ephemeralKeyPair.getPublic(),
-                sessionTranscript);
-
-        try {
-            credential.setReaderEphemeralPublicKey(reader.getEphemeralPublicKey());
-        } catch (InvalidKeyException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        credential.setSessionTranscript(sessionTranscript);
-
-        // Exchange a couple of messages... this is to test that the nonce/counter
-        // state works as expected.
-        for (int n = 0; n < 5; n++) {
-            // First send a message from the Reader to the Holder...
-            byte[] messageToHolder = ("Hello Holder! (serial=" + n + ")").getBytes();
-            byte[] encryptedMessageToHolder = reader.encryptMessageToHolder(messageToHolder);
-            assertNotEquals(messageToHolder, encryptedMessageToHolder);
-            byte[] decryptedMessageToHolder = credential.decryptMessageFromReader(
-                    encryptedMessageToHolder);
-            assertArrayEquals(messageToHolder, decryptedMessageToHolder);
-
-            // Then from the Holder to the Reader...
-            byte[] messageToReader = ("Hello Reader! (serial=" + n + ")").getBytes();
-            byte[] encryptedMessageToReader = credential.encryptMessageToReader(messageToReader);
-            assertNotEquals(messageToReader, encryptedMessageToReader);
-            byte[] decryptedMessageToReader = reader.decryptMessageFromHolder(
-                    encryptedMessageToReader);
-            assertArrayEquals(messageToReader, decryptedMessageToReader);
-        }
-    }
-
-    static class TestReader {
-
-        @IdentityCredentialStore.Ciphersuite
-        private int mCipherSuite;
-
-        private PublicKey mHolderEphemeralPublicKey;
-        private KeyPair mEphemeralKeyPair;
-        private SecretKey mSKDevice;
-        private SecretKey mSKReader;
-        private int mSKDeviceCounter;
-        private int mSKReaderCounter;
-
-        private SecureRandom mSecureRandom;
-
-        private boolean mRemoteIsReaderDevice;
-
-        // This is basically the reader-side of what needs to happen for encryption/decryption
-        // of messages.. could easily be re-used in an mDL reader application.
-        TestReader(@IdentityCredentialStore.Ciphersuite int cipherSuite,
-                PublicKey holderEphemeralPublicKey,
-                byte[] sessionTranscript) throws IdentityCredentialException {
-            mCipherSuite = cipherSuite;
-            mHolderEphemeralPublicKey = holderEphemeralPublicKey;
-            mSKReaderCounter = 1;
-            mSKDeviceCounter = 1;
-
-            try {
-                KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
-                ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
-                kpg.initialize(ecSpec);
-                mEphemeralKeyPair = kpg.generateKeyPair();
-            } catch (NoSuchAlgorithmException
-                    | InvalidAlgorithmParameterException e) {
-                e.printStackTrace();
-                throw new IdentityCredentialException("Error generating ephemeral key", e);
-            }
-
-            try {
-                KeyAgreement ka = KeyAgreement.getInstance("ECDH");
-                ka.init(mEphemeralKeyPair.getPrivate());
-                ka.doPhase(mHolderEphemeralPublicKey, true);
-                byte[] sharedSecret = ka.generateSecret();
-
-                byte[] sessionTranscriptBytes =
-                        Util.cborEncode(Util.cborBuildTaggedByteString(sessionTranscript));
-                byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
-
-                byte[] info = new byte[] {'S', 'K', 'D', 'e', 'v', 'i', 'c', 'e'};
-                byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-                mSKDevice = new SecretKeySpec(derivedKey, "AES");
-
-                info = new byte[] {'S', 'K', 'R', 'e', 'a', 'd', 'e', 'r'};
-                derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-                mSKReader = new SecretKeySpec(derivedKey, "AES");
-
-                mSecureRandom = new SecureRandom();
-
-            } catch (InvalidKeyException
-                    | NoSuchAlgorithmException e) {
-                e.printStackTrace();
-                throw new IdentityCredentialException("Error performing key agreement", e);
-            }
-        }
-
-        PublicKey getEphemeralPublicKey() {
-            return mEphemeralKeyPair.getPublic();
-        }
-
-        byte[] encryptMessageToHolder(byte[] messagePlaintext) throws IdentityCredentialException {
-            byte[] messageCiphertext = null;
-            try {
-                ByteBuffer iv = ByteBuffer.allocate(12);
-                iv.putInt(0, 0x00000000);
-                iv.putInt(4, 0x00000000);
-                iv.putInt(8, mSKReaderCounter);
-                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-                GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array());
-                cipher.init(Cipher.ENCRYPT_MODE, mSKReader, encryptionParameterSpec);
-                messageCiphertext = cipher.doFinal(messagePlaintext); // This includes the auth tag
-            } catch (BadPaddingException
-                    | IllegalBlockSizeException
-                    | NoSuchPaddingException
-                    | InvalidKeyException
-                    | NoSuchAlgorithmException
-                    | InvalidAlgorithmParameterException e) {
-                e.printStackTrace();
-                throw new IdentityCredentialException("Error encrypting message", e);
-            }
-            mSKReaderCounter += 1;
-            return messageCiphertext;
-        }
-
-        byte[] decryptMessageFromHolder(byte[] messageCiphertext)
-                throws IdentityCredentialException {
-            ByteBuffer iv = ByteBuffer.allocate(12);
-            iv.putInt(0, 0x00000000);
-            iv.putInt(4, 0x00000001);
-            iv.putInt(8, mSKDeviceCounter);
-            byte[] plaintext = null;
-            try {
-                final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-                cipher.init(Cipher.DECRYPT_MODE, mSKDevice, new GCMParameterSpec(128, iv.array()));
-                plaintext = cipher.doFinal(messageCiphertext);
-            } catch (BadPaddingException
-                    | IllegalBlockSizeException
-                    | InvalidAlgorithmParameterException
-                    | InvalidKeyException
-                    | NoSuchAlgorithmException
-                    | NoSuchPaddingException e) {
-                e.printStackTrace();
-                throw new IdentityCredentialException("Error decrypting message", e);
-            }
-            mSKDeviceCounter += 1;
-            return plaintext;
-        }
-    }
-}
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/HkdfTest.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/HkdfTest.java
deleted file mode 100644
index 7eea98f..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/HkdfTest.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Random;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class HkdfTest {
-
-    static Random sRandom = new Random();
-
-    /** Encodes a byte array to hex. */
-    static String hexEncode(final byte[] bytes) {
-        String chars = "0123456789abcdef";
-        StringBuilder result = new StringBuilder(2 * bytes.length);
-        for (byte b : bytes) {
-            // convert to unsigned
-            int val = b & 0xff;
-            result.append(chars.charAt(val / 16));
-            result.append(chars.charAt(val % 16));
-        }
-        return result.toString();
-    }
-
-    /** Decodes a hex string to a byte array. */
-    static byte[] hexDecode(String hex) {
-        if (hex.length() % 2 != 0) {
-            throw new IllegalArgumentException("Expected a string of even length");
-        }
-        int size = hex.length() / 2;
-        byte[] result = new byte[size];
-        for (int i = 0; i < size; i++) {
-            int hi = Character.digit(hex.charAt(2 * i), 16);
-            int lo = Character.digit(hex.charAt(2 * i + 1), 16);
-            if ((hi == -1) || (lo == -1)) {
-                throw new IllegalArgumentException("input is not hexadecimal");
-            }
-            result[i] = (byte) (16 * hi + lo);
-        }
-        return result;
-    }
-
-    static byte[] randBytes(int numBytes) {
-        byte[] bytes = new byte[numBytes];
-        sRandom.nextBytes(bytes);
-        return bytes;
-    }
-
-    @Test
-    public void testNullSaltOrInfo() {
-        byte[] ikm = randBytes(20);
-        byte[] info = randBytes(20);
-        int size = 40;
-
-        byte[] hkdfWithNullSalt = Util.computeHkdf("HmacSha256", ikm, null, info, size);
-        byte[] hkdfWithEmptySalt = Util.computeHkdf("HmacSha256", ikm, new byte[0], info, size);
-        assertArrayEquals(hkdfWithNullSalt, hkdfWithEmptySalt);
-
-        byte[] salt = randBytes(20);
-        byte[] hkdfWithNullInfo = Util.computeHkdf("HmacSha256", ikm, salt, null, size);
-        byte[] hkdfWithEmptyInfo = Util.computeHkdf("HmacSha256", ikm, salt, new byte[0], size);
-        assertArrayEquals(hkdfWithNullInfo, hkdfWithEmptyInfo);
-    }
-
-    @Test
-    public void testInvalidCodeSize() {
-        try {
-            Util.computeHkdf("HmacSha256", new byte[0], new byte[0], new byte[0], 32 * 256);
-            fail("Invalid size, should have thrown exception");
-        } catch (RuntimeException expected) {
-
-            // Expected
-        }
-    }
-
-    /**
-     * Tests the implementation against the test vectors from RFC 5869.
-     */
-    @Test
-    public void testVectors() {
-        // Test case 1
-        assertEquals(
-                "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf"
-                + "1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
-                computeHkdfHex("HmacSha256",
-                        "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
-                        "000102030405060708090a0b0c",
-                        "f0f1f2f3f4f5f6f7f8f9",
-                        42));
-
-        // Test case 2
-        assertEquals(
-                "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c"
-                        + "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71"
-                        + "cc30c58179ec3e87c14c01d5c1f3434f1d87",
-                computeHkdfHex("HmacSha256",
-                        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
-                                + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
-                                + "404142434445464748494a4b4c4d4e4f",
-                        "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
-                                + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
-                                + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
-                        "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
-                                + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef"
-                                + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
-                        82));
-
-        // Test case 3: salt is empty
-        assertEquals(
-                "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d"
-                        + "9d201395faa4b61a96c8",
-                computeHkdfHex("HmacSha256",
-                        "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "",
-                        42));
-
-        // Test Case 4
-        assertEquals(
-                "085a01ea1b10f36933068b56efa5ad81a4f14b822f"
-                + "5b091568a9cdd4f155fda2c22e422478d305f3f896",
-                computeHkdfHex(
-                        "HmacSha1",
-                        "0b0b0b0b0b0b0b0b0b0b0b",
-                        "000102030405060708090a0b0c",
-                        "f0f1f2f3f4f5f6f7f8f9",
-                        42));
-
-        // Test Case 5
-        assertEquals(
-                "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe"
-                        + "8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e"
-                        + "927336d0441f4c4300e2cff0d0900b52d3b4",
-                computeHkdfHex(
-                        "HmacSha1",
-                        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
-                                + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
-                                + "404142434445464748494a4b4c4d4e4f",
-                        "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
-                                + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
-                                + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
-                        "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
-                                + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef"
-                                + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
-                        82));
-
-        // Test Case 6: salt is empty
-        assertEquals(
-                "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0"
-                        + "ea00033de03984d34918",
-                computeHkdfHex("HmacSha1", "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "",
-                        42));
-
-        // Test Case 7
-        assertEquals(
-                "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5"
-                        + "673a081d70cce7acfc48",
-                computeHkdfHex("HmacSha1", "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "", "",
-                        42));
-    }
-
-    /**
-     * Test version of Hkdf where all inputs and outputs are hexadecimal.
-     */
-    private String computeHkdfHex(String macAlgorithm, String ikmHex, String saltHex,
-            String infoHex,
-            int size) {
-        return hexEncode(
-                Util.computeHkdf(macAlgorithm, hexDecode(ikmHex), hexDecode(saltHex),
-                        hexDecode(infoHex), size));
-    }
-
-}
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/ProvisioningTest.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/ProvisioningTest.java
deleted file mode 100644
index 2c1a2db..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/ProvisioningTest.java
+++ /dev/null
@@ -1,1086 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static androidx.security.identity.ResultData.STATUS_NOT_IN_REQUEST_MESSAGE;
-import static androidx.security.identity.ResultData.STATUS_NOT_REQUESTED;
-import static androidx.security.identity.ResultData.STATUS_NO_ACCESS_CONTROL_PROFILES;
-import static androidx.security.identity.ResultData.STATUS_NO_SUCH_ENTRY;
-import static androidx.security.identity.ResultData.STATUS_OK;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.biometric.BiometricPrompt;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.model.UnicodeString;
-import co.nstant.in.cbor.model.UnsignedInteger;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-@SuppressWarnings("deprecation")
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class ProvisioningTest {
-    private static final String TAG = "ProvisioningTest";
-
-    private static byte[] getExampleDrivingPrivilegesCbor() {
-        // As per 7.4.4 of ISO 18013-5, driving privileges are defined with the following CDDL:
-        //
-        // driving_privileges = [
-        //     * driving_privilege
-        // ]
-        //
-        // driving_privilege = {
-        //     vehicle_category_code: tstr ; Vehicle category code as per ISO 18013-2 Annex A
-        //     ? issue_date: #6.0(tstr)    ; Date of issue encoded as full-date per RFC 3339
-        //     ? expiry_date: #6.0(tstr)   ; Date of expiry encoded as full-date per RFC 3339
-        //     ? code: tstr                ; Code as per ISO 18013-2 Annex A
-        //     ? sign: tstr                ; Sign as per ISO 18013-2 Annex A
-        //     ? value: int                ; Value as per ISO 18013-2 Annex A
-        // }
-        //
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            new CborEncoder(baos).encode(new CborBuilder()
-                    .addArray()
-                    .addMap()
-                    .put(new UnicodeString("vehicle_category_code"), new UnicodeString("TODO"))
-                    .put(new UnicodeString("value"), new UnsignedInteger(42))
-                    .end()
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            assertTrue(false);
-        }
-        return baos.toByteArray();
-    }
-
-    static Collection<X509Certificate> createCredential(IdentityCredentialStore store,
-            String credentialName) throws IdentityCredentialException {
-        return createCredentialWithChallenge(store, credentialName, "SomeChallenge".getBytes());
-    }
-
-    static Collection<X509Certificate> createCredentialWithChallenge(IdentityCredentialStore store,
-            String credentialName,
-            byte[] challenge) throws IdentityCredentialException {
-        WritableIdentityCredential wc = null;
-        wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
-
-        Collection<X509Certificate> certificateChain =
-                wc.getCredentialKeyCertificateChain(challenge);
-        // TODO: inspect cert-chain
-
-        // Profile 0 (no authentication)
-        AccessControlProfile noAuthProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(0))
-                        .setUserAuthenticationRequired(false)
-                        .build();
-
-        byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
-
-        Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
-        idsNoAuth.add(new AccessControlProfileId(0));
-        Collection<AccessControlProfileId> idsNoAcp = new ArrayList<AccessControlProfileId>();
-        String mdlNs = "org.iso.18013-5.2019";
-        PersonalizationData personalizationData =
-                new PersonalizationData.Builder()
-                        .addAccessControlProfile(noAuthProfile)
-                        .putEntry(mdlNs, "First name", idsNoAuth, Util.cborEncodeString("Alan"))
-                        .putEntry(mdlNs, "Last name", idsNoAuth, Util.cborEncodeString("Turing"))
-                        .putEntry(mdlNs, "Home address", idsNoAuth,
-                                Util.cborEncodeString("Maida Vale, London, England"))
-                        .putEntry(mdlNs, "Birth date", idsNoAuth, Util.cborEncodeString("19120623"))
-                        .putEntry(mdlNs, "Cryptanalyst", idsNoAuth, Util.cborEncodeBoolean(true))
-                        .putEntry(mdlNs, "Portrait image", idsNoAuth, Util.cborEncodeBytestring(
-                            new byte[]{0x01, 0x02}))
-                        .putEntry(mdlNs, "Height", idsNoAuth, Util.cborEncodeNumber(180))
-                        .putEntry(mdlNs, "Neg Item", idsNoAuth, Util.cborEncodeNumber(-42))
-                        .putEntry(mdlNs, "Int Two Bytes", idsNoAuth, Util.cborEncodeNumber(0x101))
-                        .putEntry(mdlNs, "Int Four Bytes", idsNoAuth,
-                                Util.cborEncodeNumber(0x10001))
-                        .putEntry(mdlNs, "Int Eight Bytes", idsNoAuth,
-                                Util.cborEncodeNumber(0x100000001L))
-                        .putEntry(mdlNs, "driving_privileges", idsNoAuth, drivingPrivileges)
-                        .putEntry(mdlNs, "No Access", idsNoAcp,
-                                Util.cborEncodeString("Cannot be retrieved"))
-                        .build();
-
-        byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
-        byte[] proofOfProvisioning =
-                Util.coseSign1GetData(Util.cborDecode(proofOfProvisioningSignature));
-
-        String pretty = "";
-        pretty = Util.cborPrettyPrint(proofOfProvisioning);
-        Log.e(TAG, "pretty: " + pretty);
-        // Checks that order of elements is the order it was added, using the API.
-        assertEquals("[\n"
-                + "  'ProofOfProvisioning',\n"
-                + "  'org.iso.18013-5.2019.mdl',\n"
-                + "  [\n"
-                + "    {\n"
-                + "      'id' : 0\n"
-                + "    }\n"
-                + "  ],\n"
-                + "  {\n"
-                + "    'org.iso.18013-5.2019' : [\n"
-                + "      {\n"
-                + "        'name' : 'First name',\n"
-                + "        'value' : 'Alan',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Last name',\n"
-                + "        'value' : 'Turing',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Home address',\n"
-                + "        'value' : 'Maida Vale, London, England',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Birth date',\n"
-                + "        'value' : '19120623',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Cryptanalyst',\n"
-                + "        'value' : true,\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Portrait image',\n"
-                + "        'value' : [0x01, 0x02],\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Height',\n"
-                + "        'value' : 180,\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Neg Item',\n"
-                + "        'value' : -42,\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Int Two Bytes',\n"
-                + "        'value' : 257,\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Int Four Bytes',\n"
-                + "        'value' : 65537,\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Int Eight Bytes',\n"
-                + "        'value' : 4294967297,\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'driving_privileges',\n"
-                + "        'value' : [\n"
-                + "          {\n"
-                + "            'value' : 42,\n"
-                + "            'vehicle_category_code' : 'TODO'\n"
-                + "          }\n"
-                + "        ],\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'No Access',\n"
-                + "        'value' : 'Cannot be retrieved',\n"
-                + "        'accessControlProfiles' : []\n"
-                + "      }\n"
-                + "    ]\n"
-                + "  },\n"
-                + "  false\n"
-                + "]", pretty);
-
-        assertTrue(Util.coseSign1CheckSignature(
-                Util.cborDecode(proofOfProvisioningSignature),
-                new byte[0], // Additional data
-                certificateChain.iterator().next().getPublicKey()));
-
-        // TODO: Check challenge is in certificatechain
-
-        // TODO: Check each cert signs the next one
-
-        // TODO: Check bottom cert is the Google well-know cert
-
-        // TODO: need to also get and check SecurityStatement
-
-        return certificateChain;
-    }
-
-    static Collection<X509Certificate> createCredentialMultipleNamespaces(
-            IdentityCredentialStore store,
-            String credentialName) throws IdentityCredentialException {
-        WritableIdentityCredential wc = null;
-        wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
-
-        Collection<X509Certificate> certificateChain =
-                wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
-
-        // Profile 0 (no authentication)
-        AccessControlProfile noAuthProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(0))
-                        .setUserAuthenticationRequired(false)
-                        .build();
-
-        Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
-        idsNoAuth.add(new AccessControlProfileId(0));
-        PersonalizationData personalizationData =
-                new PersonalizationData.Builder()
-                        .addAccessControlProfile(noAuthProfile)
-                        .putEntry("org.example.barfoo", "Bar", idsNoAuth,
-                                Util.cborEncodeString("Foo"))
-                        .putEntry("org.example.barfoo", "Foo", idsNoAuth,
-                                Util.cborEncodeString("Bar"))
-                        .putEntry("org.example.foobar", "Foo", idsNoAuth,
-                                Util.cborEncodeString("Bar"))
-                        .putEntry("org.example.foobar", "Bar", idsNoAuth,
-                                Util.cborEncodeString("Foo"))
-                        .build();
-
-        byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
-        byte[] proofOfProvisioning =
-                Util.coseSign1GetData(Util.cborDecode(proofOfProvisioningSignature));
-        String pretty = Util.cborPrettyPrint(proofOfProvisioning);
-        // Checks that order of elements is the order it was added, using the API.
-        assertEquals("[\n"
-                + "  'ProofOfProvisioning',\n"
-                + "  'org.iso.18013-5.2019.mdl',\n"
-                + "  [\n"
-                + "    {\n"
-                + "      'id' : 0\n"
-                + "    }\n"
-                + "  ],\n"
-                + "  {\n"
-                + "    'org.example.barfoo' : [\n"
-                + "      {\n"
-                + "        'name' : 'Bar',\n"
-                + "        'value' : 'Foo',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Foo',\n"
-                + "        'value' : 'Bar',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      }\n"
-                + "    ],\n"
-                + "    'org.example.foobar' : [\n"
-                + "      {\n"
-                + "        'name' : 'Foo',\n"
-                + "        'value' : 'Bar',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Bar',\n"
-                + "        'value' : 'Foo',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      }\n"
-                + "    ]\n"
-                + "  },\n"
-                + "  false\n"
-                + "]", pretty);
-
-        return certificateChain;
-    }
-
-    @Test
-    public void alreadyPersonalized() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        createCredential(store, "test");
-        try {
-            createCredential(store, "test");
-            assertTrue(false);
-        } catch (AlreadyPersonalizedException e) {
-            // The expected path.
-        }
-        store.deleteCredentialByName("test");
-    }
-
-    @Test
-    public void nonExistent() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNull(credential);
-    }
-
-    @Test
-    public void defaultStoreSupportsAnyDocumentType() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        String[] supportedDocTypes = store.getSupportedDocTypes();
-        assertEquals(0, supportedDocTypes.length);
-    }
-
-    @Test
-    public void deleteCredentialOld()
-            throws IdentityCredentialException, CborException, CertificateEncodingException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        assertNull(store.deleteCredentialByName("test"));
-        Collection<X509Certificate> certificateChain = createCredential(store, "test");
-
-        // Deleting the credential involves destroying the keys referenced in the returned
-        // certificateChain... so get an encoded blob we can turn into a X509 cert when
-        // checking the deletion receipt below, post-deletion.
-        PublicKey credentialKeyPublic = certificateChain.iterator().next().getPublicKey();
-
-        byte[] proofOfDeletionSignature = store.deleteCredentialByName("test");
-        byte[] proofOfDeletion = Util.coseSign1GetData(Util.cborDecode(proofOfDeletionSignature));
-
-        // Check the returned CBOR is what is expected. Specifically note the challenge
-        // is _not_ included because we're using the old method.
-        String pretty = Util.cborPrettyPrint(proofOfDeletion);
-        assertEquals("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', false]", pretty);
-
-        assertTrue(Util.coseSign1CheckSignature(
-                Util.cborDecode(proofOfDeletionSignature),
-                new byte[0], // Additional data
-                credentialKeyPublic));
-
-        // Finally, check the credential is gone.
-        assertNull(store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256));
-    }
-
-    @Test
-    public void deleteCredential()
-            throws IdentityCredentialException, CborException, CertificateEncodingException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-        assumeTrue(store.getCapabilities().isDeleteSupported());
-
-        store.deleteCredentialByName("test");
-        assertNull(store.deleteCredentialByName("test"));
-        Collection<X509Certificate> certificateChain = createCredential(store, "test");
-
-        // Deleting the credential involves destroying the keys referenced in the returned
-        // certificateChain... so get an encoded blob we can turn into a X509 cert when
-        // checking the deletion receipt below, post-deletion.
-        PublicKey credentialKeyPublic = certificateChain.iterator().next().getPublicKey();
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNotNull(credential);
-        byte[] proofOfDeletionSignature = credential.delete(new byte[] {0x01, 0x02});
-        byte[] proofOfDeletion = Util.coseSign1GetData(Util.cborDecode(proofOfDeletionSignature));
-
-        // Check the returned CBOR is what is expected. Specifically note the challenge
-        // _is_ included because we're using the new delete() method.
-        String pretty = Util.cborPrettyPrint(proofOfDeletion);
-        assertEquals("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', [0x01, 0x02], false]",
-                pretty);
-
-        assertTrue(Util.coseSign1CheckSignature(
-                Util.cborDecode(proofOfDeletionSignature),
-                new byte[0], // Additional data
-                credentialKeyPublic));
-
-        // Finally, check the credential is gone.
-        assertNull(store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256));
-    }
-
-    @Test
-    public void proofOfOwnership()
-            throws IdentityCredentialException, CborException, CertificateEncodingException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-        assumeTrue(store.getCapabilities().isProveOwnershipSupported());
-
-        store.deleteCredentialByName("test");
-        assertNull(store.deleteCredentialByName("test"));
-        Collection<X509Certificate> certificateChain = createCredential(store, "test");
-
-        byte[] encodedCredentialCert = certificateChain.iterator().next().getEncoded();
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNotNull(credential);
-
-        byte[] challenge = new byte[]{0x12, 0x22};
-        byte[] proofOfOwnershipSignature = credential.proveOwnership(challenge);
-        byte[] proofOfOwnership = Util.coseSign1GetData(Util.cborDecode(proofOfOwnershipSignature));
-
-        // Check the returned CBOR is what is expected.
-        String pretty = Util.cborPrettyPrint(proofOfOwnership);
-        assertEquals("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', [0x12, 0x22], false]",
-                pretty);
-        assertTrue(Util.coseSign1CheckSignature(
-                Util.cborDecode(proofOfOwnershipSignature),
-                new byte[0], // Additional data
-                certificateChain.iterator().next().getPublicKey()));
-
-        // Finally, check the credential is still there
-        assertNotNull(store.deleteCredentialByName("test"));
-    }
-
-    @Test
-    public void testProvisionAndRetrieve() throws IdentityCredentialException, CborException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        Collection<X509Certificate> certChain = createCredential(store, "test");
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        // Check that the read-back certChain matches the created one.
-        Collection<X509Certificate> readBackCertChain =
-                credential.getCredentialKeyCertificateChain();
-        assertEquals(certChain.size(), readBackCertChain.size());
-        Iterator<X509Certificate> it = readBackCertChain.iterator();
-        for (X509Certificate expectedCert : certChain) {
-            X509Certificate readBackCert = it.next();
-            assertEquals(expectedCert, readBackCert);
-        }
-
-        // Check we can get a CryptoObject (even though it won't get used)
-        BiometricPrompt.CryptoObject cryptoObject = credential.getCryptoObject();
-
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019",
-                Arrays.asList("First name",
-                        "Last name",
-                        "Home address",
-                        "Birth date",
-                        "Cryptanalyst",
-                        "Portrait image",
-                        "Height",
-                        "Neg Item",
-                        "Int Two Bytes",
-                        "Int Eight Bytes",
-                        "Int Four Bytes",
-                        "driving_privileges"));
-        ResultData rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-
-        Collection<String> resultNamespaces = rd.getNamespaces();
-        assertEquals(resultNamespaces.size(), 1);
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        assertEquals(12, rd.getEntryNames("org.iso.18013-5.2019").size());
-
-        String ns = "org.iso.18013-5.2019";
-        assertEquals("Alan", rd.getEntryString(ns, "First name"));
-        assertEquals("Turing", rd.getEntryString(ns, "Last name"));
-        assertEquals("Maida Vale, London, England", rd.getEntryString(ns, "Home address"));
-        assertEquals("19120623", rd.getEntryString(ns, "Birth date"));
-        assertEquals(true, rd.getEntryBoolean(ns, "Cryptanalyst"));
-        assertArrayEquals(new byte[]{0x01, 0x02},
-                rd.getEntryBytestring(ns, "Portrait image"));
-        assertEquals(180, rd.getEntryInteger(ns, "Height"));
-        assertEquals(-42, rd.getEntryInteger(ns, "Neg Item"));
-        assertEquals(0x101, rd.getEntryInteger(ns, "Int Two Bytes"));
-        assertEquals(0x10001, rd.getEntryInteger(ns, "Int Four Bytes"));
-        assertEquals(0x100000001L, rd.getEntryInteger(ns, "Int Eight Bytes"));
-        byte[] drivingPrivileges = getExampleDrivingPrivilegesCbor();
-        assertArrayEquals(drivingPrivileges, rd.getEntry(ns, "driving_privileges"));
-
-        assertEquals("{\n"
-                + "  'org.iso.18013-5.2019' : {\n"
-                + "    'Height' : 180,\n"
-                + "    'Neg Item' : -42,\n"
-                + "    'Last name' : 'Turing',\n"
-                + "    'Birth date' : '19120623',\n"
-                + "    'First name' : 'Alan',\n"
-                + "    'Cryptanalyst' : true,\n"
-                + "    'Home address' : 'Maida Vale, London, England',\n"
-                + "    'Int Two Bytes' : 257,\n"
-                + "    'Int Four Bytes' : 65537,\n"
-                + "    'Portrait image' : [0x01, 0x02],\n"
-                + "    'Int Eight Bytes' : 4294967297,\n"
-                + "    'driving_privileges' : [\n"
-                + "      {\n"
-                + "        'value' : 42,\n"
-                + "        'vehicle_category_code' : 'TODO'\n"
-                + "      }\n"
-                + "    ]\n"
-                + "  }\n"
-                + "}", Util.cborPrettyPrint(Util.canonicalizeCbor(rd.getAuthenticatedData())));
-
-        store.deleteCredentialByName("test");
-    }
-
-    @Test
-    public void testProvisionAndRetrieveMultipleTimes() throws IdentityCredentialException,
-            InvalidKeyException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        // This checks we can do multiple getEntries() calls
-
-        store.deleteCredentialByName("test");
-        Collection<X509Certificate> certChain = createCredential(store, "test");
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        // We're going to need some authentication keys for this so create some dummy ones.
-        credential.setAvailableAuthenticationKeys(5, 1);
-        Collection<X509Certificate> authKeys = credential.getAuthKeysNeedingCertification();
-        for (X509Certificate authKey : authKeys) {
-            byte[] staticAuthData = new byte[5];
-            credential.storeStaticAuthenticationData(authKey, staticAuthData);
-        }
-
-        KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
-        KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair();
-        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
-        byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
-
-        credential.setSessionTranscript(sessionTranscript);
-        for (int n = 0; n < 3; n++) {
-            ResultData rd = credential.getEntries(
-                    Util.createItemsRequest(entriesToRequest, null),
-                    entriesToRequest,
-                    null);
-            assertEquals("Alan", rd.getEntryString("org.iso.18013-5.2019", "First name"));
-            assertEquals("Turing", rd.getEntryString("org.iso.18013-5.2019", "Last name"));
-            assertTrue(rd.getMessageAuthenticationCode() != null || rd.getEcdsaSignature() != null);
-        }
-
-        // Now try with a different (but still valid) sessionTranscript - this should fail with
-        // a RuntimeException
-        KeyPair otherEphemeralKeyPair = Util.createEphemeralKeyPair();
-        byte[] otherSessionTranscript = Util.buildSessionTranscript(otherEphemeralKeyPair);
-        try {
-            credential.setSessionTranscript(otherSessionTranscript);
-            assertTrue(false);
-        } catch (RuntimeException e) {
-            // This is the expected path...
-        } catch (Exception e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        store.deleteCredentialByName("test");
-    }
-
-    @Test
-    public void testProvisionAndRetrieveWithFiltering() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        Collection<X509Certificate> certChain = createCredential(store, "test");
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019",
-                Arrays.asList("First name",
-                        "Last name",
-                        "Home address",
-                        "Birth date",
-                        "Cryptanalyst",
-                        "Portrait image",
-                        "Height"));
-        Map<String, Collection<String>> entriesToRequestWithoutHomeAddress = new LinkedHashMap<>();
-        entriesToRequestWithoutHomeAddress.put("org.iso.18013-5.2019",
-                Arrays.asList("First name",
-                        "Last name",
-                        "Birth date",
-                        "Cryptanalyst",
-                        "Portrait image",
-                        "Height"));
-        ResultData rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequestWithoutHomeAddress,
-                null);
-
-        Collection<String> resultNamespaces = rd.getNamespaces();
-        assertEquals(resultNamespaces.size(), 1);
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        assertEquals(6, rd.getEntryNames("org.iso.18013-5.2019").size());
-
-        String ns = "org.iso.18013-5.2019";
-        assertEquals("Alan", rd.getEntryString(ns, "First name"));
-        assertEquals("Turing", rd.getEntryString(ns, "Last name"));
-        assertEquals("19120623", rd.getEntryString(ns, "Birth date"));
-        assertEquals(true, rd.getEntryBoolean(ns, "Cryptanalyst"));
-        assertArrayEquals(new byte[]{0x01, 0x02},
-                rd.getEntryBytestring(ns, "Portrait image"));
-        assertEquals(180, rd.getEntryInteger(ns, "Height"));
-
-        store.deleteCredentialByName("test");
-    }
-
-    @Test
-    public void testProvisionAndRetrieveElementWithNoACP() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        Collection<X509Certificate> certChain = createCredential(store, "test");
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("No Access"));
-        ResultData rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-
-        Collection<String> resultNamespaces = rd.getNamespaces();
-        assertEquals(resultNamespaces.size(), 1);
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        assertEquals(1, rd.getEntryNames("org.iso.18013-5.2019").size());
-        assertEquals(0, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
-
-        String ns = "org.iso.18013-5.2019";
-        assertEquals(STATUS_NO_ACCESS_CONTROL_PROFILES, rd.getStatus(ns, "No Access"));
-
-        store.deleteCredentialByName("test");
-    }
-
-    // TODO: Make sure we test retrieving an entry with multiple ACPs and test all four cases:
-    //
-    // - ACP1 bad,  ACP2 bad   -> NOT OK
-    // - ACP1 good, ACP2 bad   -> OK
-    // - ACP1 bad,  ACP2 good  -> OK
-    // - ACP1 good, ACP2 good  -> OK
-    //
-
-    @Test
-    public void testProvisionAndRetrieveWithEntryNotInRequest() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        Collection<X509Certificate> certChain = createCredential(store, "test");
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019",
-                Arrays.asList("First name",
-                        "Last name",
-                        "Home address",
-                        "Birth date",
-                        "Cryptanalyst",
-                        "Portrait image",
-                        "Height"));
-        Map<String, Collection<String>> entriesToRequestWithoutHomeAddress = new LinkedHashMap<>();
-        entriesToRequestWithoutHomeAddress.put("org.iso.18013-5.2019",
-                Arrays.asList("First name",
-                        "Last name",
-                        "Birth date",
-                        "Cryptanalyst",
-                        "Portrait image",
-                        "Height"));
-        ResultData rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequestWithoutHomeAddress, null),
-                entriesToRequest,
-                null);
-
-        Collection<String> resultNamespaces = rd.getNamespaces();
-        assertEquals(resultNamespaces.size(), 1);
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        assertEquals(7, rd.getEntryNames("org.iso.18013-5.2019").size());
-        assertEquals(6, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
-
-        String ns = "org.iso.18013-5.2019";
-        assertEquals(STATUS_NOT_IN_REQUEST_MESSAGE, rd.getStatus(ns, "Home address"));
-
-        assertEquals("Alan", rd.getEntryString(ns, "First name"));
-        assertEquals("Turing", rd.getEntryString(ns, "Last name"));
-        assertEquals("19120623", rd.getEntryString(ns, "Birth date"));
-        assertEquals(true, rd.getEntryBoolean(ns, "Cryptanalyst"));
-        assertArrayEquals(new byte[]{0x01, 0x02},
-                rd.getEntryBytestring(ns, "Portrait image"));
-        assertEquals(180, rd.getEntryInteger(ns, "Height"));
-
-        store.deleteCredentialByName("test");
-    }
-
-    @Test
-    public void nonExistentEntries() throws IdentityCredentialException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        Collection<X509Certificate> certChain = createCredential(store, "test");
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019",
-                Arrays.asList("First name",
-                        "Last name",
-                        "Non-existent Entry"));
-        ResultData rd = credential.getEntries(
-                null,
-                entriesToRequest,
-                null);
-
-        Collection<String> resultNamespaces = rd.getNamespaces();
-        assertEquals(resultNamespaces.size(), 1);
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        assertEquals(3, rd.getEntryNames("org.iso.18013-5.2019").size());
-        assertEquals(2, rd.getRetrievedEntryNames("org.iso.18013-5.2019").size());
-
-        String ns = "org.iso.18013-5.2019";
-
-        assertEquals(STATUS_OK, rd.getStatus(ns, "First name"));
-        assertEquals(STATUS_OK, rd.getStatus(ns, "Last name"));
-        assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-existent Entry"));
-        assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
-
-        assertEquals("Alan", rd.getEntryString(ns, "First name"));
-        assertEquals("Turing", rd.getEntryString(ns, "Last name"));
-        assertNull(rd.getEntry(ns, "Non-existent Entry"));
-        assertNull(rd.getEntry(ns, "Entry not even requested"));
-
-        store.deleteCredentialByName("test");
-    }
-
-    @Test
-    public void multipleNamespaces() throws IdentityCredentialException, CborException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        store.deleteCredentialByName("test");
-        Collection<X509Certificate> certChain = createCredentialMultipleNamespaces(
-                store, "test");
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        // Request these in different order than they are stored
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.example.foobar", Arrays.asList("Foo", "Bar", "Non-exist"));
-        entriesToRequest.put("org.example.barfoo", Arrays.asList("Bar", "Non-exist", "Foo"));
-        entriesToRequest.put("org.example.foofoo", Arrays.asList("Bar", "Foo", "Non-exist"));
-
-        ResultData rd = credential.getEntries(
-                null,
-                entriesToRequest,
-                null);
-
-        // We should get the same number of namespaces back, as we requested - even for namespaces
-        // that do not exist in the credential.
-        //
-        // Additionally, each namespace should have exactly the items requested, in the same order.
-        Collection<String> resultNamespaces = rd.getNamespaces();
-        assertEquals(resultNamespaces.size(), 3);
-
-
-        // First requested namespace - org.example.foobar
-        String ns = "org.example.foobar";
-        assertArrayEquals(new String[]{"Foo", "Bar", "Non-exist"}, rd.getEntryNames(ns).toArray());
-        assertArrayEquals(new String[]{"Foo", "Bar"}, rd.getRetrievedEntryNames(ns).toArray());
-
-        assertEquals(STATUS_OK, rd.getStatus(ns, "Foo"));
-        assertEquals(STATUS_OK, rd.getStatus(ns, "Bar"));
-        assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
-        assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
-
-        assertEquals("Bar", rd.getEntryString(ns, "Foo"));
-        assertEquals("Foo", rd.getEntryString(ns, "Bar"));
-        assertNull(rd.getEntry(ns, "Non-exist"));
-        assertNull(rd.getEntry(ns, "Entry not even requested"));
-
-        // Second requested namespace - org.example.barfoo
-        ns = "org.example.barfoo";
-        assertArrayEquals(new String[]{"Bar", "Non-exist", "Foo"}, rd.getEntryNames(ns).toArray());
-        assertArrayEquals(new String[]{"Bar", "Foo"}, rd.getRetrievedEntryNames(ns).toArray());
-
-        assertEquals(STATUS_OK, rd.getStatus(ns, "Foo"));
-        assertEquals(STATUS_OK, rd.getStatus(ns, "Bar"));
-        assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
-        assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
-
-        assertEquals("Bar", rd.getEntryString(ns, "Foo"));
-        assertEquals("Foo", rd.getEntryString(ns, "Bar"));
-        assertNull(rd.getEntry(ns, "Non-exist"));
-        assertNull(rd.getEntry(ns, "Entry not even requested"));
-
-        // Third requested namespace - org.example.foofoo
-        ns = "org.example.foofoo";
-        assertArrayEquals(new String[]{"Bar", "Foo", "Non-exist"}, rd.getEntryNames(ns).toArray());
-        assertEquals(0, rd.getRetrievedEntryNames(ns).size());
-        assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Foo"));
-        assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Bar"));
-        assertEquals(STATUS_NO_SUCH_ENTRY, rd.getStatus(ns, "Non-exist"));
-        assertEquals(STATUS_NOT_REQUESTED, rd.getStatus(ns, "Entry not even requested"));
-
-        // Now check the returned CBOR ... note how it only has entries _and_ namespaces
-        // for data that was returned.
-        //
-        // Importantly, this is unlike the returned ResultData which mirrors one to one the passed
-        // in Map<String,Collection<String>> structure, _including_ ensuring the order is the same
-        // ... (which we - painfully - test for just above.)
-        byte[] resultCbor = rd.getAuthenticatedData();
-        String pretty = Util.cborPrettyPrint(Util.canonicalizeCbor(resultCbor));
-        assertEquals("{\n"
-                + "  'org.example.barfoo' : {\n"
-                + "    'Bar' : 'Foo',\n"
-                + "    'Foo' : 'Bar'\n"
-                + "  },\n"
-                + "  'org.example.foobar' : {\n"
-                + "    'Bar' : 'Foo',\n"
-                + "    'Foo' : 'Bar'\n"
-                + "  }\n"
-                + "}", pretty);
-
-        store.deleteCredentialByName("test");
-    }
-
-    @Test
-    public void testUpdateCredential()
-            throws IdentityCredentialException, CborException,
-            NoSuchAlgorithmException {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-        assumeTrue(store.getCapabilities().isUpdateSupported());
-
-        // Create the credential...
-        //
-        String credentialName = "test";
-        String exampleDocType = "org.example.myDocType";
-        String exampleNs = "org.example.ns";
-        byte[] challenge = {0x01, 0x02};
-        int acpId = 3;
-        store.deleteCredentialByName(credentialName);
-        WritableIdentityCredential wc = store.createCredential(credentialName, exampleDocType);
-        Collection<X509Certificate> certChain = wc.getCredentialKeyCertificateChain(challenge);
-        AccessControlProfile noAuthProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(acpId))
-                        .setUserAuthenticationRequired(false)
-                        .build();
-        Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
-        idsNoAuth.add(new AccessControlProfileId(acpId));
-        PersonalizationData personalizationData =
-                new PersonalizationData.Builder()
-                        .addAccessControlProfile(noAuthProfile)
-                        .putEntry(exampleNs, "first_name", idsNoAuth,
-                                Util.cborEncodeString("John"))
-                        .putEntry(exampleNs, "last_name", idsNoAuth,
-                                Util.cborEncodeString("Smith"))
-                        .build();
-        byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
-        byte[] proofOfProvisioning =
-                Util.coseSign1GetData(Util.cborDecode(proofOfProvisioningSignature));
-        byte[] proofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(
-                proofOfProvisioning);
-        String pretty = Util.cborPrettyPrint(proofOfProvisioning);
-        assertEquals("[\n"
-                + "  'ProofOfProvisioning',\n"
-                + "  '" + exampleDocType + "',\n"
-                + "  [\n"
-                + "    {\n"
-                + "      'id' : " + acpId + "\n"
-                + "    }\n"
-                + "  ],\n"
-                + "  {\n"
-                + "    '" + exampleNs + "' : [\n"
-                + "      {\n"
-                + "        'name' : 'first_name',\n"
-                + "        'value' : 'John',\n"
-                + "        'accessControlProfiles' : [" + acpId + "]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'last_name',\n"
-                + "        'value' : 'Smith',\n"
-                + "        'accessControlProfiles' : [" + acpId + "]\n"
-                + "      }\n"
-                + "    ]\n"
-                + "  },\n"
-                + "  false\n"
-                + "]", pretty);
-        assertTrue(Util.coseSign1CheckSignature(
-                Util.cborDecode(proofOfProvisioningSignature),
-                new byte[0], // Additional data
-                certChain.iterator().next().getPublicKey()));
-
-        IdentityCredential credential = store.getCredentialByName("test",
-                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-
-        // Configure to use 3 auth keys and endorse all of them
-        credential.setAvailableAuthenticationKeys(3, 5);
-        Collection<X509Certificate> certificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(3, certificates.size());
-        for (X509Certificate cert : certificates) {
-            credential.storeStaticAuthenticationData(cert, new byte[]{1, 2});
-            // Check each cert has the correct ProofOfProvisioning SHA-256 in the
-            // ProofOfBinding CBOR stored at OID 1.3.6.1.4.1.11129.2.1.26
-            byte[] popSha256FromCert = Util.getPopSha256FromAuthKeyCert(cert);
-            assertArrayEquals(popSha256FromCert, proofOfProvisioningSha256);
-        }
-        assertEquals(0, credential.getAuthKeysNeedingCertification().size());
-
-        // Update the credential
-        AccessControlProfile updNoAuthProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(31))
-                        .setUserAuthenticationRequired(false)
-                        .build();
-        Collection<AccessControlProfileId> updIds = new ArrayList<AccessControlProfileId>();
-        updIds.add(new AccessControlProfileId(31));
-        String updNs = "org.iso.other_ns";
-        PersonalizationData updPd =
-                new PersonalizationData.Builder()
-                        .addAccessControlProfile(updNoAuthProfile)
-                        .putEntry(updNs, "first_name", updIds,
-                                Util.cborEncodeString("Lawrence"))
-                        .putEntry(updNs, "last_name", updIds,
-                                Util.cborEncodeString("Waterhouse"))
-                        .build();
-
-        byte[] updProofOfProvisioningSignature = credential.update(updPd);
-
-        // Check the ProofOfProvisioning for the updated data (contents _and_ signature)
-        byte[] updProofOfProvisioning =
-                Util.coseSign1GetData(Util.cborDecode(updProofOfProvisioningSignature));
-        byte[] updProofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(
-                updProofOfProvisioning);
-        pretty = Util.cborPrettyPrint(updProofOfProvisioning);
-        assertEquals("[\n"
-                + "  'ProofOfProvisioning',\n"
-                + "  '" + exampleDocType + "',\n"
-                + "  [\n"
-                + "    {\n"
-                + "      'id' : 31\n"
-                + "    }\n"
-                + "  ],\n"
-                + "  {\n"
-                + "    'org.iso.other_ns' : [\n"
-                + "      {\n"
-                + "        'name' : 'first_name',\n"
-                + "        'value' : 'Lawrence',\n"
-                + "        'accessControlProfiles' : [31]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'last_name',\n"
-                + "        'value' : 'Waterhouse',\n"
-                + "        'accessControlProfiles' : [31]\n"
-                + "      }\n"
-                + "    ]\n"
-                + "  },\n"
-                + "  false\n"
-                + "]", pretty);
-        assertTrue(Util.coseSign1CheckSignature(
-                Util.cborDecode(updProofOfProvisioningSignature),
-                new byte[0], // Additional data
-                certChain.iterator().next().getPublicKey()));
-        // Check the returned CredentialKey cert chain from the now updated
-        // IdentityCredential matches the original certificate chain.
-        //
-        Collection<X509Certificate> readBackCertChain =
-                credential.getCredentialKeyCertificateChain();
-        assertEquals(certChain.size(), readBackCertChain.size());
-        Iterator<X509Certificate> it = readBackCertChain.iterator();
-        for (X509Certificate expectedCert : certChain) {
-            X509Certificate readBackCert = it.next();
-            assertEquals(expectedCert, readBackCert);
-        }
-
-        // Check that the credential is still configured to use 3 auth keys and
-        // that they all need replacement... then check and endorse the
-        // replacements
-        Collection<X509Certificate> updCertificates = credential.getAuthKeysNeedingCertification();
-        assertEquals(3, updCertificates.size());
-        for (X509Certificate cert : updCertificates) {
-            credential.storeStaticAuthenticationData(cert, new byte[]{1, 2});
-            // Check each cert has the correct - *updated* - ProofOfProvisioning SHA-256 in the
-            // ProofOfBinding CBOR stored at OID 1.3.6.1.4.1.11129.2.1.25
-            byte[] popSha256FromCert = Util.getPopSha256FromAuthKeyCert(cert);
-            assertArrayEquals(popSha256FromCert, updProofOfProvisioningSha256);
-        }
-        assertEquals(0, credential.getAuthKeysNeedingCertification().size());
-
-        // Check we can read back the updated data and it matches what we
-        // updated it to.
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put(updNs,
-                Arrays.asList("first_name",
-                        "last_name"));
-        ResultData rd = credential.getEntries(
-                Util.createItemsRequest(entriesToRequest, null),
-                entriesToRequest,
-                null);
-
-        Collection<String> resultNamespaces = rd.getNamespaces();
-        assertEquals(resultNamespaces.size(), 1);
-        assertEquals(updNs, resultNamespaces.iterator().next());
-        assertEquals(2, rd.getEntryNames(updNs).size());
-
-        assertEquals("Lawrence", rd.getEntryString(updNs, "first_name"));
-        assertEquals("Waterhouse", rd.getEntryString(updNs, "last_name"));
-
-        store.deleteCredentialByName("test");
-    }
-}
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/ReaderAuthTest.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/ReaderAuthTest.java
deleted file mode 100644
index 63a65fe..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/ReaderAuthTest.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static androidx.security.identity.IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256;
-import static androidx.security.identity.ResultData.STATUS_NOT_IN_REQUEST_MESSAGE;
-import static androidx.security.identity.ResultData.STATUS_OK;
-import static androidx.security.identity.ResultData.STATUS_READER_AUTHENTICATION_FAILED;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborException;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class ReaderAuthTest {
-    private static final String TAG = "ReaderAuthTest";
-
-
-    static KeyPair createReaderKey(String readerKeyAlias, boolean createCaKey)
-            throws InvalidAlgorithmParameterException, NoSuchProviderException,
-            NoSuchAlgorithmException {
-        KeyPairGenerator kpg = KeyPairGenerator.getInstance(
-                KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
-        KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                readerKeyAlias,
-                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
-        kpg.initialize(builder.build());
-        return kpg.generateKeyPair();
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void readerAuth()
-            throws IdentityCredentialException, CborException, InvalidAlgorithmParameterException,
-            KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,
-            NoSuchProviderException, InvalidKeyException, SignatureException {
-
-        // We create two reader keys - 'A' and 'B' - and then generate certificates for each of
-        // them, signed by a third key 'C'. We then provision a document with four elements where
-        // each element is configured to be accessible only by 'A', 'B', ('A' or 'B'), and 'C'
-        // respectively. The names of each element reflect this:
-        //
-        //  - "Accessible by A"
-        //  - "Accessible by B"
-        //  - "Accessible by A or B"
-        //  - "Accessible by C"
-        //
-        // We then try reading from the credential in the following cases
-        //
-        //  - Request signed by A and presenting certChain {certA}
-        //    - this should return the following data elements:
-        //      - "Accessible by A"
-        //      - "Accessible by A or B"
-        //
-        //  - Request signed by A and presenting certChain {certA_SignedBy_certC, certC}
-        //    - this should return the following data elements:
-        //      - "Accessible by A"
-        //      - "Accessible by A or B"
-        //      - "Accessible by C"
-        //
-        //  - Request signed by B and presenting certChain {certB}
-        //    - this should return the following data elements:
-        //      - "Accessible by B"
-        //      - "Accessible by A or B"
-        //
-        //  - Request signed by B and presenting certChain {certB_SignedBy_certC, certC}
-        //    - this should return the following data elements:
-        //      - "Accessible by B"
-        //      - "Accessible by A or B"
-        //      - "Accessible by C"
-        //
-        //  - Reader presenting an invalid certificate chain
-        //
-        // We test all this in the following.
-        //
-
-
-        // Generate keys and certificate chains.
-        KeyPair readerKeyPairA = createReaderKey("readerKeyA", false);
-        KeyPair readerKeyPairB = createReaderKey("readerKeyB", false);
-        KeyPair readerKeyPairC = createReaderKey("readerKeyC", true);
-
-        KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-        ks.load(null);
-        X509Certificate certA = (X509Certificate) ks.getCertificate("readerKeyA");
-        X509Certificate certB = (X509Certificate) ks.getCertificate("readerKeyB");
-        X509Certificate certA_SignedBy_certC = Util.signPublicKeyWithPrivateKey("readerKeyA",
-                "readerKeyC");
-        X509Certificate certB_SignedBy_certC = Util.signPublicKeyWithPrivateKey("readerKeyB",
-                "readerKeyC");
-        X509Certificate certC = (X509Certificate) ks.getCertificate("readerKeyC");
-
-        Collection<X509Certificate> certChainForA = new ArrayList<>();
-        certChainForA.add(certA);
-        Collection<X509Certificate> certChainForAwithC = new ArrayList<>();
-        certChainForAwithC.add(certA_SignedBy_certC);
-        certChainForAwithC.add(certC);
-
-        Collection<X509Certificate> certChainForB = new ArrayList<>();
-        certChainForB.add(certB);
-        Collection<X509Certificate> certChainForBwithC = new ArrayList<>();
-        certChainForBwithC.add(certB_SignedBy_certC);
-        certChainForBwithC.add(certC);
-
-        // Provision the credential.
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = Util.getIdentityCredentialStore(appContext);
-
-        String credentialName = "readerAuthTestCredential";
-        store.deleteCredentialByName(credentialName);
-        WritableIdentityCredential wc = store.createCredential(credentialName,
-                "org.iso.18013-5.2019.mdl");
-
-        // Profile 0 (reader A authentication)
-        AccessControlProfile readerAProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(0))
-                        .setReaderCertificate(certA)
-                        .setUserAuthenticationRequired(false)
-                        .build();
-        // Profile 1 (reader B authentication)
-        AccessControlProfile readerBProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(1))
-                        .setReaderCertificate(certB)
-                        .setUserAuthenticationRequired(false)
-                        .build();
-        // Profile 2 (reader C authentication)
-        AccessControlProfile readerCProfile =
-                new AccessControlProfile.Builder(new AccessControlProfileId(2))
-                        .setReaderCertificate(certC)
-                        .setUserAuthenticationRequired(false)
-                        .build();
-        Collection<AccessControlProfileId> idsReaderAuthA =
-                new ArrayList<AccessControlProfileId>();
-        idsReaderAuthA.add(new AccessControlProfileId(0));
-        Collection<AccessControlProfileId> idsReaderAuthB =
-                new ArrayList<AccessControlProfileId>();
-        idsReaderAuthB.add(new AccessControlProfileId(1));
-        Collection<AccessControlProfileId> idsReaderAuthAorB =
-                new ArrayList<AccessControlProfileId>();
-        idsReaderAuthAorB.add(new AccessControlProfileId(0));
-        idsReaderAuthAorB.add(new AccessControlProfileId(1));
-        Collection<AccessControlProfileId> idsReaderAuthC =
-                new ArrayList<AccessControlProfileId>();
-        idsReaderAuthC.add(new AccessControlProfileId(2));
-        String mdlNs = "org.iso.18013-5.2019";
-        PersonalizationData personalizationData =
-                new PersonalizationData.Builder()
-                        .addAccessControlProfile(readerAProfile)
-                        .addAccessControlProfile(readerBProfile)
-                        .addAccessControlProfile(readerCProfile)
-                        .putEntry(mdlNs, "Accessible to A", idsReaderAuthA,
-                                Util.cborEncodeString("foo"))
-                        .putEntry(mdlNs, "Accessible to B", idsReaderAuthB,
-                                Util.cborEncodeString("bar"))
-                        .putEntry(mdlNs, "Accessible to A or B", idsReaderAuthAorB,
-                                Util.cborEncodeString("baz"))
-                        .putEntry(mdlNs, "Accessible to C", idsReaderAuthC,
-                                Util.cborEncodeString("bat"))
-                        .build();
-        byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
-        byte[] proofOfProvisioning =
-                Util.coseSign1GetData(Util.cborDecode(proofOfProvisioningSignature));
-
-        String pretty = Util.cborPrettyPrint(proofOfProvisioning);
-        pretty = Util.replaceLine(pretty, 6, "      'readerCertificate' : [] // Removed");
-        pretty = Util.replaceLine(pretty, 10, "      'readerCertificate' : [] // Removed");
-        pretty = Util.replaceLine(pretty, 14, "      'readerCertificate' : [] // Removed");
-        assertEquals("[\n"
-                + "  'ProofOfProvisioning',\n"
-                + "  'org.iso.18013-5.2019.mdl',\n"
-                + "  [\n"
-                + "    {\n"
-                + "      'id' : 0,\n"
-                + "      'readerCertificate' : [] // Removed\n"
-                + "    },\n"
-                + "    {\n"
-                + "      'id' : 1,\n"
-                + "      'readerCertificate' : [] // Removed\n"
-                + "    },\n"
-                + "    {\n"
-                + "      'id' : 2,\n"
-                + "      'readerCertificate' : [] // Removed\n"
-                + "    }\n"
-                + "  ],\n"
-                + "  {\n"
-                + "    'org.iso.18013-5.2019' : [\n"
-                + "      {\n"
-                + "        'name' : 'Accessible to A',\n"
-                + "        'value' : 'foo',\n"
-                + "        'accessControlProfiles' : [0]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Accessible to B',\n"
-                + "        'value' : 'bar',\n"
-                + "        'accessControlProfiles' : [1]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Accessible to A or B',\n"
-                + "        'value' : 'baz',\n"
-                + "        'accessControlProfiles' : [0, 1]\n"
-                + "      },\n"
-                + "      {\n"
-                + "        'name' : 'Accessible to C',\n"
-                + "        'value' : 'bat',\n"
-                + "        'accessControlProfiles' : [2]\n"
-                + "      }\n"
-                + "    ]\n"
-                + "  },\n"
-                + "  false\n"
-                + "]", pretty);
-
-
-        // Get the credential we'll be reading from and provision it with a sufficient number
-        // of dynamic auth keys
-        IdentityCredential credential = store.getCredentialByName(credentialName,
-                CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        assertNotNull(credential);
-        credential.setAvailableAuthenticationKeys(1, 10);
-        Collection<X509Certificate> dynAuthKeyCerts = credential.getAuthKeysNeedingCertification();
-        credential.storeStaticAuthenticationData(dynAuthKeyCerts.iterator().next(), new byte[0]);
-        KeyPair eKeyPair = credential.createEphemeralKeyPair();
-
-        Collection<String> entryNames;
-        Collection<String> resultNamespaces;
-        ResultData rd;
-
-        // Create the request message which will be signed by the reader.
-        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
-        entriesToRequest.put("org.iso.18013-5.2019",
-                Arrays.asList("Accessible to A",
-                        "Accessible to B",
-                        "Accessible to A or B",
-                        "Accessible to C"));
-        byte[] requestMessage = Util.createItemsRequest(entriesToRequest,
-                "org.iso.18013-5.2019.mdl");
-
-        // Signing with A and presenting cert chain {certA}.
-        //
-        rd = retrieveForReader(credential, eKeyPair, readerKeyPairA, certChainForA, requestMessage,
-                entriesToRequest);
-        resultNamespaces = rd.getNamespaces();
-        assertEquals(1, resultNamespaces.size());
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        entryNames = rd.getRetrievedEntryNames("org.iso.18013-5.2019");
-        assertEquals(2, entryNames.size());
-        assertTrue(entryNames.contains("Accessible to A"));
-        assertTrue(entryNames.contains("Accessible to A or B"));
-        assertEquals(STATUS_OK, rd.getStatus("org.iso.18013-5.2019", "Accessible to A"));
-        assertEquals(STATUS_OK, rd.getStatus("org.iso.18013-5.2019", "Accessible to A or B"));
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED, rd.getStatus("org.iso.18013-5.2019",
-                "Accessible to B"));
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED, rd.getStatus("org.iso.18013-5.2019",
-                "Accessible to C"));
-
-        // Signing with A and presenting cert chain {certA} and providing a requestMessage
-        // that doesn't request "Accessible to A or B" in the signed |requestMessage| but
-        // includes it in |entriesToRequest| (which is not signed)... should result
-        // in requesting "Accessible to A or B" failing with NOT_IN_REQUEST_MESSAGE
-        // and "Accessible to B" and "Accessible to C" failing with
-        // READER_AUTHENTICATION_FAILED.
-        //
-        Map<String, Collection<String>> entriesToRequest2 = new LinkedHashMap<>();
-        entriesToRequest2.put("org.iso.18013-5.2019",
-                Arrays.asList("Accessible to A",
-                        "Accessible to B",
-                        "Accessible to C"));
-        byte[] requestMessage2 = Util.createItemsRequest(entriesToRequest2,
-                "org.iso.18013-5.2019.mdl");
-        credential = store.getCredentialByName(credentialName,
-                CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        eKeyPair = credential.createEphemeralKeyPair();
-        rd = retrieveForReader(credential, eKeyPair, readerKeyPairA, certChainForA,
-                requestMessage2, entriesToRequest);
-        resultNamespaces = rd.getNamespaces();
-        assertEquals(1, resultNamespaces.size());
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        entryNames = rd.getRetrievedEntryNames("org.iso.18013-5.2019");
-        assertEquals(1, entryNames.size());
-        assertTrue(entryNames.contains("Accessible to A"));
-        assertEquals(STATUS_OK, rd.getStatus("org.iso.18013-5.2019", "Accessible to A"));
-        assertEquals(STATUS_NOT_IN_REQUEST_MESSAGE,
-                rd.getStatus("org.iso.18013-5.2019", "Accessible to A or B"));
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED,
-                rd.getStatus("org.iso.18013-5.2019", "Accessible to B"));
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED,
-                rd.getStatus("org.iso.18013-5.2019", "Accessible to C"));
-
-        // Signing with A and presenting cert chain {certA_SignedBy_certC, certC}.
-        //
-        credential = store.getCredentialByName(credentialName,
-                CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        eKeyPair = credential.createEphemeralKeyPair();
-        rd = retrieveForReader(credential, eKeyPair, readerKeyPairA, certChainForAwithC,
-                requestMessage, entriesToRequest);
-        resultNamespaces = rd.getNamespaces();
-        assertEquals(1, resultNamespaces.size());
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        entryNames = rd.getRetrievedEntryNames("org.iso.18013-5.2019");
-        assertEquals(3, entryNames.size());
-        assertTrue(entryNames.contains("Accessible to A"));
-        assertTrue(entryNames.contains("Accessible to A or B"));
-        assertTrue(entryNames.contains("Accessible to C"));
-
-        // Signing with B and presenting cert chain {certB}.
-        //
-        credential = store.getCredentialByName(credentialName,
-                CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        eKeyPair = credential.createEphemeralKeyPair();
-        rd = retrieveForReader(credential, eKeyPair, readerKeyPairB, certChainForB, requestMessage,
-                entriesToRequest);
-        resultNamespaces = rd.getNamespaces();
-        assertEquals(1, resultNamespaces.size());
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        entryNames = rd.getRetrievedEntryNames("org.iso.18013-5.2019");
-        assertEquals(2, entryNames.size());
-        assertTrue(entryNames.contains("Accessible to B"));
-        assertTrue(entryNames.contains("Accessible to A or B"));
-
-        // Signing with B and presenting cert chain {certB_SignedBy_certC, certC}.
-        //
-        credential = store.getCredentialByName(credentialName,
-                CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        eKeyPair = credential.createEphemeralKeyPair();
-        rd = retrieveForReader(credential, eKeyPair, readerKeyPairB, certChainForBwithC,
-                requestMessage, entriesToRequest);
-        resultNamespaces = rd.getNamespaces();
-        assertEquals(1, resultNamespaces.size());
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        entryNames = rd.getRetrievedEntryNames("org.iso.18013-5.2019");
-        assertEquals(3, entryNames.size());
-        assertTrue(entryNames.contains("Accessible to B"));
-        assertTrue(entryNames.contains("Accessible to A or B"));
-        assertTrue(entryNames.contains("Accessible to C"));
-
-        // Signing with B and presenting invalid cert chain {certB, certC} should fail
-        // because certB is not signed by certC.
-        try {
-            credential = store.getCredentialByName(credentialName,
-                    CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-            eKeyPair = credential.createEphemeralKeyPair();
-            Collection<X509Certificate> certChain = new ArrayList<>();
-            certChain.add(certB);
-            certChain.add(certC);
-            retrieveForReader(credential, eKeyPair, readerKeyPairB, certChain, requestMessage,
-                    entriesToRequest);
-            assertTrue(false);
-        } catch (InvalidReaderSignatureException e) {
-            // Do nothing, this is the expected exception...
-        }
-
-        // No request message should result in returning zero data elements - they're
-        // all protected by reader authentication.
-        //
-        credential = store.getCredentialByName(credentialName,
-                CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
-        eKeyPair = credential.createEphemeralKeyPair();
-        rd = credential.getEntries(
-                null,
-                entriesToRequest,
-                null);
-        resultNamespaces = rd.getNamespaces();
-        assertEquals(1, resultNamespaces.size());
-        assertEquals("org.iso.18013-5.2019", resultNamespaces.iterator().next());
-        entryNames = rd.getRetrievedEntryNames("org.iso.18013-5.2019");
-        assertEquals(0, entryNames.size());
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED,
-                rd.getStatus("org.iso.18013-5.2019", "Accessible to A"));
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED,
-                rd.getStatus("org.iso.18013-5.2019", "Accessible to A or B"));
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED,
-                rd.getStatus("org.iso.18013-5.2019", "Accessible to B"));
-        assertEquals(STATUS_READER_AUTHENTICATION_FAILED,
-                rd.getStatus("org.iso.18013-5.2019", "Accessible to C"));
-    }
-
-    private ResultData retrieveForReader(
-            IdentityCredential credential,
-            KeyPair ephemeralKeyPair,
-            KeyPair readerKeyToSignWith,
-            Collection<X509Certificate> readerCertificateChainToPresent,
-            byte[] requestMessage,
-            Map<String, Collection<String>> entriesToRequest)
-            throws IdentityCredentialException, CborException, InvalidAlgorithmParameterException,
-            KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,
-            NoSuchProviderException, InvalidKeyException, SignatureException {
-
-        byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
-
-        // Finally, create the structure that the reader signs, and sign it.
-
-        byte[] readerAuthentication = Util.cborEncode(new CborBuilder()
-                .addArray()
-                .add("ReaderAuthentication")
-                .add(Util.cborDecode(sessionTranscript))
-                .add(Util.cborBuildTaggedByteString(requestMessage))
-                .end()
-                .build().get(0));
-        byte[] readerAuthenticationBytes =
-                Util.cborEncode(Util.cborBuildTaggedByteString((readerAuthentication)));
-        byte[] readerSignature = Util.cborEncode(
-                Util.coseSign1Sign(readerKeyToSignWith.getPrivate(),
-                null, // payload
-                readerAuthenticationBytes, // detached content
-                readerCertificateChainToPresent)); // certificate-chain
-
-        // Now issue the request.
-        credential.setSessionTranscript(sessionTranscript);
-        ResultData result = credential.getEntries(
-                requestMessage,
-                entriesToRequest,
-                readerSignature);
-        return result;
-    }
-
-}
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/UtilUnitTests.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/UtilUnitTests.java
deleted file mode 100644
index f58cf5c..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/UtilUnitTests.java
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.icu.util.Calendar;
-import android.icu.util.GregorianCalendar;
-import android.icu.util.TimeZone;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.cert.X509Certificate;
-import java.util.LinkedList;
-
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.builder.ArrayBuilder;
-import co.nstant.in.cbor.model.ByteString;
-import co.nstant.in.cbor.model.DataItem;
-import co.nstant.in.cbor.model.DoublePrecisionFloat;
-import co.nstant.in.cbor.model.HalfPrecisionFloat;
-import co.nstant.in.cbor.model.NegativeInteger;
-import co.nstant.in.cbor.model.SimpleValue;
-import co.nstant.in.cbor.model.SimpleValueType;
-import co.nstant.in.cbor.model.SinglePrecisionFloat;
-import co.nstant.in.cbor.model.UnicodeString;
-import co.nstant.in.cbor.model.UnsignedInteger;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class UtilUnitTests {
-    @Test
-    public void prettyPrintMultipleCompleteTypes() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .add("text")                // add string
-                .add(1234)                  // add integer
-                .add(new byte[]{0x10})   // add byte array
-                .addArray()                 // add array
-                .add(1)
-                .add("text")
-                .end()
-                .build());
-        assertEquals("'text',\n"
-                + "1234,\n"
-                + "[0x10],\n"
-                + "[1, 'text']", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintString() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new UnicodeString("foobar"));
-        assertEquals("'foobar'", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintBytestring() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new ByteString(new byte[]{1, 2, 33, (byte) 254}));
-        assertEquals("[0x01, 0x02, 0x21, 0xfe]", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintUnsignedInteger() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new UnsignedInteger(42));
-        assertEquals("42", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintNegativeInteger() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new NegativeInteger(-42));
-        assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintDouble() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new DoublePrecisionFloat(1.1));
-        assertEquals("1.1", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new DoublePrecisionFloat(-42.0000000001));
-        assertEquals("-42.0000000001", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new DoublePrecisionFloat(-5));
-        assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintFloat() throws CborException {
-        ByteArrayOutputStream baos;
-
-        // TODO: These two tests yield different results on different devices, disable for now
-        /*
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SinglePrecisionFloat(1.1f));
-        assertEquals("1.100000023841858", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SinglePrecisionFloat(-42.0001f));
-        assertEquals("-42.000099182128906", Util.cborPrettyPrint(baos.toByteArray()));
-        */
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SinglePrecisionFloat(-5f));
-        assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintHalfFloat() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new HalfPrecisionFloat(1.1f));
-        assertEquals("1.099609375", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new HalfPrecisionFloat(-42.0001f));
-        assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new HalfPrecisionFloat(-5f));
-        assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintFalse() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.FALSE));
-        assertEquals("false", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintTrue() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.TRUE));
-        assertEquals("true", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintNull() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.NULL));
-        assertEquals("null", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintUndefined() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.UNDEFINED));
-        assertEquals("undefined", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintTag() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addTag(0)
-                .add("ABC")
-                .build());
-        byte[] data = baos.toByteArray();
-        assertEquals("tag 0 'ABC'", Util.cborPrettyPrint(data));
-    }
-
-    @Test
-    public void prettyPrintArrayNoCompounds() throws CborException {
-        // If an array has no compound elements, no newlines are used.
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addArray()                 // add array
-                .add(1)
-                .add("text")
-                .add(new ByteString(new byte[]{1, 2, 3}))
-                .end()
-                .build());
-        assertEquals("[1, 'text', [0x01, 0x02, 0x03]]", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintArray() throws CborException {
-        // This array contains a compound value so will use newlines
-        CborBuilder array = new CborBuilder();
-        ArrayBuilder<CborBuilder> arrayBuilder = array.addArray();
-        arrayBuilder.add(2);
-        arrayBuilder.add(3);
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addArray()                 // add array
-                .add(1)
-                .add("text")
-                .add(new ByteString(new byte[]{1, 2, 3}))
-                .add(array.build().get(0))
-                .end()
-                .build());
-        assertEquals("[\n"
-                + "  1,\n"
-                + "  'text',\n"
-                + "  [0x01, 0x02, 0x03],\n"
-                + "  [2, 3]\n"
-                + "]", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintMap() throws CborException {
-        // If an array has no compound elements, no newlines are used.
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addMap()
-                .put("Foo", 42)
-                .put("Bar", "baz")
-                .put(43, 44)
-                .put(new UnicodeString("bstr"), new ByteString(new byte[]{1, 2, 3}))
-                .put(new ByteString(new byte[]{1, 2, 3}), new UnicodeString("other way"))
-                .end()
-                .build());
-        assertEquals("{\n"
-                + "  43 : 44,\n"
-                + "  [0x01, 0x02, 0x03] : 'other way',\n"
-                + "  'Bar' : 'baz',\n"
-                + "  'Foo' : 42,\n"
-                + "  'bstr' : [0x01, 0x02, 0x03]\n"
-                + "}", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void cborEncodeDecode() {
-        // TODO: add better coverage and check specific encoding etc.
-        assertEquals(42, Util.cborDecodeLong(Util.cborEncodeNumber(42)));
-        assertEquals(123456, Util.cborDecodeLong(Util.cborEncodeNumber(123456)));
-        assertFalse(Util.cborDecodeBoolean(Util.cborEncodeBoolean(false)));
-        assertTrue(Util.cborDecodeBoolean(Util.cborEncodeBoolean(true)));
-    }
-
-    @Test
-    public void cborEncodeDecodeCalendar() throws CborException {
-        GregorianCalendar c;
-        byte[] data;
-
-        c = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
-        c.clear();
-        c.set(2019, Calendar.JULY, 8, 11, 51, 42);
-        data = Util.cborEncodeDateTime(c);
-        assertEquals("tag 0 '2019-07-08T11:51:42Z'", Util.cborPrettyPrint(data));
-        assertEquals("tag 0 '2019-07-08T11:51:42Z'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-        assertEquals(0, c.compareTo(Util.cborDecodeDateTime(data)));
-
-        c = new GregorianCalendar(TimeZone.getTimeZone("GMT-04:00"));
-        c.clear();
-        c.set(2019, Calendar.JULY, 8, 11, 51, 42);
-        data = Util.cborEncodeDateTime(c);
-        assertEquals("tag 0 '2019-07-08T11:51:42-04:00'", Util.cborPrettyPrint(data));
-        assertEquals("tag 0 '2019-07-08T11:51:42-04:00'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-        assertEquals(0, c.compareTo(Util.cborDecodeDateTime(data)));
-
-        c = new GregorianCalendar(TimeZone.getTimeZone("GMT-08:00"));
-        c.clear();
-        c.set(2019, Calendar.JULY, 8, 11, 51, 42);
-        data = Util.cborEncodeDateTime(c);
-        assertEquals("tag 0 '2019-07-08T11:51:42-08:00'", Util.cborPrettyPrint(data));
-        assertEquals("tag 0 '2019-07-08T11:51:42-08:00'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-        assertEquals(0, c.compareTo(Util.cborDecodeDateTime(data)));
-
-        c = new GregorianCalendar(TimeZone.getTimeZone("GMT+04:30"));
-        c.clear();
-        c.set(2019, Calendar.JULY, 8, 11, 51, 42);
-        data = Util.cborEncodeDateTime(c);
-        assertEquals("tag 0 '2019-07-08T11:51:42+04:30'", Util.cborPrettyPrint(data));
-        assertEquals("tag 0 '2019-07-08T11:51:42+04:30'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-        assertEquals(0, c.compareTo(Util.cborDecodeDateTime(data)));
-    }
-
-    @Test
-    public void cborCalendarMilliseconds() throws CborException {
-        Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
-        c.clear();
-        c.set(2019, Calendar.JULY, 8, 11, 51, 42);
-        c.set(Calendar.MILLISECOND, 123);
-        byte[] data = Util.cborEncodeDateTime(c);
-        assertEquals("tag 0 '2019-07-08T11:51:42.123Z'", Util.cborPrettyPrint(data));
-        assertEquals("tag 0 '2019-07-08T11:51:42.123Z'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-        assertEquals(0, c.compareTo(Util.cborDecodeDateTime(data)));
-    }
-
-    @Test
-    public void cborCalendarForeign() throws CborException {
-        ByteArrayOutputStream baos;
-        byte[] data;
-
-        // milliseconds, non-standard format
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addTag(0)
-                .add("2019-07-08T11:51:42.25Z")
-                .build());
-        data = baos.toByteArray();
-        assertEquals("tag 0 '2019-07-08T11:51:42.250Z'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-
-        // milliseconds set to 0
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addTag(0)
-                .add("2019-07-08T11:51:42.0Z")
-                .build());
-        data = baos.toByteArray();
-        assertEquals("tag 0 '2019-07-08T11:51:42Z'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-
-        // we only support millisecond-precision
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addTag(0)
-                .add("2019-07-08T11:51:42.9876Z")
-                .build());
-        data = baos.toByteArray();
-        assertEquals("tag 0 '2019-07-08T11:51:42.987Z'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-
-        // milliseconds and timezone
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addTag(0)
-                .add("2019-07-08T11:51:42.26-11:30")
-                .build());
-        data = baos.toByteArray();
-        assertEquals("tag 0 '2019-07-08T11:51:42.260-11:30'",
-                Util.cborPrettyPrint(Util.cborEncodeDateTime(Util.cborDecodeDateTime(data))));
-    }
-
-    private KeyPair coseGenerateKeyPair() throws Exception {
-        KeyPairGenerator kpg = KeyPairGenerator.getInstance(
-                KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
-        KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                "coseTestKeyPair",
-                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
-        kpg.initialize(builder.build());
-        return kpg.generateKeyPair();
-    }
-
-    @Test
-    public void coseSignAndVerify() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[]{0x10, 0x11, 0x12, 0x13};
-        byte[] detachedContent = new byte[]{};
-        DataItem sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(0, Util.coseSign1GetX5Chain(sig).size());
-    }
-
-    @Test
-    public void coseSignAndVerifyDetachedContent() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[]{};
-        byte[] detachedContent = new byte[]{0x20, 0x21, 0x22, 0x23, 0x24};
-        DataItem sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(0, Util.coseSign1GetX5Chain(sig).size());
-    }
-
-    @Test
-    public void coseSignAndVerifySingleCertificate() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[]{};
-        byte[] detachedContent = new byte[]{0x20, 0x21, 0x22, 0x23, 0x24};
-        LinkedList<X509Certificate> certs = new LinkedList<X509Certificate>();
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        DataItem sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(certs, Util.coseSign1GetX5Chain(sig));
-    }
-
-    @Test
-    public void coseSignAndVerifyMultipleCertificates() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[]{};
-        byte[] detachedContent = new byte[]{0x20, 0x21, 0x22, 0x23, 0x24};
-        LinkedList<X509Certificate> certs = new LinkedList<X509Certificate>();
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        DataItem sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(certs, Util.coseSign1GetX5Chain(sig));
-    }
-
-    @Test
-    public void coseMac0() throws Exception {
-        SecretKey secretKey = new SecretKeySpec(new byte[32], "");
-        byte[] data = new byte[]{0x10, 0x11, 0x12, 0x13};
-        byte[] detachedContent = new byte[]{};
-        DataItem mac = Util.coseMac0(secretKey, data, detachedContent);
-        assertEquals("[\n"
-                + "  [0xa1, 0x01, 0x05],\n"
-                + "  {},\n"
-                + "  [0x10, 0x11, 0x12, 0x13],\n"
-                + "  [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
-                + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
-                + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
-                + "]", Util.cborPrettyPrint(mac));
-    }
-
-    @Test
-    public void coseMac0DetachedContent() throws Exception {
-        SecretKey secretKey = new SecretKeySpec(new byte[32], "");
-        byte[] data = new byte[]{};
-        byte[] detachedContent = new byte[]{0x10, 0x11, 0x12, 0x13};
-        DataItem mac = Util.coseMac0(secretKey, data, detachedContent);
-        // Same HMAC as in coseMac0 test, only difference is that payload is null.
-        assertEquals("[\n"
-                + "  [0xa1, 0x01, 0x05],\n"
-                + "  {},\n"
-                + "  null,\n"
-                + "  [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
-                + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
-                + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
-                + "]", Util.cborPrettyPrint(mac));
-    }
-
-    @Test
-    public void replaceLineTest() {
-        assertEquals("foo",
-                Util.replaceLine("Hello World", 0, "foo"));
-        assertEquals("foo\n",
-                Util.replaceLine("Hello World\n", 0, "foo"));
-        assertEquals("Hello World",
-                Util.replaceLine("Hello World", 1, "foo"));
-        assertEquals("Hello World\n",
-                Util.replaceLine("Hello World\n", 1, "foo"));
-        assertEquals("foo\ntwo\nthree",
-                Util.replaceLine("one\ntwo\nthree", 0, "foo"));
-        assertEquals("one\nfoo\nthree",
-                Util.replaceLine("one\ntwo\nthree", 1, "foo"));
-        assertEquals("one\ntwo\nfoo",
-                Util.replaceLine("one\ntwo\nthree", 2, "foo"));
-        assertEquals("one\ntwo\nfoo",
-                Util.replaceLine("one\ntwo\nthree", -1, "foo"));
-        assertEquals("one\ntwo\nthree\nfoo",
-                Util.replaceLine("one\ntwo\nthree\nfour", -1, "foo"));
-        assertEquals("one\ntwo\nfoo\nfour",
-                Util.replaceLine("one\ntwo\nthree\nfour", -2, "foo"));
-    }
-
-}
diff --git a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/X509CertificateSigningTest.java b/security/security-identity-credential/src/androidTest/java/androidx/security/identity/X509CertificateSigningTest.java
deleted file mode 100644
index 09578b2..0000000
--- a/security/security-identity-credential/src/androidTest/java/androidx/security/identity/X509CertificateSigningTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static junit.framework.TestCase.assertNotNull;
-import static junit.framework.TestCase.assertTrue;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.content.Context;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-import android.util.AtomicFile;
-import android.util.Log;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-@SuppressWarnings("deprecation")
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class X509CertificateSigningTest {
-
-    private static final String TAG = "X509CertificateSigningTest";
-
-    @Test
-    public void testSigning() {
-        Context appContext = androidx.test.InstrumentationRegistry.getTargetContext();
-
-        String keyToSignAlias = "testKeyToSign";
-        String keyToSignWithAlias = "testKeyToSignWith";
-
-        KeyStore ks = null;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            ks.deleteEntry(keyToSignAlias);
-            ks.deleteEntry(keyToSignWithAlias);
-            assertFalse(ks.containsAlias(keyToSignAlias));
-            assertFalse(ks.containsAlias(keyToSignWithAlias));
-
-            KeyPairGenerator kpg = KeyPairGenerator.getInstance(
-                    KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
-
-            kpg.initialize(new KeyGenParameterSpec.Builder(
-                    keyToSignAlias,
-                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512).build());
-            KeyPair keyToSignKeyPair = kpg.generateKeyPair();
-
-            kpg.initialize(new KeyGenParameterSpec.Builder(
-                    keyToSignWithAlias,
-                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512).build());
-            KeyPair keyToSignWithKeyPair = kpg.generateKeyPair();
-
-            assertTrue(ks.containsAlias(keyToSignAlias));
-            assertTrue(ks.containsAlias(keyToSignWithAlias));
-
-            X509Certificate cert = Util.signPublicKeyWithPrivateKey(keyToSignAlias,
-                    keyToSignWithAlias);
-            assertNotNull(cert);
-            Log.d(TAG, "Cert:\n--\n" + cert.toString() + "\n--\n");
-
-            String filename = "ic_cert.bin";
-            AtomicFile file = new AtomicFile(appContext.getFileStreamPath(filename));
-            FileOutputStream outputStream = null;
-            try {
-                outputStream = file.startWrite();
-                outputStream.write(cert.getEncoded());
-                outputStream.close();
-                file.finishWrite(outputStream);
-            } catch (IOException e) {
-                if (outputStream != null) {
-                    file.failWrite(outputStream);
-                }
-                e.printStackTrace();
-                assertTrue(false);
-            }
-
-
-            // Check |cert| is for |keyToSignAlias|
-            assertArrayEquals(keyToSignKeyPair.getPublic().getEncoded(),
-                    cert.getPublicKey().getEncoded());
-
-            // Check |cert| was signed by |keyToSignWithAlias|
-            cert.verify(keyToSignWithKeyPair.getPublic());   // Throws if verification fails.
-
-        } catch (IOException
-                | InvalidAlgorithmParameterException
-                | NoSuchAlgorithmException
-                | NoSuchProviderException
-                | KeyStoreException
-                | CertificateException
-                | InvalidKeyException
-                | SignatureException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        //assertTrue(false);
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/AndroidManifest.xml b/security/security-identity-credential/src/main/AndroidManifest.xml
deleted file mode 100644
index 78f1829..0000000
--- a/security/security-identity-credential/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2019 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.
--->
-<manifest>
-</manifest>
\ No newline at end of file
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/AccessControlProfile.java b/security/security-identity-credential/src/main/java/androidx/security/identity/AccessControlProfile.java
deleted file mode 100644
index cf03728..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/AccessControlProfile.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.security.cert.X509Certificate;
-
-/**
- * A class used to specify access controls.
- */
-public class AccessControlProfile {
-    @NonNull AccessControlProfileId mAccessControlProfileId = new AccessControlProfileId(0);
-    @Nullable X509Certificate mReaderCertificate = null;
-    boolean mUserAuthenticationRequired = true;
-    long mUserAuthenticationTimeout = 0;
-
-    AccessControlProfile() {
-    }
-
-    @NonNull AccessControlProfileId getAccessControlProfileId() {
-        return mAccessControlProfileId;
-    }
-
-    long getUserAuthenticationTimeout() {
-        return mUserAuthenticationTimeout;
-    }
-
-    boolean isUserAuthenticationRequired() {
-        return mUserAuthenticationRequired;
-    }
-
-    @Nullable X509Certificate getReaderCertificate() {
-        return mReaderCertificate;
-    }
-
-    /**
-     * A builder for {@link AccessControlProfile}.
-     */
-    public static final class Builder {
-        private AccessControlProfile mProfile;
-
-        /**
-         * Each access control profile has numeric identifier that must be unique within the
-         * context of a Credential and may be used to reference the profile.
-         *
-         * <p>By default, the resulting {@link AccessControlProfile} will require user
-         * authentication with a timeout of zero, thus requiring the holder to authenticate for
-         * every presentation where data elements using this access control profile is used.</p>
-         *
-         * @param accessControlProfileId the access control profile identifier.
-         */
-        public Builder(@NonNull AccessControlProfileId accessControlProfileId) {
-            mProfile = new AccessControlProfile();
-            mProfile.mAccessControlProfileId = accessControlProfileId;
-        }
-
-        /**
-         * Set whether user authentication is required.
-         *
-         * <p>This should be used sparingly since disabling user authentication on just a single
-         * data element can easily create a
-         * <a href="https://en.wikipedia.org/wiki/Relay_attack">Relay Attack</a> if the device
-         * on which the credential is stored is compromised.</p>
-         *
-         * <p>The default behavior of a {@link AccessControlProfile} created from a builder
-         * is to require user authentication.</p>
-         *
-         * @param userAuthenticationRequired Set to true if user authentication is required,
-         *                                   false otherwise.
-         * @return The builder.
-         */
-        public @NonNull Builder setUserAuthenticationRequired(boolean userAuthenticationRequired) {
-            mProfile.mUserAuthenticationRequired = userAuthenticationRequired;
-            return this;
-        }
-
-        /**
-         * Sets the authentication timeout to use.
-         *
-         * <p>The authentication timeout specifies the amount of time, in milliseconds, for which a
-         * user authentication is valid, if user authentication is required (see
-         * {@link #setUserAuthenticationRequired(boolean)}).</p>
-         *
-         * <p>If the timeout is zero, then authentication is always required for each reader
-         * session.</p>
-         *
-         * <p>The default behavior of a {@link AccessControlProfile} created from a builder
-         * is to use a timeout of 0.</p>
-         *
-         * @param userAuthenticationTimeoutMillis the authentication timeout, in milliseconds.
-         * @return The builder.
-         */
-        public @NonNull Builder setUserAuthenticationTimeout(long userAuthenticationTimeoutMillis) {
-            mProfile.mUserAuthenticationTimeout = userAuthenticationTimeoutMillis;
-            return this;
-        }
-
-        /**
-         * Sets the reader certificate to use when checking access control.
-         *
-         * <p>If set, this is checked against the certificate chain presented by reader. The
-         * access check is fulfilled only if the public key from one of the certificates in the
-         * chain, matches the public key in the certificate set by this
-         * method.</p>
-         *
-         * <p>The default behavior of a {@link AccessControlProfile} created from a builder
-         * is to not use reader authentication.</p>
-         *
-         * @param readerCertificate the certificate to use for the access control check.
-         * @return The builder.
-         */
-        public @NonNull Builder setReaderCertificate(@NonNull X509Certificate readerCertificate) {
-            mProfile.mReaderCertificate = readerCertificate;
-            return this;
-        }
-
-        /**
-         * Creates a new {@link AccessControlProfile} from the data supplied to the builder.
-         *
-         * @return The created {@link AccessControlProfile} object.
-         */
-        public @NonNull AccessControlProfile build() {
-            return mProfile;
-        }
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/AccessControlProfileId.java b/security/security-identity-credential/src/main/java/androidx/security/identity/AccessControlProfileId.java
deleted file mode 100644
index 86a4740..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/AccessControlProfileId.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-/**
- * A class used to wrap an access control profile identifiers.
- */
-public class AccessControlProfileId {
-    private int mId = 0;
-
-    /**
-     * Constructs a new object holding a numerical identifier.
-     *
-     * @param id the identifier.
-     */
-    public AccessControlProfileId(int id) {
-        this.mId = id;
-    }
-
-    /**
-     * Gets the numerical identifier wrapped by this object.
-     *
-     * @return the identifier.
-     */
-    public int getId() {
-        return this.mId;
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/AlreadyPersonalizedException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/AlreadyPersonalizedException.java
deleted file mode 100644
index 4cbecdb..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/AlreadyPersonalizedException.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Thrown if trying to create a credential which already exists.
- */
-public class AlreadyPersonalizedException extends IdentityCredentialException {
-    /**
-     * Constructs a new {@link AlreadyPersonalizedException} exception.
-     *
-     * @param message the detail message.
-     */
-    public AlreadyPersonalizedException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link AlreadyPersonalizedException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public AlreadyPersonalizedException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/CipherSuiteNotSupportedException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/CipherSuiteNotSupportedException.java
deleted file mode 100644
index 5b48822..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/CipherSuiteNotSupportedException.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Thrown if trying to use a cipher suite which isn't supported.
- */
-public class CipherSuiteNotSupportedException extends IdentityCredentialException {
-    /**
-     * Constructs a new {@link CipherSuiteNotSupportedException} exception.
-     *
-     * @param message the detail message.
-     */
-    public CipherSuiteNotSupportedException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link CipherSuiteNotSupportedException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public CipherSuiteNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/CredentialData.java b/security/security-identity-credential/src/main/java/androidx/security/identity/CredentialData.java
deleted file mode 100644
index a53f4c3..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/CredentialData.java
+++ /dev/null
@@ -1,1258 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.content.Context;
-import android.icu.util.Calendar;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Pair;
-
-import org.jspecify.annotations.NonNull;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.nio.ByteBuffer;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.UnrecoverableEntryException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.AbstractList;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborDecoder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.builder.ArrayBuilder;
-import co.nstant.in.cbor.builder.MapBuilder;
-import co.nstant.in.cbor.model.Array;
-import co.nstant.in.cbor.model.ByteString;
-import co.nstant.in.cbor.model.DataItem;
-import co.nstant.in.cbor.model.Number;
-import co.nstant.in.cbor.model.UnicodeString;
-import co.nstant.in.cbor.model.UnsignedInteger;
-
-/**
- * Internal routines used to manage credential data.
- */
-class CredentialData {
-    private static final String TAG = "CredentialData";
-
-    private Context mContext;
-    private String mCredentialName;
-
-    private String mDocType = "";
-    private String mCredentialKeyAlias = "";
-    private Collection<X509Certificate> mCertificateChain = null;
-    private byte[] mProofOfProvisioningSha256 = null;
-    private AbstractList<AccessControlProfile> mAccessControlProfiles = new ArrayList<>();
-    private AbstractMap<Integer, AccessControlProfile> mProfileIdToAcpMap = new HashMap<>();
-    private AbstractList<PersonalizationData.NamespaceData> mNamespaceDatas = new ArrayList<>();
-
-    private int mAuthKeyCount = 0;
-    private int mAuthMaxUsesPerKey = 1;
-
-    // The alias for the key that must be unlocked by user auth for every reader session.
-    //
-    // Is non-empty if and only if there is at least one ACP requiring user-auth.
-    private String mPerReaderSessionKeyAlias = "";
-
-    // A map from ACP id to key alias.
-    //
-    // This is for ACPs with positive timeouts.
-    private AbstractMap<Integer, String> mAcpTimeoutKeyAliases;
-
-    // The data for each authentication key, this is always mAuthKeyCount items.
-    private AbstractList<AuthKeyData> mAuthKeyDatas = new ArrayList<>();
-
-    private CredentialData(Context context, String credentialName) {
-        mContext = context;
-        mCredentialName = credentialName;
-    }
-
-    /**
-     * Deletes KeyStore keys no longer needed when updating a credential (every KeyStore key
-     * except for CredentialKey).
-     */
-    void deleteKeysForReplacement() {
-        KeyStore ks;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-        } catch (CertificateException
-                | IOException
-                | NoSuchAlgorithmException
-                | KeyStoreException e) {
-            throw new RuntimeException("Error loading keystore", e);
-        }
-
-        // Nuke all keys except for CredentialKey.
-        try {
-            if (!mPerReaderSessionKeyAlias.isEmpty()) {
-                ks.deleteEntry(mPerReaderSessionKeyAlias);
-            }
-            for (String alias : mAcpTimeoutKeyAliases.values()) {
-                ks.deleteEntry(alias);
-            }
-            for (AuthKeyData authKeyData : mAuthKeyDatas) {
-                if (!authKeyData.mAlias.isEmpty()) {
-                    ks.deleteEntry(authKeyData.mAlias);
-                }
-                if (!authKeyData.mPendingAlias.isEmpty()) {
-                    ks.deleteEntry(authKeyData.mPendingAlias);
-                }
-            }
-        } catch (KeyStoreException e) {
-            throw new RuntimeException("Error deleting key", e);
-        }
-    }
-
-    /**
-     * Creates a new {@link CredentialData} with the given name and saves it to disk.
-     *
-     * <p>The created data will be configured with zero authentication keys and max one use per
-     * key and can later be loaded via the {@link #loadCredentialData(Context, String)}
-     * method and deleted by calling {@link #delete(Context, String, byte[])}.
-     *
-     * <p>An auth-bound key will be created for each access control profile with
-     * user-authentication.
-     *
-     * @param context             the context.
-     * @param credentialName      the name of the credential.
-     * @param credentialKeyAlias  the alias of the credential key which must have already been
-     *                            created.
-     * @param certificateChain    the certificate chain for the credential key.
-     * @param personalizationData the data for the credential.
-     * @param isReplacement       set to true if this replaces an existing credential
-     * @return a new @{link CredentialData} object
-     */
-    static CredentialData createCredentialData(Context context,
-            String docType,
-            String credentialName,
-            String credentialKeyAlias,
-            Collection<X509Certificate> certificateChain,
-            PersonalizationData personalizationData,
-            byte[] proofOfProvisioningSha256,
-            boolean isReplacement) {
-        if (!isReplacement) {
-            if (credentialAlreadyExists(context, credentialName)) {
-                throw new RuntimeException("Credential with given name already exists");
-            }
-        }
-
-        CredentialData data = new CredentialData(context, credentialName);
-        data.mDocType = docType;
-        data.mCredentialKeyAlias = credentialKeyAlias;
-        data.mCertificateChain = certificateChain;
-        data.mProofOfProvisioningSha256 = proofOfProvisioningSha256;
-        data.mAccessControlProfiles = new ArrayList<>();
-        data.mProfileIdToAcpMap = new HashMap<>();
-        for (AccessControlProfile item : personalizationData.getAccessControlProfiles()) {
-            data.mAccessControlProfiles.add(item);
-            data.mProfileIdToAcpMap.put(item.getAccessControlProfileId().getId(), item);
-        }
-        data.mNamespaceDatas = new ArrayList<>();
-        data.mNamespaceDatas.addAll(personalizationData.getNamespaceDatas());
-
-        data.mAcpTimeoutKeyAliases = new HashMap<>();
-        for (AccessControlProfile profile : personalizationData.getAccessControlProfiles()) {
-            boolean isAuthRequired = profile.isUserAuthenticationRequired();
-            long timeoutSeconds = profile.getUserAuthenticationTimeout();
-            if (isAuthRequired) {
-                // Always make sure the per-reader-session key exists since this is what we're
-                // going to be handing out a Cipher for when returning a CryptoObject at
-                // presentation time.
-                ensurePerReaderSessionKey(credentialName, data);
-
-                ensureAcpTimoutKeyForProfile(credentialName, data, profile, timeoutSeconds);
-            }
-        }
-
-        data.createDataEncryptionKey();
-
-        data.saveToDisk();
-        return data;
-    }
-
-    static boolean credentialAlreadyExists(Context context, String credentialName) {
-        String filename = getFilenameForCredentialData(credentialName);
-        AtomicFile file = new AtomicFile(context.getFileStreamPath(filename));
-        try {
-            file.openRead();
-        } catch (FileNotFoundException e) {
-            return false;
-        }
-        return true;
-    }
-
-    @SuppressWarnings("deprecation")  // setUserAuthenticationValidityDurationSeconds()
-    private static void ensurePerReaderSessionKey(String credentialName,
-            CredentialData data) {
-        if (!data.mPerReaderSessionKeyAlias.isEmpty()) {
-            return;
-        }
-        data.mPerReaderSessionKeyAlias = getAcpKeyAliasFromCredentialName(credentialName);
-        try {
-            KeyGenerator kg = KeyGenerator.getInstance(
-                    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
-
-            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                    data.mPerReaderSessionKeyAlias,
-                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                    .setKeySize(128)
-                    // Can't use setUserAuthenticationParameters() since we need to run
-                    // on API level 24.
-                    .setUserAuthenticationRequired(true)
-                    .setUserAuthenticationValidityDurationSeconds(-1); // Auth for every use
-            kg.init(builder.build());
-            kg.generateKey();
-        } catch (InvalidAlgorithmParameterException
-                | NoSuchAlgorithmException
-                | NoSuchProviderException e) {
-            throw new RuntimeException("Error creating ACP auth-bound key", e);
-        }
-    }
-
-    @SuppressWarnings("deprecation")  // setUserAuthenticationValidityDurationSeconds()
-    private static void ensureAcpTimoutKeyForProfile(String credentialName, CredentialData data,
-            AccessControlProfile profile, long timeoutMilliSeconds) {
-        if (timeoutMilliSeconds > 0) {
-            int profileId = profile.getAccessControlProfileId().getId();
-            String acpAlias = getAcpTimeoutKeyAliasFromCredentialName(credentialName,
-                    profileId);
-            try {
-                final int timeoutSeconds = (int) (timeoutMilliSeconds / 1000);
-                KeyGenerator kg = KeyGenerator.getInstance(
-                        KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
-                KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                        acpAlias,
-                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                        .setUserAuthenticationRequired(true)
-                        // Can't use setUserAuthenticationParameters() since we need to run
-                        // on API level 24.
-                        .setUserAuthenticationValidityDurationSeconds(timeoutSeconds)
-                        .setKeySize(128);
-                kg.init(builder.build());
-                kg.generateKey();
-            } catch (InvalidAlgorithmParameterException
-                    | NoSuchAlgorithmException
-                    | NoSuchProviderException e) {
-                throw new RuntimeException("Error creating ACP auth-bound timeout key", e);
-            }
-            data.mAcpTimeoutKeyAliases.put(profileId, acpAlias);
-        }
-    }
-
-    /**
-     * Loads a {@link CredentialData} object previously created with
-     * {@link #createCredentialData(Context, String, String, String, Collection,
-     * PersonalizationData, byte[])}.
-     *
-     * @param context        the application context
-     * @param credentialName the name of the credential.
-     * @return a new {@link CredentialData} object or {@code null} if an error occurred.
-     */
-    static CredentialData loadCredentialData(Context context, String credentialName) {
-        CredentialData data = new CredentialData(context, credentialName);
-        String dataKeyAlias = getDataKeyAliasFromCredentialName(credentialName);
-        if (!data.loadFromDisk(dataKeyAlias)) {
-            return null;
-        }
-        return data;
-    }
-
-    static String escapeCredentialName(String componentName, String credentialName) {
-        try {
-            return "identity_credential_" + componentName + "_"
-                    + URLEncoder.encode(credentialName, "UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException("Unexpected UnsupportedEncodingException", e);
-        }
-    }
-
-    static String getFilenameForCredentialData(String credentialName) {
-        return escapeCredentialName("data", credentialName);
-    }
-
-    static String getAliasFromCredentialName(String credentialName) {
-        return escapeCredentialName("credkey", credentialName);
-    }
-
-    static String getDataKeyAliasFromCredentialName(String credentialName) {
-        return escapeCredentialName("datakey", credentialName);
-    }
-
-    static String getAcpTimeoutKeyAliasFromCredentialName(String credentialName, int acpProfileId) {
-        return escapeCredentialName("acp_timeout_for_id" + acpProfileId, credentialName);
-    }
-
-    static String getAcpKeyAliasFromCredentialName(String credentialName) {
-        return escapeCredentialName("acp", credentialName);
-    }
-
-    PrivateKey getCredentialKeyPrivate() {
-        KeyStore ks;
-        KeyStore.Entry entry;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            entry = ks.getEntry(mCredentialKeyAlias, null);
-        } catch (CertificateException
-                | IOException
-                | NoSuchAlgorithmException
-                | KeyStoreException
-                | UnrecoverableEntryException e) {
-            throw new RuntimeException("Error loading keystore", e);
-        }
-        return ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
-    }
-
-    // Returns COSE_Sign1 with payload set to ProofOfOwnership
-    byte @NonNull [] proveOwnership(byte @NonNull [] challenge) {
-        PrivateKey key = getCredentialKeyPrivate();
-
-        CborBuilder signedDataBuilder = new CborBuilder();
-        signedDataBuilder.addArray()
-                .add("ProofOfOwnership")
-                .add(mDocType)
-                .add(challenge)
-                .add(false);
-        byte[] signatureBytes;
-        try {
-            ByteArrayOutputStream dtsBaos = new ByteArrayOutputStream();
-            CborEncoder dtsEncoder = new CborEncoder(dtsBaos);
-            dtsEncoder.encode(signedDataBuilder.build().get(0));
-            byte[] dataToSign = dtsBaos.toByteArray();
-
-            signatureBytes = Util.cborEncode(Util.coseSign1Sign(key,
-                    dataToSign,
-                    null,
-                    null));
-        } catch (NoSuchAlgorithmException
-                | InvalidKeyException
-                | CertificateEncodingException
-                | CborException e) {
-            throw new RuntimeException("Error building ProofOfOwnership", e);
-        }
-        return signatureBytes;
-    }
-
-    // Returns COSE_Sign1 with payload set to ProofOfDeletion
-    static byte[] buildProofOfDeletionSignature(String docType, PrivateKey key, byte[] challenge) {
-
-        CborBuilder signedDataBuilder = new CborBuilder();
-        ArrayBuilder<CborBuilder> arrayBuilder = signedDataBuilder.addArray();
-        arrayBuilder.add("ProofOfDeletion")
-                .add(docType);
-        if (challenge != null) {
-            arrayBuilder.add(challenge);
-        }
-        arrayBuilder.add(false);
-
-        byte[] signatureBytes;
-        try {
-            ByteArrayOutputStream dtsBaos = new ByteArrayOutputStream();
-            CborEncoder dtsEncoder = new CborEncoder(dtsBaos);
-            dtsEncoder.encode(signedDataBuilder.build().get(0));
-            byte[] dataToSign = dtsBaos.toByteArray();
-
-            signatureBytes = Util.cborEncode(Util.coseSign1Sign(key,
-                    dataToSign,
-                    null,
-                    null));
-        } catch (NoSuchAlgorithmException
-                | InvalidKeyException
-                | CertificateEncodingException
-                | CborException e) {
-            throw new RuntimeException("Error building ProofOfDeletion", e);
-        }
-        return signatureBytes;
-    }
-
-    static byte[] delete(Context context, String credentialName, byte[] challenge) {
-        String filename = getFilenameForCredentialData(credentialName);
-        AtomicFile file = new AtomicFile(context.getFileStreamPath(filename));
-        try {
-            file.openRead();
-        } catch (FileNotFoundException e) {
-            return null;
-        }
-
-        CredentialData data = new CredentialData(context, credentialName);
-        String dataKeyAlias = getDataKeyAliasFromCredentialName(credentialName);
-        try {
-            data.loadFromDisk(dataKeyAlias);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error parsing file on disk (old version?). Deleting anyway.");
-            file.delete();
-            return null;
-        }
-
-        KeyStore ks;
-        KeyStore.Entry entry;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            entry = ks.getEntry(data.mCredentialKeyAlias, null);
-        } catch (CertificateException
-                | IOException
-                | NoSuchAlgorithmException
-                | KeyStoreException
-                | UnrecoverableEntryException e) {
-            throw new RuntimeException("Error loading keystore", e);
-        }
-
-        byte[] signature = buildProofOfDeletionSignature(data.mDocType,
-                ((KeyStore.PrivateKeyEntry) entry).getPrivateKey(), challenge);
-
-        file.delete();
-
-        // Nuke all keys.
-        try {
-            ks.deleteEntry(data.mCredentialKeyAlias);
-            if (!data.mPerReaderSessionKeyAlias.isEmpty()) {
-                ks.deleteEntry(data.mPerReaderSessionKeyAlias);
-            }
-            for (String alias : data.mAcpTimeoutKeyAliases.values()) {
-                ks.deleteEntry(alias);
-            }
-            for (AuthKeyData authKeyData : data.mAuthKeyDatas) {
-                if (!authKeyData.mAlias.isEmpty()) {
-                    ks.deleteEntry(authKeyData.mAlias);
-                }
-                if (!authKeyData.mPendingAlias.isEmpty()) {
-                    ks.deleteEntry(authKeyData.mPendingAlias);
-                }
-            }
-        } catch (KeyStoreException e) {
-            throw new RuntimeException("Error deleting key", e);
-        }
-
-        return signature;
-    }
-
-    private void createDataEncryptionKey() {
-        // TODO: it could maybe be nice to encrypt data with the appropriate auth-bound
-        //  key (the one associated with the ACP with the longest timeout), if it doesn't
-        //  have a no-auth ACP.
-        try {
-            String dataKeyAlias = getDataKeyAliasFromCredentialName(mCredentialName);
-            KeyGenerator kg = KeyGenerator.getInstance(
-                    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
-            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                    dataKeyAlias,
-                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                    .setKeySize(128);
-            kg.init(builder.build());
-            kg.generateKey();
-        } catch (InvalidAlgorithmParameterException
-                | NoSuchAlgorithmException
-                | NoSuchProviderException e) {
-            throw new RuntimeException("Error creating data encryption key", e);
-        }
-    }
-
-    private void saveToDisk() {
-        CborBuilder builder = new CborBuilder();
-        MapBuilder<CborBuilder> map = builder.addMap();
-
-        saveToDiskBasic(map);
-        saveToDiskAuthDatas(map);
-        saveToDiskACPs(map);
-        saveToDiskNamespaceDatas(map);
-        saveToDiskAuthKeys(map);
-
-        byte[] cleartextDataToSaveBytes = saveToDiskEncode(builder);
-
-        byte[] dataToSaveBytes = saveToDiskEncrypt(cleartextDataToSaveBytes);
-
-        String filename = getFilenameForCredentialData(mCredentialName);
-        AtomicFile file = new AtomicFile(mContext.getFileStreamPath(filename));
-        FileOutputStream outputStream = null;
-        try {
-            outputStream = file.startWrite();
-            outputStream.write(dataToSaveBytes);
-            outputStream.close();
-            file.finishWrite(outputStream);
-        } catch (IOException e) {
-            if (outputStream != null) {
-                file.failWrite(outputStream);
-            }
-            throw new RuntimeException("Error writing data", e);
-        }
-    }
-
-    private byte[] saveToDiskEncode(CborBuilder map) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        CborEncoder encoder = new CborEncoder(baos);
-        try {
-            encoder.encode(map.build());
-        } catch (CborException e) {
-            throw new RuntimeException("Error encoding data", e);
-        }
-        return baos.toByteArray();
-    }
-
-    private byte[] saveToDiskEncrypt(byte[] cleartextDataToSaveBytes) {
-        byte[] dataToSaveBytes;
-        try {
-            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            String dataKeyAlias = getDataKeyAliasFromCredentialName(mCredentialName);
-
-            KeyStore.Entry entry = ks.getEntry(dataKeyAlias, null);
-            SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey();
-            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
-
-            byte[] cipherText = cipher.doFinal(
-                    cleartextDataToSaveBytes); // This includes the auth tag
-            ByteBuffer byteBuffer = ByteBuffer.allocate(12 + cipherText.length);
-            byteBuffer.put(cipher.getIV());
-            byteBuffer.put(cipherText);
-            dataToSaveBytes = byteBuffer.array();
-        } catch (NoSuchPaddingException
-                | BadPaddingException
-                | NoSuchAlgorithmException
-                | CertificateException
-                | InvalidKeyException
-                | UnrecoverableEntryException
-                | IOException
-                | IllegalBlockSizeException
-                | KeyStoreException e) {
-            throw new RuntimeException("Error encrypting CBOR for saving to disk", e);
-        }
-        return dataToSaveBytes;
-    }
-
-    private void saveToDiskAuthKeys(MapBuilder<CborBuilder> map) {
-        map.put("perReaderSessionKeyAlias", mPerReaderSessionKeyAlias);
-        MapBuilder<MapBuilder<CborBuilder>> acpTimeoutKeyMapBuilder = map.putMap(
-                "acpTimeoutKeyMap");
-        for (Map.Entry<Integer, String> entry : mAcpTimeoutKeyAliases.entrySet()) {
-            int profileId = entry.getKey();
-            String acpAlias = entry.getValue();
-            acpTimeoutKeyMapBuilder.put(new UnsignedInteger(profileId),
-                    new UnicodeString(acpAlias));
-        }
-    }
-
-    private void saveToDiskNamespaceDatas(MapBuilder<CborBuilder> map) {
-        MapBuilder<MapBuilder<CborBuilder>> ensArrayBuilder = map.putMap(
-                "namespaceDatas");
-        for (PersonalizationData.NamespaceData namespaceData : mNamespaceDatas) {
-            ensArrayBuilder.put(new UnicodeString(namespaceData.getNamespaceName()),
-                    Util.namespaceDataToCbor(namespaceData));
-        }
-    }
-
-    private void saveToDiskACPs(MapBuilder<CborBuilder> map) {
-        ArrayBuilder<MapBuilder<CborBuilder>> acpArrayBuilder = map.putArray(
-                "accessControlProfiles");
-        for (AccessControlProfile profile : mAccessControlProfiles) {
-            acpArrayBuilder.add(Util.accessControlProfileToCbor(profile));
-        }
-    }
-
-    private void saveToDiskAuthDatas(MapBuilder<CborBuilder> map) {
-        ArrayBuilder<MapBuilder<CborBuilder>> authKeyDataArrayBuilder = map.putArray(
-                "authKeyDatas");
-        for (AuthKeyData data : mAuthKeyDatas) {
-            long expirationDateMillis = Long.MAX_VALUE;
-            if (data.mExpirationDate != null) {
-                expirationDateMillis = data.mExpirationDate.getTimeInMillis();
-            }
-            authKeyDataArrayBuilder.addMap()
-                    .put("alias", data.mAlias)
-                    .put("useCount", data.mUseCount)
-                    .put("certificate", data.mCertificate)
-                    .put("staticAuthenticationData", data.mStaticAuthenticationData)
-                    .put("pendingAlias", data.mPendingAlias)
-                    .put("pendingCertificate", data.mPendingCertificate)
-                    .put("expirationDateMillis", expirationDateMillis)
-                    .end();
-        }
-    }
-
-    private void saveToDiskBasic(MapBuilder<CborBuilder> map) {
-        map.put("docType", mDocType);
-        map.put("credentialKeyAlias", mCredentialKeyAlias);
-        ArrayBuilder<MapBuilder<CborBuilder>> credentialKeyCertChainBuilder =
-                map.putArray("credentialKeyCertChain");
-        for (X509Certificate certificate : mCertificateChain) {
-            try {
-                credentialKeyCertChainBuilder.add(certificate.getEncoded());
-            } catch (CertificateEncodingException e) {
-                throw new RuntimeException("Error encoding certificate", e);
-            }
-        }
-        map.put("proofOfProvisioningSha256", mProofOfProvisioningSha256);
-        map.put("authKeyCount", mAuthKeyCount);
-        map.put("authKeyMaxUses", mAuthMaxUsesPerKey);
-    }
-
-    private boolean loadFromDisk(String dataKeyAlias) {
-        String filename = getFilenameForCredentialData(mCredentialName);
-        byte[] encryptedFileData = new byte[0];
-        try {
-            AtomicFile file = new AtomicFile(mContext.getFileStreamPath(filename));
-            encryptedFileData = file.readFully();
-        } catch (Exception e) {
-            return false;
-        }
-
-        byte[] fileData = loadFromDiskDecrypt(dataKeyAlias, encryptedFileData);
-
-        try {
-            ByteArrayInputStream bais = new ByteArrayInputStream(fileData);
-            List<DataItem> dataItems = new CborDecoder(bais).decode();
-            if (dataItems.size() != 1) {
-                throw new RuntimeException("Expected 1 item, found " + dataItems.size());
-            }
-            if (!(dataItems.get(0) instanceof co.nstant.in.cbor.model.Map)) {
-                throw new RuntimeException("Item is not a map");
-            }
-            co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) dataItems.get(0);
-
-            loadBasic(map);
-            loadCredentialKeyCertChain(map);
-            loadProofOfProvisioningSha256(map);
-            loadAccessControlProfiles(map);
-            loadNamespaceDatas(map);
-            loadAuthKey(map);
-
-        } catch (CborException e) {
-            throw new RuntimeException("Error decoding data", e);
-        }
-        return true;
-    }
-
-    private byte[] loadFromDiskDecrypt(String dataKeyAlias, byte[] encryptedFileData) {
-        byte[] fileData = null;
-        try {
-            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            KeyStore.Entry entry = ks.getEntry(dataKeyAlias, null);
-            SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey();
-
-            if (encryptedFileData.length < 12) {
-                throw new RuntimeException("Encrypted CBOR on disk is too small");
-            }
-            ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedFileData);
-            byte[] iv = new byte[12];
-            byteBuffer.get(iv);
-            byte[] cipherText = new byte[encryptedFileData.length - 12];
-            byteBuffer.get(cipherText);
-
-            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
-            fileData = cipher.doFinal(cipherText);
-        } catch (InvalidAlgorithmParameterException
-                | NoSuchPaddingException
-                | BadPaddingException
-                | NoSuchAlgorithmException
-                | CertificateException
-                | InvalidKeyException
-                | IOException
-                | IllegalBlockSizeException
-                | UnrecoverableEntryException
-                | KeyStoreException e) {
-            throw new RuntimeException("Error decrypting CBOR", e);
-        }
-        return fileData;
-    }
-
-    private void loadBasic(co.nstant.in.cbor.model.Map map) {
-        mDocType = ((UnicodeString) map.get(new UnicodeString("docType"))).getString();
-        mCredentialKeyAlias = ((UnicodeString) map.get(
-                new UnicodeString("credentialKeyAlias"))).getString();
-    }
-
-    private void loadAuthKey(co.nstant.in.cbor.model.Map map) {
-        mPerReaderSessionKeyAlias = ((UnicodeString) map.get(
-                new UnicodeString("perReaderSessionKeyAlias"))).getString();
-
-        DataItem userAuthKeyAliases = map.get(new UnicodeString("acpTimeoutKeyMap"));
-        if (!(userAuthKeyAliases instanceof co.nstant.in.cbor.model.Map)) {
-            throw new RuntimeException("acpTimeoutKeyMap not found or not map");
-        }
-        mAcpTimeoutKeyAliases = new HashMap<>();
-        for (DataItem key : ((co.nstant.in.cbor.model.Map) userAuthKeyAliases).getKeys()) {
-            if (!(key instanceof UnsignedInteger)) {
-                throw new RuntimeException(
-                        "Key in acpTimeoutKeyMap is not an integer");
-            }
-            int profileId = ((UnsignedInteger) key).getValue().intValue();
-            DataItem item = ((co.nstant.in.cbor.model.Map) userAuthKeyAliases).get(key);
-            if (!(item instanceof UnicodeString)) {
-                throw new RuntimeException(
-                        "Item in acpTimeoutKeyMap is not a string");
-            }
-            String acpAlias = ((UnicodeString) item).getString();
-            mAcpTimeoutKeyAliases.put(profileId, acpAlias);
-        }
-
-        mAuthKeyCount = ((Number) map.get(
-                new UnicodeString("authKeyCount"))).getValue().intValue();
-        mAuthMaxUsesPerKey = ((Number) map.get(
-                new UnicodeString("authKeyMaxUses"))).getValue().intValue();
-
-        DataItem authKeyDatas = map.get(new UnicodeString("authKeyDatas"));
-        if (!(authKeyDatas instanceof Array)) {
-            throw new RuntimeException("authKeyDatas not found or not array");
-        }
-        mAuthKeyDatas = new ArrayList<AuthKeyData>();
-        for (DataItem item : ((Array) authKeyDatas).getDataItems()) {
-            AuthKeyData data = new AuthKeyData();
-
-            co.nstant.in.cbor.model.Map im = (co.nstant.in.cbor.model.Map) item;
-
-            data.mAlias = ((UnicodeString) im.get(new UnicodeString("alias"))).getString();
-            data.mUseCount = ((Number) im.get(new UnicodeString("useCount"))).getValue().intValue();
-            data.mCertificate = ((ByteString) im.get(new UnicodeString("certificate"))).getBytes();
-            data.mStaticAuthenticationData = ((ByteString) im.get(
-                    new UnicodeString("staticAuthenticationData"))).getBytes();
-            data.mPendingAlias = ((UnicodeString) im.get(
-                    new UnicodeString("pendingAlias"))).getString();
-            data.mPendingCertificate = ((ByteString) im.get(
-                    new UnicodeString("pendingCertificate"))).getBytes();
-
-            // expirationDateMillis was added in a later release, may not be present
-            long expirationDateMillis = Long.MAX_VALUE;
-            DataItem expirationDateMillisItem = im.get(new UnicodeString("expirationDateMillis"));
-            if (expirationDateMillisItem != null) {
-                if (!(expirationDateMillisItem instanceof Number)) {
-                    throw new RuntimeException("expirationDateMillis not a number");
-                }
-                expirationDateMillis = ((Number) expirationDateMillisItem).getValue().longValue();
-            }
-            Calendar expirationDate = Calendar.getInstance();
-            expirationDate.setTimeInMillis(expirationDateMillis);
-            data.mExpirationDate = expirationDate;
-
-            mAuthKeyDatas.add(data);
-        }
-    }
-
-    private void loadNamespaceDatas(co.nstant.in.cbor.model.Map map) {
-        DataItem namespaceDatas = map.get(new UnicodeString("namespaceDatas"));
-        if (!(namespaceDatas instanceof co.nstant.in.cbor.model.Map)) {
-            throw new RuntimeException("namespaceDatas not found or not map");
-        }
-        mNamespaceDatas = new ArrayList<PersonalizationData.NamespaceData>();
-        for (DataItem key : ((co.nstant.in.cbor.model.Map) namespaceDatas).getKeys()) {
-            if (!(key instanceof UnicodeString)) {
-                throw new RuntimeException("Key in namespaceDatas is not a string");
-            }
-            String namespaceName = ((UnicodeString) key).getString();
-            DataItem item = ((co.nstant.in.cbor.model.Map) namespaceDatas).get(key);
-            mNamespaceDatas.add(Util.namespaceDataFromCbor(namespaceName, item));
-        }
-    }
-
-    private void loadAccessControlProfiles(co.nstant.in.cbor.model.Map map) {
-        DataItem accessControlProfiles = map.get(new UnicodeString("accessControlProfiles"));
-        if (!(accessControlProfiles instanceof Array)) {
-            throw new RuntimeException(
-                    "accessControlProfiles not found or not array");
-        }
-        mAccessControlProfiles = new ArrayList<AccessControlProfile>();
-        mProfileIdToAcpMap = new HashMap<Integer, AccessControlProfile>();
-        for (DataItem item : ((Array) accessControlProfiles).getDataItems()) {
-            AccessControlProfile profile = Util.accessControlProfileFromCbor(item);
-            mAccessControlProfiles.add(profile);
-            mProfileIdToAcpMap.put(profile.getAccessControlProfileId().getId(), profile);
-        }
-    }
-
-    private void loadProofOfProvisioningSha256(co.nstant.in.cbor.model.Map map) {
-        DataItem proofOfProvisioningSha256 = map.get(
-                new UnicodeString("proofOfProvisioningSha256"));
-        if (!(proofOfProvisioningSha256 instanceof ByteString)) {
-            throw new RuntimeException(
-                    "proofOfProvisioningSha256 not found or not bstr");
-        }
-        mProofOfProvisioningSha256 = ((ByteString) proofOfProvisioningSha256).getBytes();
-    }
-
-    private void loadCredentialKeyCertChain(co.nstant.in.cbor.model.Map map) {
-        DataItem credentialKeyCertChain = map.get(new UnicodeString("credentialKeyCertChain"));
-        if (!(credentialKeyCertChain instanceof Array)) {
-            throw new RuntimeException(
-                    "credentialKeyCertChain not found or not array");
-        }
-        mCertificateChain = new ArrayList<>();
-        for (DataItem item : ((Array) credentialKeyCertChain).getDataItems()) {
-            byte[] encodedCert = ((ByteString) item).getBytes();
-            try {
-                CertificateFactory cf = CertificateFactory.getInstance("X.509");
-                ByteArrayInputStream certBais = new ByteArrayInputStream(encodedCert);
-                mCertificateChain.add((X509Certificate) cf.generateCertificate(certBais));
-            } catch (CertificateException e) {
-                throw new RuntimeException("Error decoding certificate blob", e);
-            }
-        }
-    }
-
-    Collection<AccessControlProfile> getAccessControlProfiles() {
-        return mAccessControlProfiles;
-    }
-
-    Collection<PersonalizationData.NamespaceData> getNamespaceDatas() {
-        return mNamespaceDatas;
-    }
-
-    PersonalizationData.NamespaceData lookupNamespaceData(String nameSpace) {
-        // TODO: This might be slow, maybe build map at load/build time
-        for (PersonalizationData.NamespaceData namespaceData : mNamespaceDatas) {
-            if (namespaceData.getNamespaceName().equals(nameSpace)) {
-                return namespaceData;
-            }
-        }
-        return null;
-    }
-
-    String getCredentialKeyAlias() {
-        return mCredentialKeyAlias;
-    }
-
-    String getPerReaderSessionKeyAlias() {
-        return mPerReaderSessionKeyAlias;
-    }
-
-    int getAuthKeyCount() {
-        return mAuthKeyCount;
-    }
-
-    int getAuthMaxUsesPerKey() {
-        return mAuthMaxUsesPerKey;
-    }
-
-    int[] getAuthKeyUseCounts() {
-        int[] result = new int[mAuthKeyCount];
-        int n = 0;
-        for (AuthKeyData data : mAuthKeyDatas) {
-            result[n++] = data.mUseCount;
-        }
-        return result;
-    }
-
-    void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
-        int prevAuthKeyCount = mAuthKeyCount;
-        mAuthKeyCount = keyCount;
-        mAuthMaxUsesPerKey = maxUsesPerKey;
-
-        if (prevAuthKeyCount < mAuthKeyCount) {
-            // Added non-zero number of auth keys...
-            for (int n = prevAuthKeyCount; n < mAuthKeyCount; n++) {
-                mAuthKeyDatas.add(new AuthKeyData());
-            }
-        } else if (prevAuthKeyCount > mAuthKeyCount) {
-            KeyStore ks = null;
-            try {
-                ks = KeyStore.getInstance("AndroidKeyStore");
-                ks.load(null);
-            } catch (CertificateException
-                    | IOException
-                    | NoSuchAlgorithmException
-                    | KeyStoreException e) {
-                throw new RuntimeException("Error loading keystore", e);
-            }
-
-            int numKeysToDelete = prevAuthKeyCount - mAuthKeyCount;
-            // Removed non-zero number of auth keys. For now we just delete
-            // the keys at the beginning... (an optimization could be to instead
-            // delete the keys with the biggest use count).
-            for (int n = 0; n < numKeysToDelete; n++) {
-                AuthKeyData data = mAuthKeyDatas.get(0);
-                if (!data.mAlias.isEmpty()) {
-                    try {
-                        if (ks.containsAlias(data.mAlias)) {
-                            ks.deleteEntry(data.mAlias);
-                        }
-                    } catch (KeyStoreException e) {
-                        throw new RuntimeException(
-                                "Error deleting auth key with mAlias " + data.mAlias, e);
-                    }
-                }
-                if (!data.mPendingAlias.isEmpty()) {
-                    try {
-                        if (ks.containsAlias(data.mPendingAlias)) {
-                            ks.deleteEntry(data.mPendingAlias);
-                        }
-                    } catch (KeyStoreException e) {
-                        throw new RuntimeException(
-                                "Error deleting auth key with mPendingAlias " + data.mPendingAlias,
-                                e);
-                    }
-                }
-                mAuthKeyDatas.remove(0);
-            }
-        }
-        saveToDisk();
-    }
-
-    Collection<X509Certificate> getAuthKeysNeedingCertification() {
-        KeyStore ks = null;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-        } catch (CertificateException
-                | IOException
-                | NoSuchAlgorithmException
-                | KeyStoreException e) {
-            throw new RuntimeException("Error loading keystore", e);
-        }
-
-        ArrayList<X509Certificate> certificates = new ArrayList<X509Certificate>();
-
-        Calendar now = Calendar.getInstance();
-
-        // Determine which keys need certification (or re-certification) and generate
-        // keys and X.509 certs for these and mark them as pending.
-        for (int n = 0; n < mAuthKeyCount; n++) {
-            AuthKeyData data = mAuthKeyDatas.get(n);
-
-            boolean keyExceededUseCount = (data.mUseCount >= mAuthMaxUsesPerKey);
-            boolean keyBeyondExpirationDate = false;
-            if (data.mExpirationDate != null) {
-                keyBeyondExpirationDate = now.after(data.mExpirationDate);
-            }
-            boolean newKeyNeeded =
-                    data.mAlias.isEmpty() || keyExceededUseCount || keyBeyondExpirationDate;
-            boolean certificationPending = !data.mPendingAlias.isEmpty();
-
-            if (newKeyNeeded && !certificationPending) {
-                try {
-                    // Calculate name to use and be careful to avoid collisions when
-                    // re-certifying an already populated slot.
-                    String aliasForAuthKey = mCredentialKeyAlias + String.format("_auth_%d", n);
-                    if (aliasForAuthKey.equals(data.mAlias)) {
-                        aliasForAuthKey = aliasForAuthKey + "_";
-                    }
-
-                    KeyPairGenerator kpg = KeyPairGenerator.getInstance(
-                            KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
-                    KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                            aliasForAuthKey,
-                            KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                            .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
-                    kpg.initialize(builder.build());
-                    kpg.generateKeyPair();
-
-                    X509Certificate certificate = Util.generateAuthenticationKeyCert(
-                            aliasForAuthKey, mCredentialKeyAlias, mProofOfProvisioningSha256);
-
-                    data.mPendingAlias = aliasForAuthKey;
-                    data.mPendingCertificate = certificate.getEncoded();
-                    certificationPending = true;
-                } catch (InvalidAlgorithmParameterException
-                        | NoSuchAlgorithmException
-                        | NoSuchProviderException
-                        | CertificateEncodingException e) {
-                    throw new RuntimeException("Error creating auth key", e);
-                }
-            }
-
-            if (certificationPending) {
-                try {
-                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
-                    ByteArrayInputStream bais = new ByteArrayInputStream(data.mPendingCertificate);
-                    certificates.add((X509Certificate) cf.generateCertificate(bais));
-                } catch (CertificateException e) {
-                    throw new RuntimeException(
-                            "Error creating certificate for auth key", e);
-                }
-            }
-        }
-
-        saveToDisk();
-
-        return certificates;
-    }
-
-    void storeStaticAuthenticationData(X509Certificate authenticationKey,
-            Calendar expirationDate,
-            byte[] staticAuthData)
-            throws UnknownAuthenticationKeyException {
-        AuthKeyData dataForAuthKey = null;
-        CertificateFactory cf = null;
-        try {
-            cf = CertificateFactory.getInstance("X.509");
-
-            for (AuthKeyData data : mAuthKeyDatas) {
-                if (data.mPendingCertificate.length > 0) {
-                    ByteArrayInputStream bais = new ByteArrayInputStream(data.mPendingCertificate);
-                    X509Certificate certificate = (X509Certificate) cf.generateCertificate(bais);
-                    if (certificate.equals(authenticationKey)) {
-                        dataForAuthKey = data;
-                        break;
-                    }
-                }
-            }
-        } catch (CertificateException e) {
-            throw new RuntimeException("Error encoding certificate", e);
-        }
-
-        if (dataForAuthKey == null) {
-            throw new UnknownAuthenticationKeyException("No such authentication key");
-        }
-
-        // Delete old key, if set.
-        if (!dataForAuthKey.mAlias.isEmpty()) {
-            KeyStore ks = null;
-            try {
-                ks = KeyStore.getInstance("AndroidKeyStore");
-                ks.load(null);
-                if (ks.containsAlias(dataForAuthKey.mAlias)) {
-                    ks.deleteEntry(dataForAuthKey.mAlias);
-                }
-            } catch (CertificateException
-                    | IOException
-                    | NoSuchAlgorithmException
-                    | KeyStoreException e) {
-                throw new RuntimeException("Error deleting old authentication key", e);
-            }
-        }
-        dataForAuthKey.mAlias = dataForAuthKey.mPendingAlias;
-        dataForAuthKey.mCertificate = dataForAuthKey.mPendingCertificate;
-        dataForAuthKey.mStaticAuthenticationData = staticAuthData;
-        dataForAuthKey.mUseCount = 0;
-        dataForAuthKey.mPendingAlias = "";
-        dataForAuthKey.mPendingCertificate = new byte[0];
-        dataForAuthKey.mExpirationDate = expirationDate;
-        saveToDisk();
-    }
-
-    /**
-     * Selects an authentication key to use.
-     *
-     * The victim is picked simply by choosing the key with the smallest use count. (This may
-     * change in the future.)
-     *
-     * The use count of the returned authentication key will be increased by one.
-     *
-     * If no key could be found {@code null} is returned.
-     *
-     * @param allowUsingExhaustedKeys If {@code true}, allow using an authentication key which
-     *                                use count has been exceeded if no other key is available.
-     * @param allowUsingExpiredKeys If {@code true}, allow using an authentication key which
-     *                              is expired.
-     * @return A pair containing the authentication key and its associated static authentication
-     * data or {@code null} if no key could be found.
-     */
-    Pair<PrivateKey, byte[]> selectAuthenticationKey(boolean allowUsingExhaustedKeys,
-            boolean allowUsingExpiredKeys) {
-
-        // First try to find a un-expired key..
-        Pair<PrivateKey, byte[]> keyAndStaticData =
-                selectAuthenticationKeyHelper(allowUsingExhaustedKeys, false);
-        if (keyAndStaticData != null) {
-            return keyAndStaticData;
-        }
-        // Nope, try to see if there's an expired key (if allowed)
-        if (!allowUsingExpiredKeys) {
-            return null;
-        }
-        return selectAuthenticationKeyHelper(allowUsingExhaustedKeys, true);
-    }
-
-    Pair<PrivateKey, byte[]> selectAuthenticationKeyHelper(boolean allowUsingExhaustedKeys,
-            boolean allowUsingExpiredKeys) {
-        AuthKeyData candidate = null;
-
-        Calendar now = Calendar.getInstance();
-
-        for (int n = 0; n < mAuthKeyCount; n++) {
-            AuthKeyData data = mAuthKeyDatas.get(n);
-            if (!data.mAlias.isEmpty()) {
-                if (data.mExpirationDate != null) {
-                    if (now.after(data.mExpirationDate)) {
-                        // expired...
-                        if (!allowUsingExpiredKeys) {
-                            continue;
-                        }
-                    }
-                }
-
-                if (candidate == null || data.mUseCount < candidate.mUseCount) {
-                    candidate = data;
-                }
-            }
-        }
-
-        if (candidate == null) {
-            return null;
-        }
-
-        // We've found the key with lowest use count... so if this is beyond maximum uses
-        // so are all the other ones. So fail if we're not allowed to use exhausted keys.
-        if (candidate.mUseCount >= mAuthMaxUsesPerKey && !allowUsingExhaustedKeys) {
-            return null;
-        }
-
-        KeyStore.Entry entry = null;
-        try {
-            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            entry = ks.getEntry(candidate.mAlias, null);
-        } catch (CertificateException
-                | IOException
-                | NoSuchAlgorithmException
-                | KeyStoreException
-                | UnrecoverableEntryException e) {
-            throw new RuntimeException("Error loading keystore", e);
-        }
-
-        Pair<PrivateKey, byte[]> result = new Pair<>(
-                ((KeyStore.PrivateKeyEntry) entry).getPrivateKey(),
-                candidate.mStaticAuthenticationData);
-
-        candidate.mUseCount += 1;
-        saveToDisk();
-
-        return result;
-    }
-
-    String getDocType() {
-        return mDocType;
-    }
-
-    Collection<X509Certificate> getCredentialKeyCertificateChain() {
-        return mCertificateChain;
-    }
-
-    AccessControlProfile getAccessControlProfile(AccessControlProfileId profileId) {
-        AccessControlProfile profile = mProfileIdToAcpMap.get(profileId.getId());
-        if (profile == null) {
-            throw new RuntimeException("No profile with id " + profileId.getId());
-        }
-        return profile;
-    }
-
-    private boolean checkUserAuthenticationTimeout(String acpAlias) {
-        // Unfortunately there are no APIs to tell if a key needs user authentication to work so
-        // we check if the key is available by simply trying to encrypt some data.
-        try {
-            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            KeyStore.Entry entry = ks.getEntry(acpAlias, null);
-            SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey();
-
-            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
-            byte[] clearText = {0x01, 0x02};
-            cipher.doFinal(clearText);
-            // We don't care about the cipherText, only whether the key is unlocked.
-        } catch (NoSuchPaddingException
-                | BadPaddingException
-                | NoSuchAlgorithmException
-                | CertificateException
-                | InvalidKeyException
-                | IOException
-                | IllegalBlockSizeException
-                | UnrecoverableEntryException
-                | KeyStoreException e) {
-            // If this fails, it probably means authentication is needed... (there's no
-            // specific exception for that in API level 23, unfortunately.)
-            return false;
-        }
-        return true;
-    }
-
-    boolean checkUserAuthentication(AccessControlProfileId accessControlProfileId,
-            boolean didUserAuth) {
-        AccessControlProfile profile = getAccessControlProfile(accessControlProfileId);
-        if (profile.getUserAuthenticationTimeout() == 0) {
-            return didUserAuth;
-        }
-        String acpAlias = mAcpTimeoutKeyAliases.get(accessControlProfileId.getId());
-        if (acpAlias == null) {
-            throw new RuntimeException(
-                    "No key alias for ACP with ID " + accessControlProfileId.getId());
-        }
-        return checkUserAuthenticationTimeout(acpAlias);
-    }
-
-    // Note that a dynamic authentication key may have two Android Keystore keys associated with
-    // it.. the obvious one is for a previously certificated key. This key may possibly have an
-    // use-count which is already exhausted. The other one is for a key yet pending certification.
-    //
-    // Why is it implemented this way? Because we never want selectAuthenticationKey() to fail.
-    // That is, it's better to use a key with an exhausted use-count (slightly bad for user privacy
-    // in terms of linkability between multiple presentations) than the user not being able to
-    // present their credential at all...
-    private static class AuthKeyData {
-        // The mAlias for the key in Android Keystore. Is set to the empty string if the key has not
-        // yet been created. This is set to the empty string if no key has been certified.
-        String mAlias = "";
-        // The X509 certificate for the key. This is empty if mAlias is empty.
-        byte[] mCertificate = new byte[0];
-        // The static authentication data, as set by the application as part of certification. This
-        // is empty if mAlias is empty.
-        byte[] mStaticAuthenticationData = new byte[0];
-        // The number of times a key has been used.
-        int mUseCount = 0;
-        // The alias for a key pending certification. Once a key has been certified - by
-        // calling storeStaticAuthenticationData() - and is no longer pending, both mPendingAlias
-        // and mPendingCertificate will be set to the empty string and empty byte array.
-        String mPendingAlias = "";
-        // The X509 certificate for a key pending certification.
-        byte[] mPendingCertificate = new byte[0];
-
-        Calendar mExpirationDate = null;
-
-        AuthKeyData() {
-        }
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/DocTypeNotSupportedException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/DocTypeNotSupportedException.java
deleted file mode 100644
index 322a22b..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/DocTypeNotSupportedException.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Thrown if trying to create a credential with an unsupported document type.
- */
-public class DocTypeNotSupportedException extends IdentityCredentialException {
-    /**
-     * Constructs a new {@link DocTypeNotSupportedException} exception.
-     *
-     * @param message the detail message.
-     */
-    public DocTypeNotSupportedException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link DocTypeNotSupportedException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public DocTypeNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/EphemeralPublicKeyNotFoundException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/EphemeralPublicKeyNotFoundException.java
deleted file mode 100644
index 1b7a10f..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/EphemeralPublicKeyNotFoundException.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-import java.util.Map;
-
-/**
- * Thrown if the ephemeral public key was not found in the session transcript
- * passed to {@link IdentityCredential#getEntries(byte[], Map, byte[])}.
- */
-public class EphemeralPublicKeyNotFoundException extends IdentityCredentialException {
-    /**
-     * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
-     *
-     * @param message the detail message.
-     */
-    public EphemeralPublicKeyNotFoundException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public EphemeralPublicKeyNotFoundException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
deleted file mode 100644
index 775bec1..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
+++ /dev/null
@@ -1,372 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.icu.util.Calendar;
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-import androidx.biometric.BiometricPrompt;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.nio.ByteBuffer;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.cert.X509Certificate;
-import java.time.Instant;
-import java.util.Collection;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyAgreement;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-@RequiresApi(Build.VERSION_CODES.R)
-class HardwareIdentityCredential extends IdentityCredential {
-
-    private static final String TAG = "HardwareIdentityCredential";
-
-    private KeyPair mEphemeralKeyPair = null;
-    private PublicKey mReaderEphemeralPublicKey = null;
-    private byte[] mSessionTranscript = null;
-
-    private SecretKey mSKDevice = null;
-    private SecretKey mSKReader = null;
-
-    private int mSKDeviceCounter;
-    private int mSKReaderCounter;
-
-    private android.security.identity.IdentityCredential mCredential  = null;
-
-    HardwareIdentityCredential(android.security.identity.IdentityCredential credential) {
-        mCredential = credential;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public @NonNull KeyPair createEphemeralKeyPair() {
-        if (mEphemeralKeyPair == null) {
-            mEphemeralKeyPair = mCredential.createEphemeralKeyPair();
-        }
-        return mEphemeralKeyPair;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
-            throws InvalidKeyException {
-        mReaderEphemeralPublicKey = readerEphemeralPublicKey;
-        mCredential.setReaderEphemeralPublicKey(readerEphemeralPublicKey);
-    }
-
-    @Override
-    public void setSessionTranscript(byte @NonNull [] sessionTranscript) {
-        if (mSessionTranscript != null) {
-            throw new RuntimeException("SessionTranscript already set");
-        }
-        mSessionTranscript = sessionTranscript.clone();
-    }
-
-    private void ensureSessionEncryptionKey() {
-        if (mSKDevice != null) {
-            return;
-        }
-        if (mReaderEphemeralPublicKey == null) {
-            throw new RuntimeException("Reader ephemeral key not set");
-        }
-        if (mSessionTranscript == null) {
-            throw new RuntimeException("Session transcript not set");
-        }
-        try {
-            KeyAgreement ka = KeyAgreement.getInstance("ECDH");
-            ka.init(mEphemeralKeyPair.getPrivate());
-            ka.doPhase(mReaderEphemeralPublicKey, true);
-            byte[] sharedSecret = ka.generateSecret();
-
-            byte[] sessionTranscriptBytes =
-                    Util.cborEncode(Util.cborBuildTaggedByteString(mSessionTranscript));
-            byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
-
-            byte[] info = new byte[] {'S', 'K', 'D', 'e', 'v', 'i', 'c', 'e'};
-            byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-            mSKDevice = new SecretKeySpec(derivedKey, "AES");
-
-            info = new byte[] {'S', 'K', 'R', 'e', 'a', 'd', 'e', 'r'};
-            derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-            mSKReader = new SecretKeySpec(derivedKey, "AES");
-
-            mSKDeviceCounter = 1;
-            mSKReaderCounter = 1;
-
-        } catch (InvalidKeyException
-                | NoSuchAlgorithmException e) {
-            throw new RuntimeException("Error performing key agreement", e);
-        }
-    }
-
-    @Override
-    public     byte @NonNull [] encryptMessageToReader(byte @NonNull [] messagePlaintext) {
-        ensureSessionEncryptionKey();
-        byte[] messageCiphertextAndAuthTag = null;
-        try {
-            ByteBuffer iv = ByteBuffer.allocate(12);
-            iv.putInt(0, 0x00000000);
-            iv.putInt(4, 0x00000001);
-            iv.putInt(8, mSKDeviceCounter);
-            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array());
-            cipher.init(Cipher.ENCRYPT_MODE, mSKDevice, encryptionParameterSpec);
-            messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext);
-        } catch (BadPaddingException
-                | IllegalBlockSizeException
-                | NoSuchPaddingException
-                | InvalidKeyException
-                | NoSuchAlgorithmException
-                | InvalidAlgorithmParameterException e) {
-            throw new RuntimeException("Error encrypting message", e);
-        }
-        mSKDeviceCounter += 1;
-        return messageCiphertextAndAuthTag;
-    }
-
-    @Override
-    public     byte @NonNull [] decryptMessageFromReader(byte @NonNull [] messageCiphertext)
-            throws MessageDecryptionException {
-        ensureSessionEncryptionKey();
-        ByteBuffer iv = ByteBuffer.allocate(12);
-        iv.putInt(0, 0x00000000);
-        iv.putInt(4, 0x00000000);
-        iv.putInt(8, mSKReaderCounter);
-        byte[] plainText = null;
-        try {
-            final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            cipher.init(Cipher.DECRYPT_MODE, mSKReader, new GCMParameterSpec(128,
-                    iv.array()));
-            plainText = cipher.doFinal(messageCiphertext);
-        } catch (BadPaddingException
-                | IllegalBlockSizeException
-                | InvalidAlgorithmParameterException
-                | InvalidKeyException
-                | NoSuchAlgorithmException
-                | NoSuchPaddingException e) {
-            throw new MessageDecryptionException("Error decrypting message", e);
-        }
-        mSKReaderCounter += 1;
-        return plainText;
-    }
-
-    @Override
-    public     @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain() {
-        return mCredential.getCredentialKeyCertificateChain();
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
-        mCredential.setAllowUsingExhaustedKeys(allowUsingExhaustedKeys);
-    }
-
-    @Override
-    public BiometricPrompt.@Nullable CryptoObject getCryptoObject() {
-        BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(mCredential);
-        return cryptoObject;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public @NonNull ResultData getEntries(
-            byte @Nullable [] requestMessage,
-            java.util.@NonNull Map<String, Collection<String>> entriesToRequest,
-            byte @Nullable [] readerSignature)
-            throws NoAuthenticationKeyAvailableException,
-            InvalidReaderSignatureException, InvalidRequestMessageException,
-            EphemeralPublicKeyNotFoundException {
-
-        android.security.identity.ResultData rd;
-        try {
-            rd = mCredential.getEntries(requestMessage,
-                    entriesToRequest,
-                    mSessionTranscript,
-                    readerSignature);
-        } catch (android.security.identity.NoAuthenticationKeyAvailableException e) {
-            throw new NoAuthenticationKeyAvailableException(e.getMessage(), e);
-        } catch (android.security.identity.InvalidReaderSignatureException e) {
-            throw new InvalidReaderSignatureException(e.getMessage(), e);
-        } catch (android.security.identity.InvalidRequestMessageException e) {
-            throw new InvalidRequestMessageException(e.getMessage(), e);
-        } catch (android.security.identity.EphemeralPublicKeyNotFoundException e) {
-            throw new EphemeralPublicKeyNotFoundException(e.getMessage(), e);
-        } catch (android.security.identity.SessionTranscriptMismatchException e) {
-            throw new RuntimeException("Unexpected SessionMismatchException", e);
-        }
-
-        SimpleResultData.Builder builder = new SimpleResultData.Builder();
-        builder.setMessageAuthenticationCode(rd.getMessageAuthenticationCode());
-        builder.setAuthenticatedData(rd.getAuthenticatedData());
-        builder.setStaticAuthenticationData(rd.getStaticAuthenticationData());
-
-        for (String namespaceName : rd.getNamespaces()) {
-            for (String entryName : rd.getEntryNames(namespaceName)) {
-                int status = rd.getStatus(namespaceName, entryName);
-                if (status == ResultData.STATUS_OK) {
-                    byte[] value = rd.getEntry(namespaceName, entryName);
-                    builder.addEntry(namespaceName, entryName, value);
-                } else {
-                    builder.addErrorStatus(namespaceName, entryName, status);
-                }
-            }
-        }
-
-        return builder.build();
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
-        mCredential.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
-    }
-
-    @Override
-    public     @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() {
-        return mCredential.getAuthKeysNeedingCertification();
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public void storeStaticAuthenticationData(@NonNull X509Certificate authenticationKey,
-            byte @NonNull [] staticAuthData) throws UnknownAuthenticationKeyException {
-        try {
-            mCredential.storeStaticAuthenticationData(authenticationKey, staticAuthData);
-        } catch (android.security.identity.UnknownAuthenticationKeyException e) {
-            throw new UnknownAuthenticationKeyException(e.getMessage(), e);
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public     int @NonNull [] getAuthenticationDataUsageCount() {
-        return mCredential.getAuthenticationDataUsageCount();
-    }
-
-    @RequiresApi(Build.VERSION_CODES.S)
-    private static class ApiImplS {
-        @SuppressWarnings("deprecation")
-        static void callSetAllowUsingExpiredKeys(
-                android.security.identity.@NonNull IdentityCredential credential,
-                boolean allowUsingExpiredKeys) {
-            credential.setAllowUsingExpiredKeys(allowUsingExpiredKeys);
-        }
-
-        static void callStoreStaticAuthenticationData(
-                android.security.identity.@NonNull IdentityCredential credential,
-                @NonNull X509Certificate authenticationKey,
-                @NonNull Instant expirationDate,
-                byte @NonNull [] staticAuthData)
-                throws android.security.identity.UnknownAuthenticationKeyException {
-            credential.storeStaticAuthenticationData(authenticationKey,
-                    expirationDate,
-                    staticAuthData);
-        }
-
-        static byte @NonNull [] callProveOwnership(
-                android.security.identity.@NonNull IdentityCredential credential,
-                byte @NonNull [] challenge) {
-            return credential.proveOwnership(challenge);
-        }
-
-        static byte @NonNull [] callDelete(
-                android.security.identity.@NonNull IdentityCredential credential,
-                byte @NonNull [] challenge) {
-            return credential.delete(challenge);
-        }
-
-        static byte @NonNull [] callUpdate(
-                android.security.identity.@NonNull IdentityCredential credential,
-                android.security.identity.@NonNull PersonalizationData personalizationData) {
-            return credential.update(personalizationData);
-        }
-    }
-
-    @Override
-    public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            ApiImplS.callSetAllowUsingExpiredKeys(mCredential, allowUsingExpiredKeys);
-        } else {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    @Override
-    public void storeStaticAuthenticationData(
-            @NonNull X509Certificate authenticationKey,
-            @NonNull Calendar expirationDate,
-            byte @NonNull [] staticAuthData)
-            throws UnknownAuthenticationKeyException {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            try {
-                Instant expirationDateAsInstant =
-                        Instant.ofEpochMilli(expirationDate.getTimeInMillis());
-                ApiImplS.callStoreStaticAuthenticationData(mCredential,
-                        authenticationKey,
-                        expirationDateAsInstant,
-                        staticAuthData);
-            } catch (android.security.identity.UnknownAuthenticationKeyException e) {
-                throw new UnknownAuthenticationKeyException(e.getMessage(), e);
-            }
-        } else {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    @Override
-    public byte @NonNull [] proveOwnership(byte @NonNull [] challenge)  {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            return ApiImplS.callProveOwnership(mCredential, challenge);
-        } else {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    @Override
-    public byte @NonNull [] delete(byte @NonNull [] challenge)  {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            return ApiImplS.callDelete(mCredential, challenge);
-        } else {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    @Override
-    public byte @NonNull [] update(@NonNull PersonalizationData personalizationData) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            return ApiImplS.callUpdate(mCredential,
-                    HardwareWritableIdentityCredential.convertPDFromJetpack(personalizationData));
-        } else {
-            throw new UnsupportedOperationException();
-        }
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredentialStore.java b/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredentialStore.java
deleted file mode 100644
index 09f0a3e7..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredentialStore.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-@RequiresApi(Build.VERSION_CODES.R)
-class HardwareIdentityCredentialStore extends IdentityCredentialStore {
-
-    private static final String TAG = "HardwareIdentityCredentialStore";
-    private final Context mContext;
-
-    private android.security.identity.IdentityCredentialStore mStore = null;
-    private boolean mIsDirectAccess = false;
-
-    private HardwareIdentityCredentialStore(
-            android.security.identity.@NonNull IdentityCredentialStore store,
-            @NonNull Context context,
-            boolean isDirectAccess) {
-        mStore = store;
-        mContext = context;
-        mIsDirectAccess = isDirectAccess;
-    }
-
-    static @Nullable IdentityCredentialStore getInstanceIfSupported(@NonNull Context context) {
-        android.security.identity.IdentityCredentialStore store =
-                android.security.identity.IdentityCredentialStore.getInstance(context);
-        if (store != null) {
-            return new HardwareIdentityCredentialStore(store, context, false);
-        }
-        return null;
-    }
-
-    @SuppressWarnings("deprecation")
-    public static @NonNull IdentityCredentialStore getInstance(@NonNull Context context) {
-        IdentityCredentialStore instance = getInstanceIfSupported(context);
-        if (instance != null) {
-            return instance;
-        }
-        throw new RuntimeException("HW-backed IdentityCredential not supported");
-    }
-
-    static @Nullable IdentityCredentialStore getDirectAccessInstanceIfSupported(
-            @NonNull Context context) {
-        android.security.identity.IdentityCredentialStore store =
-                android.security.identity.IdentityCredentialStore.getDirectAccessInstance(context);
-        if (store != null) {
-            return new HardwareIdentityCredentialStore(store, context, true);
-        }
-        return null;
-    }
-
-    @SuppressWarnings("deprecation")
-    public static @NonNull IdentityCredentialStore getDirectAccessInstance(
-            @NonNull Context context) {
-        IdentityCredentialStore instance = getDirectAccessInstanceIfSupported(context);
-        if (instance != null) {
-            return instance;
-        }
-        throw new RuntimeException("HW-backed direct-access IdentityCredential not supported");
-    }
-
-    public static boolean isDirectAccessSupported(@NonNull Context context) {
-        IdentityCredentialStore directAccessStore = getDirectAccessInstanceIfSupported(context);
-        if (directAccessStore != null) {
-            return true;
-        }
-        return false;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public String @NonNull [] getSupportedDocTypes() {
-        Set<String> docTypeSet = getCapabilities().getSupportedDocTypes();
-        String[] docTypes = new String[docTypeSet.size()];
-        int n = 0;
-        for (String docType : docTypeSet) {
-            docTypes[n++] = docType;
-        }
-        return docTypes;
-    }
-
-    @Override
-    public @NonNull WritableIdentityCredential createCredential(
-            @NonNull String credentialName,
-            @NonNull String docType) throws AlreadyPersonalizedException,
-            DocTypeNotSupportedException {
-        try {
-            android.security.identity.WritableIdentityCredential writableCredential =
-                    mStore.createCredential(credentialName, docType);
-            return new HardwareWritableIdentityCredential(writableCredential);
-        } catch (android.security.identity.AlreadyPersonalizedException e) {
-            throw new AlreadyPersonalizedException(e.getMessage(), e);
-        } catch (android.security.identity.DocTypeNotSupportedException e) {
-            throw new DocTypeNotSupportedException(e.getMessage(), e);
-        }
-    }
-
-    @Override
-    public @Nullable IdentityCredential getCredentialByName(
-            @NonNull String credentialName,
-            @Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException {
-        try {
-            android.security.identity.IdentityCredential credential =
-                    mStore.getCredentialByName(credentialName, cipherSuite);
-            if (credential == null) {
-                return null;
-            }
-            return new HardwareIdentityCredential(credential);
-        } catch (android.security.identity.CipherSuiteNotSupportedException e) {
-            throw new CipherSuiteNotSupportedException(e.getMessage(), e);
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public byte @Nullable [] deleteCredentialByName(@NonNull String credentialName) {
-        return mStore.deleteCredentialByName(credentialName);
-    }
-
-    SimpleIdentityCredentialStoreCapabilities mCapabilities = null;
-
-    @Override
-    public     @NonNull IdentityCredentialStoreCapabilities getCapabilities() {
-        LinkedHashSet<String> supportedDocTypesSet =
-                new LinkedHashSet<>(Arrays.asList(mStore.getSupportedDocTypes()));
-
-        if (mCapabilities == null) {
-            PackageManager pm = mContext.getPackageManager();
-            String featureName = PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE;
-            if (mIsDirectAccess) {
-                featureName = PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS;
-            }
-
-            if (pm.hasSystemFeature(featureName,
-                    IdentityCredentialStoreCapabilities.FEATURE_VERSION_202101)) {
-                mCapabilities = SimpleIdentityCredentialStoreCapabilities.getFeatureVersion202101(
-                        mIsDirectAccess,
-                        true,
-                        supportedDocTypesSet);
-            } else {
-                mCapabilities = SimpleIdentityCredentialStoreCapabilities.getFeatureVersion202009(
-                        mIsDirectAccess,
-                        true,
-                        supportedDocTypesSet);
-            }
-        }
-        return mCapabilities;
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareWritableIdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareWritableIdentityCredential.java
deleted file mode 100644
index 8da9547..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareWritableIdentityCredential.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-
-import org.jspecify.annotations.NonNull;
-
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-
-@RequiresApi(Build.VERSION_CODES.R)
-class HardwareWritableIdentityCredential extends WritableIdentityCredential {
-
-    private static final String TAG = "HardwareWritableIdentityCredential";
-
-    android.security.identity.WritableIdentityCredential mWritableCredential = null;
-
-    HardwareWritableIdentityCredential(
-            android.security.identity.WritableIdentityCredential writableCredential) {
-        mWritableCredential = writableCredential;
-    }
-
-    @Override
-    public @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
-            byte @NonNull [] challenge) {
-        return mWritableCredential.getCredentialKeyCertificateChain(challenge);
-    }
-
-    static android.security.identity.@NonNull PersonalizationData convertPDFromJetpack(
-            @NonNull PersonalizationData personalizationData) {
-
-        android.security.identity.PersonalizationData.Builder builder =
-                new android.security.identity.PersonalizationData.Builder();
-        for (PersonalizationData.NamespaceData nsData : personalizationData.getNamespaceDatas()) {
-            for (String entryName : nsData.getEntryNames()) {
-                ArrayList<android.security.identity.AccessControlProfileId> acpIds =
-                        new ArrayList<>();
-                for (AccessControlProfileId id : nsData.getAccessControlProfileIds(entryName)) {
-                    acpIds.add(new android.security.identity.AccessControlProfileId(id.getId()));
-                }
-                builder.putEntry(nsData.getNamespaceName(),
-                        entryName,
-                        acpIds,
-                        nsData.getEntryValue(entryName));
-            }
-        }
-
-        for (AccessControlProfile profile : personalizationData.getAccessControlProfiles()) {
-            android.security.identity.AccessControlProfileId id =
-                    new android.security.identity.AccessControlProfileId(
-                            profile.getAccessControlProfileId().getId());
-            android.security.identity.AccessControlProfile.Builder profileBuilder =
-                    new android.security.identity.AccessControlProfile.Builder(id);
-            profileBuilder.setReaderCertificate(profile.getReaderCertificate());
-            profileBuilder.setUserAuthenticationTimeout(profile.getUserAuthenticationTimeout());
-            profileBuilder.setUserAuthenticationRequired(profile.isUserAuthenticationRequired());
-            builder.addAccessControlProfile(profileBuilder.build());
-        }
-
-        return builder.build();
-    }
-
-    @Override
-    public byte @NonNull [] personalize(@NonNull PersonalizationData personalizationData) {
-        return mWritableCredential.personalize(convertPDFromJetpack(personalizationData));
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredential.java
deleted file mode 100644
index f12db719..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredential.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.icu.util.Calendar;
-
-import androidx.annotation.RestrictTo;
-import androidx.biometric.BiometricPrompt;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * Class used to read data from a previously provisioned credential.
- * <p>
- * Use {@link IdentityCredentialStore#getCredentialByName(String, int)} to get a
- * {@link IdentityCredential} instance.
- */
-public abstract class IdentityCredential {
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    protected IdentityCredential() {}
-
-    /**
-     * Create an ephemeral key pair to use to establish a secure channel with a reader.
-     *
-     * <p>Most applications will use only the public key, and only to send it to the reader,
-     * allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])}
-     * and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for
-     * applications that wish to use a cipher suite that is not supported by
-     * {@link IdentityCredentialStore}.
-     *
-     * @return ephemeral key pair to use to establish a secure channel with a reader.
-     */
-    public abstract @NonNull KeyPair createEphemeralKeyPair();
-
-    /**
-     * Set the ephemeral public key provided by the reader. This must be called before
-     * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called.
-     *
-     * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
-     *                                 establish a secure session.
-     * @throws InvalidKeyException if the given key is invalid.
-     */
-    public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
-            throws InvalidKeyException;
-
-    /**
-     * Set the session transcript. This must be called before
-     * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called.
-     *
-     * <p>This method can only be called once per {@link IdentityCredential} instance.
-     *
-     * @param sessionTranscript the session transcript.
-     */
-    public abstract void setSessionTranscript(byte @NonNull [] sessionTranscript);
-
-    /**
-     * Encrypt a message for transmission to the reader.
-     *
-     * <p>In order for this to work, {@link #setSessionTranscript(byte[])} and
-     * {@link #setReaderEphemeralPublicKey(PublicKey)} must have already been
-     * called.
-     *
-     * @param messagePlaintext unencrypted message to encrypt.
-     * @return encrypted message.
-     */
-    public abstract byte @NonNull [] encryptMessageToReader(byte @NonNull [] messagePlaintext);
-
-    /**
-     * Decrypt a message received from the reader.
-     *
-     * <p>In order for this to work, {@link #setSessionTranscript(byte[])} and
-     * {@link #setReaderEphemeralPublicKey(PublicKey)} must have already been
-     * called.
-     *
-     * @param messageCiphertext encrypted message to decrypt.
-     * @return decrypted message.
-     * @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
-     */
-    public abstract byte @NonNull [] decryptMessageFromReader(byte @NonNull [] messageCiphertext)
-            throws MessageDecryptionException;
-
-    /**
-     * Gets the X.509 certificate chain for the CredentialKey which identifies this
-     * credential to the issuing authority. This is the same certificate chain that
-     * was returned by {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])}
-     * when the credential was first created and its Android Keystore extension will
-     * contain the <code>challenge</code> data set at that time. See the documentation
-     * for that method for important information about this certificate chain.
-     *
-     * @return the certificate chain for this credential's CredentialKey.
-     */
-    public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain();
-
-    /**
-     * Sets whether to allow using an authentication key which use count has been exceeded if no
-     * other key is available. This must be called prior to calling
-     * {@link #getEntries(byte[], Map, byte[])} or using a {@link BiometricPrompt.CryptoObject}
-     * which references this object.
-     *
-     * <p>By default this is set to true.</p>
-     *
-     * @param allowUsingExhaustedKeys whether to allow using an authentication key which use count
-     *                                has been exceeded if no other key is available.
-     */
-    public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
-
-    /**
-     * Sets whether to allow using an authentication key which has been expired if no
-     * other key is available. This must be called prior to calling
-     * {@link #getEntries(byte[], Map, byte[])} or using a {@link BiometricPrompt.CryptoObject}
-     * which references this object.
-     *
-     * <p>By default this is set to false.
-     *
-     * <p>This is only implemented if
-     * {@link IdentityCredentialStoreCapabilities#isStaticAuthenticationDataExpirationSupported()}
-     * returns {@code true}. If not the call fails with {@link UnsupportedOperationException}.
-     *
-     * @param allowUsingExpiredKeys whether to allow using an authentication key which use count
-     *                              has been exceeded if no other key is available.
-     */
-    public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Gets a {@link BiometricPrompt.CryptoObject} which can be used with this
-     * {@link IdentityCredential}.
-     *
-     * <p>If {@link IdentityCredential} is not hardware-backed the returned
-     * {@link BiometricPrompt.CryptoObject} is associated with a {@link java.security.Signature}
-     * object. If {@link IdentityCredential} is hardware-backed, the returned
-     * {@link BiometricPrompt.CryptoObject} is associated
-     * {@link android.security.identity.IdentityCredential} object from the Android Framework.
-     * Because of this, this method is the preferred way
-     * to obtain a {@link BiometricPrompt.CryptoObject} rather than to construct it
-     * manually.</p>
-     *
-     * <p>If the credential has no access control profiles with user-authentication, the value
-     * {@code null} may be returned.</p>
-     *
-     * @return A {@link BiometricPrompt.CryptoObject} which can be used with
-     * {@link BiometricPrompt} or {@code null}.
-     */
-    public abstract BiometricPrompt.@Nullable CryptoObject getCryptoObject();
-
-    /**
-     * Retrieve data entries and associated data from this {@code IdentityCredential}.
-     *
-     * <p>If an access control check fails for one of the requested entries or if the entry
-     * doesn't exist, the entry is simply not returned. The application can detect this
-     * by using the {@link ResultData#getStatus(String, String)} method on each of the requested
-     * entries.
-     *
-     * <p>It is the responsibility of the calling application to know if authentication is needed
-     * and use e.g. {@link androidx.biometric.BiometricPrompt} to make the user
-     * authenticate using a {@link androidx.biometric.BiometricPrompt.CryptoObject} which
-     * references this object. If needed, this must be done before calling
-     * {@link #getEntries(byte[], Map, byte[])}.
-     *
-     * <p>It is permissible to call this method multiple times using the same instance.
-     *
-     * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
-     * from the verifier. The content can be defined in the way appropriate for the credential, but
-     * there are three requirements that must be met to work with this API:
-     * <ul>
-     * <li>The content must be a CBOR-encoded structure.</li>
-     * <li>The CBOR structure must be a map.</li>
-     * <li>The map must contain a tstr key "nameSpaces" whose value contains a map, as described in
-     *     the example below.</li>
-     * </ul>
-     *
-     * <p>If these requirements are not met the {@link InvalidRequestMessageException} exception
-     * is thrown.
-     *
-     * <p>Here's an example of CBOR which conforms to this requirement:
-     * <pre>
-     *   ItemsRequest = {
-     *     ? "docType" : DocType,
-     *     "nameSpaces" : NameSpaces,
-     *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
-     *   }
-     *
-     *   DocType = tstr
-     *
-     *   NameSpaces = {
-     *     + NameSpace => DataElements    ; Requested data elements for each NameSpace
-     *   }
-     *
-     *   NameSpace = tstr
-     *
-     *   DataElements = {
-     *     + DataElement => IntentToRetain
-     *   }
-     *
-     *   DataElement = tstr
-     *   IntentToRetain = bool
-     * </pre>
-     *
-     * <p>If the {@link #setSessionTranscript(byte[])} was called, the X and Y coordinates
-     * of the public part of the key-pair previously generated by {@link #createEphemeralKeyPair()}
-     * must appear somewhere in the byte array that was set. Each of these coordinates must appear
-     * encoded with the most significant bits first and use the exact amount of bits indicated by
-     * the key size of the ephemeral keys. For example, if the ephemeral key is using the P-256
-     * curve then the 32 bytes for the X coordinate encoded with the most significant bits first
-     * must appear somewhere in the CBOR and ditto for the 32 bytes for the Y coordinate.
-     *
-     * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a {@code COSE_Sign1}
-     * structure as defined in RFC 8152. For the payload nil shall be used and the
-     * detached payload is the ReaderAuthenticationBytes CBOR described below.
-     * <pre>
-     *     ReaderAuthentication = [
-     *       "ReaderAuthentication",
-     *       SessionTranscript,
-     *       ItemsRequestBytes
-     *     ]
-     *
-     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
-     *
-     *     ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
-     * </pre>
-     *
-     * <p>where {@code ItemsRequestBytes} are the bytes in the {@code requestMessage} parameter.
-     *
-     * <p>The public key corresponding to the key used to make the signature, can be found in the
-     * {@code x5chain} unprotected header element of the {@code COSE_Sign1} structure (as as
-     * described in
-     * <a href="https://tools.ietf.org/html/draft-ietf-cose-x509-04">draft-ietf-cose-x509-04</a>).
-     * There will be at least one certificate in said element and there may be more (and if so,
-     * each certificate must be signed by its successor).
-     *
-     * <p>Data elements protected by reader authentication are returned if, and only if, they are
-     * mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most
-     * certificate in the reader's certificate chain, and the data element is configured
-     * with an {@link AccessControlProfile} configured with an X.509 certificate which appears
-     * in the certificate chain.
-     *
-     * <p>Note that only items referenced in {@code entriesToRequest} are returned - the
-     * {@code requestMessage} parameter is used only for enforcing reader authentication.
-     *
-     * <p>The reason for having {@code requestMessage} and {@code entriesToRequest} as separate
-     * parameters is that the former represents a request from the remote verifier device
-     * (optionally signed) and this allows the application to filter the request to not include
-     * data elements which the user has not consented to sharing.
-     *
-     * @param requestMessage         If not {@code null}, must contain CBOR data conforming to
-     *                               the schema mentioned above.
-     * @param entriesToRequest       The entries to request, organized as a map of namespace
-     *                               names with each value being a collection of data elements
-     *                               in the given namespace.
-     * @param readerSignature        A {@code COSE_Sign1} structure as described above or
-     *                               {@code null} if reader authentication is not being used.
-     * @return A {@link ResultData} object containing entry data organized by namespace and a
-     *         cryptographically authenticated representation of the same data.
-     * @throws NoAuthenticationKeyAvailableException  is thrown if authentication keys were never
-     *                                                provisioned, none are available, or the
-     *                                                available ones are all exhausted and
-     *                                                {@link #setAllowUsingExhaustedKeys(boolean)}
-     *                                                was called with {@code false}.
-     * @throws InvalidReaderSignatureException        if the reader signature is invalid, or it
-     *                                                doesn't contain a certificate chain, or if
-     *                                                the signature failed to validate.
-     * @throws InvalidRequestMessageException         if the requestMessage is malformed.
-     * @throws EphemeralPublicKeyNotFoundException    if the ephemeral public key was not found in
-     *                                                the session transcript.
-     */
-    public abstract @NonNull ResultData getEntries(
-            byte @Nullable [] requestMessage,
-            @NonNull Map<String, Collection<String>> entriesToRequest,
-            byte @Nullable [] readerSignature)
-            throws NoAuthenticationKeyAvailableException,
-            InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException,
-            InvalidRequestMessageException;
-
-    /**
-     * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
-     * and the number of times each should be used.
-     *
-     * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each
-     * time {@link #getEntries(byte[], Map, byte[])} is called. {@code IdentityCredential}s
-     * for which this method has not been called behave as though it had been called wit
-     * {@code keyCount} 0 and {@code maxUsesPerKey} 1.
-     *
-     * @param keyCount      The number of active, certified dynamic authentication keys the
-     *                      {@code IdentityCredential} will try to keep available. This value
-     *                      must be non-negative.
-     * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
-     *                      eligible for replacement. This value must be greater than zero.
-     */
-    public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
-
-    /**
-     * Gets a collection of dynamic authentication keys that need certification.
-     *
-     * <p>When there aren't enough certified dynamic authentication keys (either because the key
-     * count has been increased, one or more keys have reached their usage count, or keys have
-     * expired), this method will generate replacement keys and certificates and return them for
-     * issuer certification. The issuer certificates and associated static authentication data
-     * must then be provided back to the {@code IdentityCredential} using
-     * {@link #storeStaticAuthenticationData(X509Certificate, Calendar, byte[])}.
-     *
-     * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
-     * can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
-     *
-     * <p>The following non-optional fields for the X.509 certificate are set as follows:
-     * <ul>
-     *  <li>version: INTEGER 2 (means v3 certificate).</li>
-     *  <li>serialNumber: INTEGER 1 (fixed value: same on all certs).</li>
-     *  <li>signature: must be set to ECDSA.</li>
-     *  <li>subject: CN shall be set to "Android Identity Credential Authentication Key" (fixed
-     *  value: same on all certs).</li>
-     *  <li>issuer: CN shall be set to "Android Identity Credential Key" (fixed value: same on
-     *  all certs).</li>
-     *  <li>validity: should be from current time and one year in the future (365 days).</li>
-     *  <li>subjectPublicKeyInfo: must contain attested public key.</li>
-     * </ul>
-     *
-     * <p>If {@link IdentityCredentialStoreCapabilities#isUpdateSupported()} returns
-     * {@code true}, each X.509 certificate contains an X.509 extension at OID 1.3.6.1.4.1.11129
-     * .2.1.26 which contains a DER encoded OCTET STRING with the bytes of the CBOR with the
-     * following CDDL:
-     * <pre>
-     *   ProofOfBinding = [
-     *     "ProofOfBinding",
-     *     bstr,              // Contains SHA-256(ProofOfProvisioning)
-     *   ]
-     * </pre>
-     * <p>This CBOR enables an issuer to determine the exact state of the credential it
-     * returns issuer-signed data for.
-     *
-     * @return A collection of X.509 certificates for dynamic authentication keys that need issuer
-     * certification.
-     */
-    public abstract @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification();
-
-    /**
-     * Store authentication data associated with a dynamic authentication key.
-     *
-     * <p>This should only be called for an authenticated key returned by
-     * {@link #getAuthKeysNeedingCertification()}.</p>
-     *
-     * @param authenticationKey The dynamic authentication key for which certification and
-     *                          associated static authentication data is being provided.
-     * @param staticAuthData    Static authentication data provided by the issuer that validates
-     *                          the authenticity
-     *                          and integrity of the credential data fields.
-     * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
-     * @deprecated Use {@link #storeStaticAuthenticationData(X509Certificate, Calendar, byte[])}
-     *     instead.
-     */
-    @Deprecated
-    public abstract void storeStaticAuthenticationData(
-            @NonNull X509Certificate authenticationKey,
-            byte @NonNull [] staticAuthData)
-            throws UnknownAuthenticationKeyException;
-
-    /**
-     * Store authentication data associated with a dynamic authentication key.
-     *
-     * <p>This should only be called for an authenticated key returned by
-     * {@link #getAuthKeysNeedingCertification()}.</p>
-     *
-     * <p>This is only implemented if
-     * {@link IdentityCredentialStoreCapabilities#isStaticAuthenticationDataExpirationSupported()}
-     * returns {@code true}. If not the call fails with {@link UnsupportedOperationException}.
-     *
-     * @param authenticationKey The dynamic authentication key for which certification and
-     *                          associated static authentication data is being provided.
-     * @param expirationDate    The expiration date of the static authentication data.
-     * @param staticAuthData    Static authentication data provided by the issuer that validates
-     *                          the authenticity
-     *                          and integrity of the credential data fields.
-     * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
-     */
-    public void storeStaticAuthenticationData(
-            @NonNull X509Certificate authenticationKey,
-            @NonNull Calendar expirationDate,
-            byte @NonNull [] staticAuthData)
-            throws UnknownAuthenticationKeyException {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Get the number of times the dynamic authentication keys have been used.
-     *
-     * @return int array of dynamic authentication key usage counts.
-     */
-    public abstract int @NonNull [] getAuthenticationDataUsageCount();
-
-
-    /**
-     * Proves ownership of a credential.
-     *
-     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
-     * with payload set to {@code ProofOfOwnership} as defined below.</p>
-     * <pre>
-     *     ProofOfOwnership = [
-     *          "ProofOfOwnership",           ; tstr
-     *          tstr,                         ; DocType
-     *          bstr,                         ; Challenge
-     *          bool                          ; true if this is a test credential, should
-     *                                        ; always be false.
-     *      ]
-     * </pre>
-     *
-     * <p>This is only implemented if
-     * {@link IdentityCredentialStoreCapabilities#isProveOwnershipSupported()}
-     * returns {@code true}. If not the call fails with {@link UnsupportedOperationException}.
-     *
-     * @param challenge is a non-empty byte array whose contents should be unique, fresh and
-     *                  provided by the issuing authority. The value provided is embedded in the
-     *                  generated CBOR and enables the issuing authority to verify that the
-     *                  returned proof is fresh.
-     * @return the COSE_Sign1 data structure above
-     */
-    public byte @NonNull [] proveOwnership(byte @NonNull [] challenge)  {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Deletes a credential.
-     *
-     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
-     * with payload set to {@code ProofOfDeletion} as defined below.</p>
-     * <pre>
-     *     ProofOfDeletion = [
-     *          "ProofOfDeletion",            ; tstr
-     *          tstr,                         ; DocType
-     *          bstr,                         ; Challenge
-     *          bool                          ; true if this is a test credential, should
-     *                                        ; always be false.
-     *      ]
-     * </pre>
-     *
-     * <p>This is only implemented if
-     * {@link IdentityCredentialStoreCapabilities#isDeleteSupported()}
-     * returns {@code true}. If not the call fails with {@link UnsupportedOperationException}.
-     *
-     * @param challenge is a non-empty byte array whose contents should be unique, fresh and
-     *                  provided by the issuing authority. The value provided is embedded in the
-     *                  generated CBOR and enables the issuing authority to verify that the
-     *                  returned proof is fresh.
-     * @return the COSE_Sign1 data structure above
-     */
-    public byte @NonNull [] delete(byte @NonNull [] challenge)  {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Updates the credential with new access control profiles and data items.
-     *
-     * <p>This method is similar to
-     * {@link WritableIdentityCredential#personalize(PersonalizationData)} except that it operates
-     * on an existing credential, see the documentation for that method for the format of the
-     * returned data.
-     *
-     * <p>If this call succeeds an side-effect is that all dynamic authentication keys for the
-     * credential are deleted. The application will need to use
-     * {@link #getAuthKeysNeedingCertification()} to generate replacement keys and return
-     * them for issuer certification.
-     *
-     * <p>This is only implemented if
-     * {@link IdentityCredentialStoreCapabilities#isUpdateSupported()}
-     * returns {@code true}. If not the call fails with {@link UnsupportedOperationException}.
-     *
-     * @param personalizationData   The data to update, including access control profiles
-     *                              and data elements and their values, grouped into namespaces.
-     * @return A COSE_Sign1 data structure, see above.
-     */
-    public byte @NonNull [] update(@NonNull PersonalizationData personalizationData) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialException.java
deleted file mode 100644
index 26a6211..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialException.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Base class for all Identity Credential exceptions.
- */
-public class IdentityCredentialException extends Exception {
-    /**
-     * Constructs a new {@link IdentityCredentialException} exception.
-     *
-     * @param message the detail message.
-     */
-    public IdentityCredentialException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link IdentityCredentialException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public IdentityCredentialException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialStore.java b/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialStore.java
deleted file mode 100644
index 4504e8e..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialStore.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.content.Context;
-import android.os.Build;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.RestrictTo;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.security.cert.X509Certificate;
-
-/**
- * An interface to a secure store for user identity documents.
- *
- * <p>This interface is deliberately fairly general and abstract.  To the extent possible,
- * specification of the message formats and semantics of communication with credential
- * verification devices and issuing authorities (IAs) is out of scope. It provides the
- * interface with secure storage but a credential-specific Android application will be
- * required to implement the presentation and verification protocols and processes
- * appropriate for the specific credential type.
- *
- * <p>Multiple credentials can be created.  Each credential comprises:</p>
- * <ul>
- * <li>A document type, which is a string.</li>
- *
- * <li>A set of namespaces, which serve to disambiguate value names. It is recommended
- * that namespaces be structured as reverse domain names so that IANA effectively serves
- * as the namespace registrar.</li>
- *
- * <li>For each namespace, a set of name/value pairs, each with an associated set of
- * access control profile IDs.  Names are strings and values are typed and can be any
- * value supported by <a href="http://cbor.io/">CBOR</a>.</li>
- *
- * <li>A set of access control profiles, each with a profile ID and a specification
- * of the conditions which satisfy the profile's requirements.</li>
- *
- * <li>An asymmetric key pair which is used to authenticate the credential to the Issuing
- * Authority, called the <em>CredentialKey</em>.</li>
- *
- * <li>A set of zero or more named reader authentication public keys, which are used to
- * authenticate an authorized reader to the credential.</li>
- *
- * <li>A set of named signing keys, which are used to sign collections of values and session
- * transcripts.</li>
- * </ul>
- *
- * <p>Implementing support for user identity documents in secure storage requires dedicated
- * hardware-backed support and may not always be available. In addition to hardware-backed
- * Identity Credential support (which is only available in Android 11 and later and only
- * if the device has support for the <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/master/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl">Identity Credential HAL</a>),
- * this Jetpack has an Android Keystore backed implementation (also known as the "software"
- * implementation) which works on any Android device with API level 24 or later.
- *
- * <p>The Identity Credential API is designed to be able to evolve and change over time
- * but still provide 100% backwards compatibility. This is complicated by the fact that
- * there may be a version skew between the API used by the application and the version
- * implemented in secure hardware. To solve this problem, the API provides for a way
- * for the application to query for hardware capabilities through
- * {@link IdentityCredentialStoreCapabilities}. The software-based store is designed
- * so it implements all capabilities that don't explicitly require hardware features. Each
- * of the methods in that class will state whether it's implemented in the software-based
- * implementation.
- *
- * <p>When provisioning a document, applications should use {@link #getInstance(Context)} to
- * obtain an {@link IdentityCredentialStore} instance. This method returns a hardware-backed
- * store if available and a software-based store if not and the application should use
- * {@link IdentityCredentialStoreCapabilities} to examine if the store supports the
- * capabilities required by the application. In the negative, the application can
- * obtain the software-based store by calling {@link #getSoftwareInstance(Context)}.
- *
- * <p>Since it's possible for an OS upgrade on a device to include an updated version of the
- * drivers used for secure hardware, it's possible that {@link #getInstance(Context)} returns the
- * software implementation at one point and the hardware implementation at a later point.
- * Therefore, applications should only use {@link #getInstance(Context)} only when creating a
- * credential, record whether it's hardware- or software-backed (using
- * {@link IdentityCredentialStoreCapabilities#isHardwareBacked()}, and then use this information
- * when retrieving the credential (using either {@link #getSoftwareInstance(Context)} or
- * {@link #getHardwareInstance(Context)}).
- *
- * <p>Apart from hardware- vs software-backed, two different flavors of credential stores exist -
- * the <em>default</em> store and the <em>direct access</em> store. Most often credentials will
- * be accessed through the default store but that requires that the Android device be powered up
- * and fully functional. It is desirable to allow identity credential usage when the Android
- * device's battery is too low to boot the Android operating system, so direct access to the
- * secure hardware via NFC may allow data retrieval, if the secure hardware chooses to implement it.
- *
- * <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader
- * authentication to protect data elements. The reason for this is user authentication or user
- * approval of data release is not possible when the device is off.
- */
-public abstract class IdentityCredentialStore {
-    IdentityCredentialStore() {}
-
-    /**
-     * Specifies that the cipher suite that will be used to secure communications between the
-     * reader and the prover is using the following primitives
-     *
-     * <ul>
-     * <li>ECKA-DH (Elliptic Curve Key Agreement Algorithm - Diffie-Hellman, see BSI TR-03111).</li>
-     * <li>HKDF-SHA-256 (see RFC 5869).</li>
-     * <li>AES-256-GCM (see NIST SP 800-38D).</li>
-     * <li>HMAC-SHA-256 (see RFC 2104).</li>
-     * </ul>
-     *
-     * <p>The exact way these primitives are combined to derive the session key
-     * is specified in section 9.2.1.4 of ISO/IEC 18013-5 (see description of cipher
-     * suite '1').</p>
-     *
-     * <p>At present this is the only supported cipher suite.</p>
-     */
-    public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1;
-
-    /**
-     * Gets the default {@link IdentityCredentialStore}.
-     *
-     * @param context the application context.
-     * @return the {@link IdentityCredentialStore}.
-     */
-    public static @NonNull IdentityCredentialStore getInstance(@NonNull Context context) {
-        Context appContext = context.getApplicationContext();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            IdentityCredentialStore store =
-                    HardwareIdentityCredentialStore.getInstanceIfSupported(appContext);
-            if (store != null) {
-                return store;
-            }
-        }
-        return SoftwareIdentityCredentialStore.getInstance(appContext);
-    }
-
-    /**
-     * Gets the {@link IdentityCredentialStore} for direct access.
-     * <p>
-     * This should only be called if {@link #isDirectAccessSupported(Context)} returns {@code true}.
-     *
-     * @param context the application context.
-     * @return the {@link IdentityCredentialStore} or {@code null} if direct access is not
-     *     supported on this device.
-     */
-    public static @NonNull IdentityCredentialStore getDirectAccessInstance(
-            @NonNull Context context) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            Context appContext = context.getApplicationContext();
-            IdentityCredentialStore store = HardwareIdentityCredentialStore.getDirectAccessInstance(
-                    appContext);
-            if (store != null) {
-                return store;
-            }
-        }
-        throw new RuntimeException("Direct-access IdentityCredential is not supported");
-    }
-
-    /**
-     * Checks if direct-access is supported.
-     *
-     * <p>Direct access requires specialized NFC hardware and may not be supported on all
-     * devices even if default store is available.</p>
-     *
-     * <p>Because Android is not running when direct-access credentials are presented, there is
-     * no way for the user to consent to release of credential data. Therefore, credentials
-     * provisioned to the direct access store should <strong>always</strong> use reader
-     * authentication to protect data elements such that only readers authorized by the issuer
-     * can access them. The
-     * {@link AccessControlProfile.Builder#setReaderCertificate(X509Certificate)}
-     * method can be used at provisioning time to set which reader (or group of readers) are
-     * authorized to access data elements.</p>
-     *
-     * @param context the application context.
-     * @return {@code true} if direct-access is supported.
-     */
-    public static boolean isDirectAccessSupported(@NonNull Context context) {
-        // SoftwareIdentityCredentialStore will never support direct-access.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            Context appContext = context.getApplicationContext();
-            return HardwareIdentityCredentialStore.isDirectAccessSupported(appContext);
-        }
-        return false;
-    }
-
-    /**
-     * Gets a list of supported document types.
-     *
-     * <p>Only the direct-access store may restrict the kind of document types that can be used for
-     * credentials. The default store always supports any document type.
-     *
-     * @return The supported document types or the empty array if any document type is supported.
-     * @deprecated Use {@link IdentityCredentialStoreCapabilities#getSupportedDocTypes()} instead.
-     */
-    @Deprecated
-    public abstract String @NonNull [] getSupportedDocTypes();
-
-    /**
-     * Creates a new credential.
-     *
-     * <p>Note that the credential is not persisted until calling
-     * {@link WritableIdentityCredential#personalize(PersonalizationData)} on the returned
-     * {@link WritableIdentityCredential} object.
-     *
-     * @param credentialName The name used to identify the credential.
-     * @param docType        The document type for the credential.
-     * @return A @{link WritableIdentityCredential} that can be used to create a new credential.
-     * @throws AlreadyPersonalizedException if a credential with the given name already exists.
-     * @throws DocTypeNotSupportedException if the given document type isn't supported by the store.
-     */
-    public abstract @NonNull WritableIdentityCredential createCredential(
-            @NonNull String credentialName, @NonNull String docType)
-            throws AlreadyPersonalizedException, DocTypeNotSupportedException;
-
-    /**
-     * Retrieve a named credential.
-     *
-     * @param credentialName the name of the credential to retrieve.
-     * @param cipherSuite    the cipher suite to use for communicating with the verifier.
-     * @return The named credential, or null if not found.
-     */
-    public abstract @Nullable IdentityCredential getCredentialByName(@NonNull String credentialName,
-            @Ciphersuite int cipherSuite)
-            throws CipherSuiteNotSupportedException;
-
-    /**
-     * Delete a named credential.
-     *
-     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
-     * with payload set to {@code ProofOfDeletion} as defined below:
-     *
-     * <pre>
-     *     ProofOfDeletion = [
-     *          "ProofOfDeletion",            ; tstr
-     *          tstr,                         ; DocType
-     *          bool                          ; true if this is a test credential, should
-     *                                        ; always be false.
-     *      ]
-     * </pre>
-     *
-     * @param credentialName the name of the credential to delete.
-     * @return {@code null} if the credential was not found, the COSE_Sign1 data structure above
-     *     if the credential was found and deleted.
-     * @deprecated Use {@link IdentityCredential#delete(byte[])} instead.
-     */
-    @Deprecated
-    public abstract byte @Nullable [] deleteCredentialByName(@NonNull String credentialName);
-
-    @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    @IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256})
-    public @interface Ciphersuite {
-    }
-
-    /**
-     * Returns the capabilities of the store.
-     *
-     * @return the capabilities of the store
-     */
-    public @NonNull IdentityCredentialStoreCapabilities getCapabilities() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Gets a {@link IdentityCredentialStore} implemented via Android Keystore. This
-     * is also known as the software implementation.
-     *
-     * @return an implementation of {@link IdentityCredentialStore} implemented on top
-     * of Android Keystore.
-     */
-    @SuppressWarnings("deprecation")
-    public static @NonNull IdentityCredentialStore getSoftwareInstance(@NonNull Context context) {
-        return SoftwareIdentityCredentialStore.getInstance(context);
-    }
-
-    /**
-     * Gets a {@link IdentityCredentialStore} implemented via secure hardware using
-     * the
-     * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/master/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl">Identity Credential HAL</a>.
-     *
-     * <p>This only works on devices running Android 11 or later and only if the device has
-     * support for the Identity Credential HAL.
-     *
-     * @return an implementation of {@link IdentityCredentialStore} implemented in
-     * secure hardware or {@code null} if the device doesn't support the Android Identity
-     * Credential HAL.
-     */
-    @SuppressWarnings("deprecation")
-    public static @Nullable IdentityCredentialStore getHardwareInstance(@NonNull Context context) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            return HardwareIdentityCredentialStore.getInstance(context);
-        }
-        return null;
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialStoreCapabilities.java b/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialStoreCapabilities.java
deleted file mode 100644
index 81b54189..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/IdentityCredentialStoreCapabilities.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.icu.util.Calendar;
-
-import org.jspecify.annotations.NonNull;
-
-import java.security.cert.X509Certificate;
-import java.util.Set;
-
-/* TODO: use this
- *
- *  {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE}
- *  {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS}
- *
- * when building against the Android 12 SDK.
- */
-
-/**
- * A class that supports querying the capabilities of a {@link IdentityCredentialStore} as
- * implemented in secure hardware or in software (backed by Android Keystore).
- *
- * <p>Capabilities depend on the Android system features and can be queried using
- * {@link android.content.pm.PackageManager#getSystemAvailableFeatures()} and
- * {@link android.content.pm.PackageManager#hasSystemFeature(String, int)}.
- * The feature names in question are <em>android.hardware.identity_credential and</em>
- * <em>android.hardware.identity_credential_direct_access</em> for the direct access store.
- *
- * <p>Known feature versions include {@link #FEATURE_VERSION_202009} and
- * {@link #FEATURE_VERSION_202101}.
- */
-public class IdentityCredentialStoreCapabilities {
-    IdentityCredentialStoreCapabilities() {}
-
-    /**
-     * The feature version corresponding to features included in the Identity Credential API
-     * shipped in Android 11.
-     */
-    public static final int FEATURE_VERSION_202009 = 202009;
-
-    /**
-     * The feature version corresponding to features included in the Identity Credential API
-     * shipped in Android 12. This feature version adds support for
-     * {@link IdentityCredential#delete(byte[])},
-     * {@link IdentityCredential#update(PersonalizationData)},
-     * {@link IdentityCredential#proveOwnership(byte[])}, and
-     * {@link IdentityCredential#storeStaticAuthenticationData(X509Certificate, Calendar, byte[])}.
-     */
-    public static final int FEATURE_VERSION_202101 = 202101;
-
-    /**
-     * Returns the feature version of the {@link IdentityCredentialStore}.
-     *
-     * @return the feature version.
-     */
-    public int getFeatureVersion() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns whether the credential store is for direct access.
-     *
-     * <p>This always return {@code false} for the software-based store.
-     *
-     * @return {@code true} if credential store is for direct access, {@code false} if not.
-     */
-    public boolean isDirectAccess() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns whether the credential is backed by Secure Hardware.
-     *
-     * <p>This always return {@code false} for the software-based store.
-     *
-     * <p>Note that the software-based store is still using Android Keystore which
-     * itself is backed by secure hardware.
-     *
-     * @return {@code true} if backed by secure hardware, {@code false} if not.
-     */
-    public boolean isHardwareBacked() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Gets a set of supported document types.
-     *
-     * <p>Only the direct-access store may restrict the kind of document types that can be used for
-     * credentials. The default store always supports any document type.
-     *
-     * <p>This always return the empty set for the software-based store.
-     *
-     * @return The supported document types or the empty set if any document type is supported.
-     */
-    public     @NonNull Set<String> getSupportedDocTypes() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns whether {@link IdentityCredential#delete(byte[])} is supported
-     * by the underlying hardware.
-     *
-     * <p>This is supported in feature version {@link #FEATURE_VERSION_202101} and later.
-     *
-     * <p>This is always supported by the software-based store.
-     *
-     * @return {@code true} if supported, {@code false} if not.
-     */
-    public boolean isDeleteSupported() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns true if {@link IdentityCredential#update(PersonalizationData)} is supported
-     * by the underlying hardware.
-     *
-     * <p>This is supported in feature version {@link #FEATURE_VERSION_202101} and later.
-     *
-     * <p>This is always supported by the software-based store.
-     *
-     * @return {@code true} if supported, {@code false} if not.
-     */
-    public boolean isUpdateSupported() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns true if {@link IdentityCredential#proveOwnership(byte[])} is supported by the
-     * underlying hardware.
-     *
-     * <p>This is supported in feature version {@link #FEATURE_VERSION_202101} and later.
-     *
-     * <p>This is always supported by the software-based store.
-     *
-     * @return {@code true} if supported, {@code false} if not.
-     */
-    public boolean isProveOwnershipSupported() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns true if
-     * {@link IdentityCredential#storeStaticAuthenticationData(X509Certificate, Calendar, byte[])}
-     * is supported by the underlying hardware.
-     *
-     * <p>This is supported in feature version {@link #FEATURE_VERSION_202101} and later.
-     *
-     * <p>This is always supported by the software-based store.
-     *
-     * @return {@code true} if supported, {@code false} if not.
-     */
-    public boolean isStaticAuthenticationDataExpirationSupported() {
-        throw new UnsupportedOperationException();
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/InvalidReaderSignatureException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/InvalidReaderSignatureException.java
deleted file mode 100644
index 4294c37..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/InvalidReaderSignatureException.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Thrown if the reader signature is invalid, or it doesn't contain a certificate chain, or if the
- * signature failed to validate.
- */
-public class InvalidReaderSignatureException extends IdentityCredentialException {
-    /**
-     * Constructs a new {@link InvalidReaderSignatureException} exception.
-     *
-     * @param message the detail message.
-     */
-    public InvalidReaderSignatureException(@NonNull String message) {
-        super(message);
-    }
-
-
-    /**
-     * Constructs a new {@link InvalidReaderSignatureException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public InvalidReaderSignatureException(@NonNull String message,
-            @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/InvalidRequestMessageException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/InvalidRequestMessageException.java
deleted file mode 100644
index 601ede3..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/InvalidRequestMessageException.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-import java.util.Map;
-
-/**
- * Thrown if message with the request doesn't satisfy the requirements documented in
- * {@link IdentityCredential#getEntries(byte[], Map, byte[])}.
- */
-public class InvalidRequestMessageException extends IdentityCredentialException {
-    /**
-     * Constructs a new {@link InvalidRequestMessageException} exception.
-     *
-     * @param message the detail message.
-     */
-    public InvalidRequestMessageException(@NonNull String message) {
-        super(message);
-    }
-
-
-    /**
-     * Constructs a new {@link InvalidRequestMessageException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public InvalidRequestMessageException(@NonNull String message,
-            @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/MessageDecryptionException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/MessageDecryptionException.java
deleted file mode 100644
index 9e73c66..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/MessageDecryptionException.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Thrown when failing to decrypt a message from the reader device.
- */
-public class MessageDecryptionException extends IdentityCredentialException {
-
-    /**
-     * Constructs a new {@link MessageDecryptionException} exception.
-     *
-     * @param message the detail message.
-     */
-    public MessageDecryptionException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link MessageDecryptionException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public MessageDecryptionException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/NoAuthenticationKeyAvailableException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/NoAuthenticationKeyAvailableException.java
deleted file mode 100644
index fd9b077..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/NoAuthenticationKeyAvailableException.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Thrown if no dynamic authentication keys are available.
- */
-public class NoAuthenticationKeyAvailableException extends IdentityCredentialException {
-
-    /**
-     * Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
-     *
-     * @param message the detail message.
-     */
-    public NoAuthenticationKeyAvailableException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public NoAuthenticationKeyAvailableException(@NonNull String message,
-            @NonNull Throwable cause) {
-        super(message, cause);
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/PersonalizationData.java b/security/security-identity-credential/src/main/java/androidx/security/identity/PersonalizationData.java
deleted file mode 100644
index e7669bd..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/PersonalizationData.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.annotation.SuppressLint;
-import android.icu.util.Calendar;
-
-import org.jspecify.annotations.NonNull;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-
-/**
- * An object that holds personalization data.
- *
- * This data includes access control profiles and a set of data entries and values, grouped by
- * namespace.
- *
- * This is used to provision data into a {@link WritableIdentityCredential}.
- *
- * @see WritableIdentityCredential#personalize
- */
-public class PersonalizationData {
-
-    PersonalizationData() {
-    }
-
-    @NonNull ArrayList<AccessControlProfile> mProfiles = new ArrayList<>();
-
-    @NonNull LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>();
-
-    Collection<AccessControlProfile> getAccessControlProfiles() {
-        return Collections.unmodifiableCollection(mProfiles);
-    }
-
-    Collection<String> getNamespaces() {
-        return Collections.unmodifiableCollection(mNamespaces.keySet());
-    }
-
-    Collection<NamespaceData> getNamespaceDatas() {
-        return Collections.unmodifiableCollection(mNamespaces.values());
-    }
-
-    NamespaceData getNamespaceData(String namespace) {
-        return mNamespaces.get(namespace);
-    }
-
-    static class NamespaceData {
-
-        protected @NonNull String mNamespace;
-        protected @NonNull LinkedHashMap<String, EntryData> mEntries = new LinkedHashMap<>();
-
-        protected NamespaceData(@NonNull String namespace) {
-            this.mNamespace = namespace;
-        }
-
-        String getNamespaceName() {
-            return mNamespace;
-        }
-
-        Collection<String> getEntryNames() {
-            return Collections.unmodifiableCollection(mEntries.keySet());
-        }
-
-        Collection<AccessControlProfileId> getAccessControlProfileIds(String name) {
-            EntryData value = mEntries.get(name);
-            if (value != null) {
-                return value.mAccessControlProfileIds;
-            }
-            return null;
-        }
-
-        byte[] getEntryValue(String name) {
-            EntryData value = mEntries.get(name);
-            if (value != null) {
-                return value.mValue;
-            }
-            return null;
-        }
-    }
-
-    static class EntryData {
-        byte[] mValue;
-        Collection<AccessControlProfileId> mAccessControlProfileIds;
-
-        EntryData(byte[] value, Collection<AccessControlProfileId> accessControlProfileIds) {
-            this.mValue = value;
-            this.mAccessControlProfileIds = accessControlProfileIds;
-        }
-    }
-
-    /**
-     * A builder for {@link PersonalizationData}.
-     */
-    public static final class Builder {
-        private PersonalizationData mData;
-
-        /**
-         * Creates a new builder for a given namespace.
-         */
-        public Builder() {
-            this.mData = new PersonalizationData();
-        }
-
-        /**
-         * Adds a new entry to the builder.
-         *
-         * <p>This is a low-level method which expects the data to be the bytes of a
-         * <a href="https://tools.ietf.org/html/rfc7049">CBOR</a>
-         * value. When possible, applications should use methods such as
-         * {@link #putEntryString(String, String, Collection, String)} or
-         * {@link #putEntryInteger(String, String, Collection, long)} which
-         * accept normal Java data types.</p>
-         *
-         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
-         * @param name                    The name of the entry, e.g. {@code height}.
-         * @param accessControlProfileIds A set of access control profiles to use.
-         * @param value                   The value to add, in CBOR encoding.
-         * @return The builder.
-         */
-        @SuppressLint("BuilderSetStyle")
-        public @NonNull Builder putEntry(@NonNull String namespace, @NonNull String name,
-                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
-                byte @NonNull [] value) {
-            NamespaceData namespaceData = mData.mNamespaces.get(namespace);
-            if (namespaceData == null) {
-                namespaceData = new NamespaceData(namespace);
-                mData.mNamespaces.put(namespace, namespaceData);
-            }
-            // TODO: validate/verify that value is proper CBOR.
-            namespaceData.mEntries.put(name, new EntryData(value, accessControlProfileIds));
-            return this;
-        }
-
-        /**
-         * Adds a new entry to the builder.
-         *
-         * <p>This is a convenience method which encodes {@code value} as CBOR and adds the
-         * resulting bytes using {@link #putEntry(String, String, Collection, byte[])}. The
-         * resulting CBOR will be major type 3 (text string).</p>
-         *
-         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
-         * @param name                    The name of the entry, e.g. {@code height}.
-         * @param accessControlProfileIds A set of access control profiles to use.
-         * @param value                   The value to add.
-         * @return The builder.
-         */
-        @SuppressLint("BuilderSetStyle")
-        public @NonNull Builder putEntryString(@NonNull String namespace, @NonNull String name,
-                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
-                @NonNull String value) {
-            return putEntry(namespace, name, accessControlProfileIds, Util.cborEncodeString(value));
-        }
-
-        /**
-         * Adds a new entry to the builder.
-         *
-         * <p>This is a convenience method which encodes {@code value} as CBOR and adds the
-         * resulting bytes using {@link #putEntry(String, String, Collection, byte[])}. The
-         * resulting CBOR will be major type 2 (bytestring).</p>
-         *
-         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
-         * @param name                    The name of the entry, e.g. {@code height}.
-         * @param accessControlProfileIds A set of access control profiles to use.
-         * @param value                   The value to add.
-         * @return The builder.
-         */
-        @SuppressLint("BuilderSetStyle")
-        public @NonNull Builder putEntryBytestring(@NonNull String namespace, @NonNull String name,
-                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
-                byte @NonNull [] value) {
-            return putEntry(namespace, name, accessControlProfileIds,
-                    Util.cborEncodeBytestring(value));
-        }
-
-        /**
-         * Adds a new entry to the builder.
-         *
-         * <p>This is a convenience method which encodes {@code value} as CBOR and adds the
-         * resulting bytes using {@link #putEntry(String, String, Collection, byte[])}. The
-         * resulting CBOR will be major type 0 (unsigned integer) if non-negative, otherwise
-         * major type 1 (negative integer).</p>
-         *
-         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
-         * @param name                    The name of the entry, e.g. {@code height}.
-         * @param accessControlProfileIds A set of access control profiles to use.
-         * @param value                   The value to add.
-         * @return The builder.
-         */
-        @SuppressLint("BuilderSetStyle")
-        public @NonNull Builder putEntryInteger(@NonNull String namespace, @NonNull String name,
-                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
-                long value) {
-            return putEntry(namespace, name, accessControlProfileIds, Util.cborEncodeNumber(value));
-        }
-
-        /**
-         * Adds a new entry to the builder.
-         *
-         * <p>This is a convenience method which encodes {@code value} as CBOR and adds the
-         * resulting bytes using {@link #putEntry(String, String, Collection, byte[])}. The
-         * resulting CBOR will be major type 7 (simple value) with additional information 20 (for
-         * the value {@code false}) or 21 (for the value {@code true}).</p>
-         *
-         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
-         * @param name                    The name of the entry, e.g. {@code height}.
-         * @param accessControlProfileIds A set of access control profiles to use.
-         * @param value                   The value to add.
-         * @return The builder.
-         */
-        @SuppressLint("BuilderSetStyle")
-        public @NonNull Builder putEntryBoolean(@NonNull String namespace, @NonNull String name,
-                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
-                boolean value) {
-            return putEntry(namespace, name, accessControlProfileIds,
-                    Util.cborEncodeBoolean(value));
-        }
-
-        /**
-         * Adds a new entry to the builder.
-         *
-         * <p>This is a convenience method which encodes {@code value} as CBOR and adds the
-         * resulting bytes using {@link #putEntry(String, String, Collection, byte[])}. The
-         * resulting CBOR will be a tagged string with tag 0 as per
-         * <a href="https://tools.ietf.org/html/rfc7049#section-2.4.1">Section 2.4.1 of RFC
-         * 7049</a>. This means that the tagged string follows the standard format described in
-         * <a href="https://tools.ietf.org/html/rfc3339">RFC 3339</a>,
-         * as refined by Section 3.3 of
-         * <a href="https://tools.ietf.org/html/rfc4287">RFC 4287></a>.</p>
-         *
-         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
-         * @param name                    The name of the entry, e.g. {@code height}.
-         * @param accessControlProfileIds A set of access control profiles to use.
-         * @param value                   The value to add.
-         * @return The builder.
-         */
-        @SuppressLint("BuilderSetStyle")
-        public @NonNull Builder putEntryCalendar(@NonNull String namespace, @NonNull String name,
-                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
-                @NonNull Calendar value) {
-            return putEntry(namespace, name, accessControlProfileIds,
-                    Util.cborEncode(Util.cborBuildDateTime(value)));
-        }
-
-        /**
-         * Adds a new access control profile to the builder.
-         *
-         * @param profile The access control profile.
-         * @return The builder.
-         */
-        public @NonNull Builder addAccessControlProfile(@NonNull AccessControlProfile profile) {
-            mData.mProfiles.add(profile);
-            return this;
-        }
-
-        /**
-         * Creates a new {@link PersonalizationData} with all the entries added to the builder.
-         *
-         * @return A new {@link PersonalizationData} instance.
-         */
-        public @NonNull PersonalizationData build() {
-            return mData;
-        }
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/ResultData.java b/security/security-identity-credential/src/main/java/androidx/security/identity/ResultData.java
deleted file mode 100644
index 71aebf2..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/ResultData.java
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.icu.util.Calendar;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.RestrictTo;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.lang.annotation.Retention;
-import java.security.PublicKey;
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * An object that contains the result of retrieving data from a credential. This is used to return
- * data requested from a {@link IdentityCredential}.
- */
-public abstract class ResultData {
-
-    /** Value was successfully retrieved. */
-    public static final int STATUS_OK = 0;
-
-    /** Requested entry does not exist. */
-    public static final int STATUS_NO_SUCH_ENTRY = 1;
-
-    /** Requested entry was not requested. */
-    public static final int STATUS_NOT_REQUESTED = 2;
-
-    /** Requested entry wasn't in the request message. */
-    public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
-
-    /** The requested entry was not retrieved because user authentication wasn't performed. */
-    public static final int STATUS_USER_AUTHENTICATION_FAILED = 4;
-
-    /** The requested entry was not retrieved because reader authentication wasn't performed. */
-    public static final int STATUS_READER_AUTHENTICATION_FAILED = 5;
-
-    /**
-     * The requested entry was not retrieved because it was configured without any access
-     * control profile.
-     */
-    public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
-
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    protected ResultData() {}
-
-    /**
-     * Returns a CBOR structure containing the retrieved data.
-     *
-     * <p>This structure - along with the session transcript - may be cryptographically
-     * authenticated to prove to the reader that the data is from a trusted credential and
-     * {@link #getMessageAuthenticationCode()} can be used to get a MAC.
-     *
-     * <p>The CBOR structure which is cryptographically authenticated is the
-     * {@code DeviceAuthenticationBytes} structure according to the following
-     * <a href="https://tools.ietf.org/html/rfc8610">CDDL</a> schema:
-     *
-     * <pre>
-     *   DeviceAuthentication = [
-     *     "DeviceAuthentication",
-     *     SessionTranscript,
-     *     DocType,
-     *     DeviceNameSpacesBytes
-     *   ]
-     *
-     *   DocType = tstr
-     *   SessionTranscript = any
-     *   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
-     *   DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
-     * </pre>
-     *
-     * <p>where
-     *
-     * <pre>
-     *   DeviceNameSpaces = {
-     *     * NameSpace => DeviceSignedItems
-     *   }
-     *
-     *   DeviceSignedItems = {
-     *     + DataItemName => DataItemValue
-     *   }
-     *
-     *   NameSpace = tstr
-     *   DataItemName = tstr
-     *   DataItemValue = any
-     * </pre>
-     *
-     * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure
-     * as defined above.
-     *
-     * @return The bytes of the {@code DeviceNameSpaces} CBOR structure.
-     */
-    public abstract byte @NonNull [] getAuthenticatedData();
-
-    /**
-     * Returns a message authentication code over the {@code DeviceAuthenticationBytes} CBOR
-     * specified in {@link #getAuthenticatedData()}, to prove to the reader that the data
-     * is from a trusted credential.
-     *
-     * <p>The MAC proves to the reader that the data is from a trusted credential. This code is
-     * produced by using the key agreement and key derivation function from the ciphersuite
-     * with the authentication private key and the reader ephemeral public key to compute a
-     * shared message authentication code (MAC) key, then using the MAC function from the
-     * ciphersuite to compute a MAC of the authenticated data. See section 9.2.3.5 of
-     * ISO/IEC 18013-5 for details of this operation.
-     *
-     * <p>If the {@code sessionTranscript} parameter passed to
-     * {@link IdentityCredential#getEntries(byte[], Map, byte[])}  was {@code null}
-     * or the reader ephmeral public key was never set using
-     * {@link IdentityCredential#setReaderEphemeralPublicKey(PublicKey)}, no message
-     * authencation code will be produced and this method will return {@code null}.
-     *
-     * At most one of {@link #getMessageAuthenticationCode()} or {@link #getEcdsaSignature()} is
-     * implemented.
-     *
-     * @return A COSE_Mac0 structure with the message authentication code as described above
-     *         or {@code null} if the conditions specified above are not met.
-     */
-    public abstract byte @Nullable [] getMessageAuthenticationCode();
-
-    /**
-     * Returns a digital signature over the {@code DeviceAuthenticationBytes} CBOR
-     * specified in {@link #getAuthenticatedData()}, to prove to the reader that the data
-     * is from a trusted credential. The signature will be made with one of the provisioned
-     * dynamic authentication keys.
-     *
-     * At most one of {@link #getMessageAuthenticationCode()} or {@link #getEcdsaSignature()} is
-     * implemented.
-     *
-     * @return {@code null} if not implemented, otherwise a COSE_Sign1 structure with the payload
-     *         set to the data returned by {@link #getAuthenticatedData()}.
-     */
-    public abstract byte @Nullable [] getEcdsaSignature();
-
-    /**
-     * Returns the static authentication data associated with the dynamic authentication
-     * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}.
-     *
-     * @return The static authentication data associated with dynamic authentication key used to
-     * MAC the data.
-     */
-    public abstract byte @NonNull [] getStaticAuthenticationData();
-
-    /**
-     * Gets the names of namespaces with retrieved entries.
-     *
-     * @return collection of name of namespaces containing retrieved entries. May be empty if no
-     *     data was retrieved.
-     */
-    public abstract @NonNull Collection<String> getNamespaces();
-
-    /**
-     * Get the names of all entries.
-     *
-     * This includes the name of entries that wasn't successfully retrieved.
-     *
-     * @param namespaceName the namespace name to get entries for.
-     * @return A collection of names or {@code null} if there are no entries for the given
-     *     namespace.
-     */
-    public abstract @Nullable Collection<String> getEntryNames(@NonNull String namespaceName);
-
-    /**
-     * Get the names of all entries that was successfully retrieved.
-     *
-     * This only return entries for which {@link #getStatus(String, String)} will return
-     * {@link #STATUS_OK}.
-     *
-     * @param namespaceName the namespace name to get entries for.
-     * @return A collection of names or {@code null} if there are no entries for the given
-     *     namespace.
-     */
-    public abstract @Nullable Collection<String> getRetrievedEntryNames(
-            @NonNull String namespaceName);
-
-    /**
-     * Gets the status of an entry.
-     *
-     * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
-     * if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested,
-     * {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't
-     * present in the request message,
-     * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
-     * wasn't retrieved because the necessary user authentication wasn't performed,
-     * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain
-     * didn't match the set of certificates the entry was provisioned with, or
-     * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any
-     * access control profiles.
-     *
-     * @param namespaceName the namespace name of the entry.
-     * @param name the name of the entry to get the value for.
-     * @return the status indicating whether the value was retrieved and if not, why.
-     */
-    public abstract @Status int getStatus(@NonNull String namespaceName, @NonNull String name);
-
-    /**
-     * Gets the raw CBOR data for the value of an entry.
-     *
-     * This should only be called on an entry for which the {@link #getStatus(String, String)}
-     * method returns {@link #STATUS_OK}.
-     *
-     * @param namespaceName the namespace name of the entry.
-     * @param name the name of the entry to get the value for.
-     * @return the raw CBOR data or {@code null} if no entry with the given name exists.
-     */
-    public abstract byte @Nullable [] getEntry(@NonNull String namespaceName, @NonNull String name);
-
-    /**
-     * Gets the value of an entry.
-     *
-     * This should only be called on an entry for which the {@link #getStatus(String, String)}
-     * method returns {@link #STATUS_OK}.
-     *
-     * @param namespaceName the namespace name of the entry.
-     * @param name the name of the entry to get the value for.
-     * @return a {@code String} or {@code null} if no entry with the given name exists.
-     */
-    public @Nullable String getEntryString(@NonNull String namespaceName, @NonNull String name) {
-        byte[] value = getEntry(namespaceName, name);
-        if (value == null) {
-            return null;
-        }
-        return Util.cborDecodeString(value);
-    }
-
-    /**
-     * Gets the value of an entry.
-     *
-     * This should only be called on an entry for which the {@link #getStatus(String, String)}
-     * method returns {@link #STATUS_OK}.
-     *
-     * @param namespaceName the namespace name of the entry.
-     * @param name the name of the entry to get the value for.
-     * @return a {@code byte[]} or {@code null} if no entry with the given name exists.
-     */
-    public byte @Nullable [] getEntryBytestring(@NonNull String namespaceName,
-            @NonNull String name) {
-        byte[] value = getEntry(namespaceName, name);
-        if (value == null) {
-            return null;
-        }
-        return Util.cborDecodeByteString(value);
-    }
-
-    /**
-     * Gets the value of an entry.
-     *
-     * This should only be called on an entry for which the {@link #getStatus(String, String)}
-     * method returns {@link #STATUS_OK}.
-     *
-     * @param namespaceName the namespace name of the entry.
-     * @param name the name of the entry to get the value for.
-     * @return a {@code long} or 0 if no entry with the given name exists.
-     */
-    public long getEntryInteger(@NonNull String namespaceName, @NonNull String name) {
-        byte[] value = getEntry(namespaceName, name);
-        if (value == null) {
-            return 0;
-        }
-        return Util.cborDecodeLong(value);
-    }
-
-    /**
-     * Gets the value of an entry.
-     *
-     * This should only be called on an entry for which the {@link #getStatus(String, String)}
-     * method returns {@link #STATUS_OK}.
-     *
-     * @param namespaceName the namespace name of the entry.
-     * @param name the name of the entry to get the value for.
-     * @return a {@code boolean} or {@code false} if no entry with the given name exists.
-     */
-    public boolean getEntryBoolean(@NonNull String namespaceName, @NonNull String name) {
-        byte[] value = getEntry(namespaceName, name);
-        if (value == null) {
-            return false;
-        }
-        return Util.cborDecodeBoolean(value);
-    }
-
-    /**
-     * Gets the value of an entry.
-     *
-     * This should only be called on an entry for which the {@link #getStatus(String, String)}
-     * method returns {@link #STATUS_OK}.
-     *
-     * @param namespaceName the namespace name of the entry.
-     * @param name the name of the entry to get the value for.
-     * @return a {@code Calendar} or {@code null} if no entry with the given name exists.
-     */
-    public     @Nullable Calendar getEntryCalendar(@NonNull String namespaceName,
-            @NonNull String name) {
-        byte[] value = getEntry(namespaceName, name);
-        if (value == null) {
-            return null;
-        }
-        return Util.cborDecodeDateTime(value);
-    }
-
-    /**
-     * The type of the entry status.
-     */
-    @Retention(SOURCE)
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED, STATUS_NOT_IN_REQUEST_MESSAGE,
-                        STATUS_USER_AUTHENTICATION_FAILED, STATUS_READER_AUTHENTICATION_FAILED,
-                        STATUS_NO_ACCESS_CONTROL_PROFILES})
-    public @interface Status {
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/SimpleIdentityCredentialStoreCapabilities.java b/security/security-identity-credential/src/main/java/androidx/security/identity/SimpleIdentityCredentialStoreCapabilities.java
deleted file mode 100644
index 238567f..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/SimpleIdentityCredentialStoreCapabilities.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-import java.util.Set;
-
-class SimpleIdentityCredentialStoreCapabilities extends IdentityCredentialStoreCapabilities {
-
-    boolean mIsDirectAccess;
-    int mFeatureVersion;
-    boolean mIsHardwareBacked;
-    Set<String> mSupportedDocTypes;
-    boolean mIsDeleteCredentialSupported;
-    boolean mIsUpdateCredentialSupported;
-    boolean mIsProveOwnershipSupported;
-    boolean mIsStaticAuthenticationDataExpirationDateSupported;
-
-    SimpleIdentityCredentialStoreCapabilities(boolean isDirectAccess,
-            int featureVersion,
-            boolean isHardwareBacked,
-            Set<String> supportedDocTypes,
-            boolean isDeleteCredentialSupported,
-            boolean isUpdateCredentialSupported,
-            boolean isProveOwnershipSupported,
-            boolean isStaticAuthenticationDataExpirationDateSupported) {
-        mIsDirectAccess = isDirectAccess;
-        mFeatureVersion = featureVersion;
-        mIsHardwareBacked = isHardwareBacked;
-        mSupportedDocTypes = supportedDocTypes;
-        mIsDeleteCredentialSupported = isDeleteCredentialSupported;
-        mIsProveOwnershipSupported = isProveOwnershipSupported;
-        mIsUpdateCredentialSupported = isUpdateCredentialSupported;
-        mIsStaticAuthenticationDataExpirationDateSupported =
-                isStaticAuthenticationDataExpirationDateSupported;
-    }
-
-    static SimpleIdentityCredentialStoreCapabilities getFeatureVersion202009(
-            boolean isDirectAccess,
-            boolean isHardwareBacked,
-            Set<String> supportedDocTypesSet) {
-        return new SimpleIdentityCredentialStoreCapabilities(
-                isDirectAccess,
-                IdentityCredentialStoreCapabilities.FEATURE_VERSION_202009,
-                isHardwareBacked,
-                supportedDocTypesSet,
-                false,
-                false,
-                false,
-                false);
-    }
-
-    static SimpleIdentityCredentialStoreCapabilities getFeatureVersion202101(
-            boolean isDirectAccess,
-            boolean isHardwareBacked,
-            Set<String> supportedDocTypesSet) {
-        return new SimpleIdentityCredentialStoreCapabilities(
-                isDirectAccess,
-                IdentityCredentialStoreCapabilities.FEATURE_VERSION_202101,
-                isHardwareBacked,
-                supportedDocTypesSet,
-                true,
-                true,
-                true,
-                true);
-    }
-
-    @Override
-    public boolean isDirectAccess() {
-        return mIsDirectAccess;
-    }
-
-    @Override
-    public int getFeatureVersion() {
-        return mFeatureVersion;
-    }
-
-    @Override
-    public boolean isHardwareBacked() {
-        return mIsHardwareBacked;
-    }
-
-    @Override
-    public @NonNull Set<String> getSupportedDocTypes() {
-        return mSupportedDocTypes;
-    }
-
-    @Override
-    public boolean isDeleteSupported() {
-        return mIsDeleteCredentialSupported;
-    }
-
-    @Override
-    public boolean isUpdateSupported() {
-        return mIsUpdateCredentialSupported;
-    }
-
-    @Override
-    public boolean isProveOwnershipSupported() {
-        return mIsProveOwnershipSupported;
-    }
-
-    @Override
-    public boolean isStaticAuthenticationDataExpirationSupported() {
-        return mIsStaticAuthenticationDataExpirationDateSupported;
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/SimpleResultData.java b/security/security-identity-credential/src/main/java/androidx/security/identity/SimpleResultData.java
deleted file mode 100644
index 30f931e..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/SimpleResultData.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * An implementation of {@link ResultData} which stores a copy of all data.
- */
-class SimpleResultData extends ResultData {
-
-    protected byte[] mStaticAuthenticationData = null;
-    protected byte[] mAuthenticatedData = null;
-    protected byte[] mEcdsaSignature = null;
-    protected byte[] mMessageAuthenticationCode = null;
-
-    protected Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>();
-
-    private static class EntryData {
-        @Status
-        int mStatus;
-        byte[] mValue;
-
-        EntryData(byte[] value, @Status int status) {
-            this.mValue = value;
-            this.mStatus = status;
-        }
-    }
-
-    SimpleResultData() {}
-
-    @Override
-    public byte @NonNull [] getAuthenticatedData() {
-        return mAuthenticatedData;
-    }
-
-    @Override
-    public byte @Nullable [] getMessageAuthenticationCode() {
-        return mMessageAuthenticationCode;
-    }
-
-    @Override
-    public byte @Nullable [] getEcdsaSignature() {
-        return mEcdsaSignature;
-    }
-
-    @Override
-    public byte @NonNull [] getStaticAuthenticationData() {
-        return mStaticAuthenticationData;
-    }
-
-    @Override
-    public @NonNull Collection<String> getNamespaces() {
-        return Collections.unmodifiableCollection(mData.keySet());
-    }
-
-    @Override
-    public @Nullable Collection<String> getEntryNames(@NonNull String namespaceName) {
-        Map<String, EntryData> innerMap = mData.get(namespaceName);
-        if (innerMap == null) {
-            return null;
-        }
-        return Collections.unmodifiableCollection(innerMap.keySet());
-    }
-
-    @Override
-    public @Nullable Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) {
-        Map<String, EntryData> innerMap = mData.get(namespaceName);
-        if (innerMap == null) {
-            return null;
-        }
-        ArrayList<String> result = new ArrayList<String>();
-        for (Map.Entry<String, EntryData> entry : innerMap.entrySet()) {
-            if (entry.getValue().mStatus == STATUS_OK) {
-                result.add(entry.getKey());
-            }
-        }
-        return result;
-    }
-
-    private EntryData getEntryData(@NonNull String namespaceName, @NonNull String name) {
-        Map<String, EntryData> innerMap = mData.get(namespaceName);
-        if (innerMap == null) {
-            return null;
-        }
-        return innerMap.get(name);
-    }
-
-    @Override
-    @Status
-    public int getStatus(@NonNull String namespaceName, @NonNull String name) {
-        EntryData value = getEntryData(namespaceName, name);
-        if (value == null) {
-            return STATUS_NOT_REQUESTED;
-        }
-        return value.mStatus;
-    }
-
-    @Override
-    public byte @Nullable [] getEntry(@NonNull String namespaceName, @NonNull String name) {
-        EntryData value = getEntryData(namespaceName, name);
-        if (value == null) {
-            return null;
-        }
-        return value.mValue;
-    }
-
-    static class Builder {
-        private SimpleResultData mResultData;
-
-        Builder() {
-            this.mResultData = new SimpleResultData();
-        }
-
-        Builder setStaticAuthenticationData(byte[] staticAuthenticationData) {
-            this.mResultData.mStaticAuthenticationData = staticAuthenticationData;
-            return this;
-        }
-
-        Builder setAuthenticatedData(byte[] authenticatedData) {
-            this.mResultData.mAuthenticatedData = authenticatedData;
-            return this;
-        }
-
-        Builder setEcdsaSignature(byte[] ecdsaSignature) {
-            this.mResultData.mEcdsaSignature = ecdsaSignature;
-            return this;
-        }
-
-        Builder setMessageAuthenticationCode(byte [] messageAuthenticationCode) {
-            this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode;
-            return this;
-        }
-
-        private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) {
-            Map<String, EntryData> innerMap = mResultData.mData.get(namespaceName);
-            if (innerMap == null) {
-                innerMap = new LinkedHashMap<>();
-                mResultData.mData.put(namespaceName, innerMap);
-            }
-            return innerMap;
-        }
-
-        Builder addEntry(String namespaceName, String name, byte[] value) {
-            Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
-            innerMap.put(name, new EntryData(value, STATUS_OK));
-            return this;
-        }
-
-        Builder addErrorStatus(String namespaceName, String name, @Status int status) {
-            Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
-            innerMap.put(name, new EntryData(null, status));
-            return this;
-        }
-
-        SimpleResultData build() {
-            return mResultData;
-        }
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareIdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareIdentityCredential.java
deleted file mode 100644
index 2209646..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareIdentityCredential.java
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.content.Context;
-import android.icu.util.Calendar;
-import android.security.keystore.KeyProperties;
-import android.util.Pair;
-
-import androidx.biometric.BiometricPrompt;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.UnrecoverableEntryException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECGenParameterSpec;
-import java.security.spec.ECPoint;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyAgreement;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborDecoder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.builder.MapBuilder;
-import co.nstant.in.cbor.model.DataItem;
-import co.nstant.in.cbor.model.Map;
-import co.nstant.in.cbor.model.UnicodeString;
-
-class SoftwareIdentityCredential extends IdentityCredential {
-
-    private static final String TAG = "SWIdentityCredential";
-    private String mCredentialName;
-
-    private Context mContext;
-
-    private CredentialData mData;
-
-    private KeyPair mEphemeralKeyPair = null;
-
-    private SecretKey mSKDevice = null;
-    private SecretKey mSKReader = null;
-
-    private int mSKDeviceCounter;
-    private int mSKReaderCounter;
-    private byte[] mAuthKeyAssociatedData = null;
-    private PrivateKey mAuthKey = null;
-    private BiometricPrompt.CryptoObject mCryptoObject = null;
-
-    private PublicKey mReaderEphemeralPublicKey = null;
-    private byte[] mSessionTranscript = null;
-
-    SoftwareIdentityCredential(Context context, String credentialName,
-            @IdentityCredentialStore.Ciphersuite int cipherSuite)
-            throws CipherSuiteNotSupportedException {
-        if (cipherSuite
-                != IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256) {
-            throw new CipherSuiteNotSupportedException("Unsupported Cipher Suite");
-        }
-        mContext = context;
-        mCredentialName = credentialName;
-    }
-
-    boolean loadData() {
-        mData = CredentialData.loadCredentialData(mContext, mCredentialName);
-        if (mData == null) {
-            return false;
-        }
-        return true;
-    }
-
-    static byte[] delete(Context context, String credentialName) {
-        return CredentialData.delete(context, credentialName, null);
-    }
-
-    @Override
-    public byte @NonNull [] delete(byte @NonNull [] challenge)  {
-        return CredentialData.delete(mContext, mCredentialName, challenge);
-    }
-
-    @Override
-    public byte @NonNull [] proveOwnership(byte @NonNull [] challenge)  {
-        return mData.proveOwnership(challenge);
-    }
-
-
-
-    // This only extracts the requested namespaces, not DocType or RequestInfo. We
-    // can do this later if it's needed.
-    private static HashMap<String, Collection<String>> parseRequestMessage(
-            byte @Nullable [] requestMessage) {
-        HashMap<String, Collection<String>> result = new HashMap<>();
-
-        if (requestMessage == null) {
-            return result;
-        }
-
-        try {
-            ByteArrayInputStream bais = new ByteArrayInputStream(requestMessage);
-            List<DataItem> dataItems = new CborDecoder(bais).decode();
-            if (dataItems.size() != 1) {
-                throw new RuntimeException("Expected 1 item, found " + dataItems.size());
-            }
-            if (!(dataItems.get(0) instanceof Map)) {
-                throw new RuntimeException("Item is not a map");
-            }
-            Map map = (Map) dataItems.get(0);
-
-            DataItem nameSpaces = map.get(new UnicodeString("nameSpaces"));
-            if (!(nameSpaces instanceof Map)) {
-                throw new RuntimeException(
-                        "nameSpaces entry not found or not map");
-            }
-
-            for (DataItem keyItem : ((Map) nameSpaces).getKeys()) {
-                if (!(keyItem instanceof UnicodeString)) {
-                    throw new RuntimeException(
-                            "Key item in NameSpaces map not UnicodeString");
-                }
-                String nameSpace = ((UnicodeString) keyItem).getString();
-                ArrayList<String> names = new ArrayList<>();
-
-                DataItem valueItem = ((Map) nameSpaces).get(keyItem);
-                if (!(valueItem instanceof Map)) {
-                    throw new RuntimeException(
-                            "Value item in NameSpaces map not Map");
-                }
-                for (DataItem item : ((Map) valueItem).getKeys()) {
-                    if (!(item instanceof UnicodeString)) {
-                        throw new RuntimeException(
-                                "Item in nameSpaces array not UnicodeString");
-                    }
-                    names.add(((UnicodeString) item).getString());
-                    // TODO: check that value is a boolean
-                }
-                result.put(nameSpace, names);
-            }
-
-        } catch (CborException e) {
-            throw new RuntimeException("Error decoding request message", e);
-        }
-        return result;
-    }
-
-    @Override
-    public @NonNull KeyPair createEphemeralKeyPair() {
-        if (mEphemeralKeyPair == null) {
-            try {
-                KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
-                ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
-                kpg.initialize(ecSpec);
-                mEphemeralKeyPair = kpg.generateKeyPair();
-            } catch (NoSuchAlgorithmException
-                    | InvalidAlgorithmParameterException e) {
-                throw new RuntimeException("Error generating ephemeral key", e);
-            }
-        }
-        return mEphemeralKeyPair;
-    }
-
-    @Override
-    public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) {
-        mReaderEphemeralPublicKey = readerEphemeralPublicKey;
-    }
-
-    @Override
-    public void setSessionTranscript(byte @NonNull [] sessionTranscript) {
-        if (mSessionTranscript != null) {
-            throw new RuntimeException("SessionTranscript already set");
-        }
-        mSessionTranscript = sessionTranscript.clone();
-    }
-
-    private void ensureSessionEncryptionKey() {
-        if (mSKDevice != null) {
-            return;
-        }
-        if (mReaderEphemeralPublicKey == null) {
-            throw new RuntimeException("Reader ephemeral key not set");
-        }
-        if (mSessionTranscript == null) {
-            throw new RuntimeException("Session transcript not set");
-        }
-        try {
-            KeyAgreement ka = KeyAgreement.getInstance("ECDH");
-            ka.init(mEphemeralKeyPair.getPrivate());
-            ka.doPhase(mReaderEphemeralPublicKey, true);
-            byte[] sharedSecret = ka.generateSecret();
-
-            byte[] sessionTranscriptBytes =
-                    Util.cborEncode(Util.cborBuildTaggedByteString(mSessionTranscript));
-            byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
-
-            byte[] info = new byte[] {'S', 'K', 'D', 'e', 'v', 'i', 'c', 'e'};
-            byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-            mSKDevice = new SecretKeySpec(derivedKey, "AES");
-
-            info = new byte[] {'S', 'K', 'R', 'e', 'a', 'd', 'e', 'r'};
-            derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-            mSKReader = new SecretKeySpec(derivedKey, "AES");
-
-            mSKDeviceCounter = 1;
-            mSKReaderCounter = 1;
-
-        } catch (InvalidKeyException
-                | NoSuchAlgorithmException e) {
-            throw new RuntimeException("Error performing key agreement", e);
-        }
-    }
-
-    @Override
-    public     byte @NonNull [] encryptMessageToReader(byte @NonNull [] messagePlaintext) {
-        ensureSessionEncryptionKey();
-        byte[] messageCiphertextAndAuthTag = null;
-        try {
-            ByteBuffer iv = ByteBuffer.allocate(12);
-            iv.putInt(0, 0x00000000);
-            iv.putInt(4, 0x00000001);
-            iv.putInt(8, mSKDeviceCounter);
-            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array());
-            cipher.init(Cipher.ENCRYPT_MODE, mSKDevice, encryptionParameterSpec);
-            messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext);
-        } catch (BadPaddingException
-                | IllegalBlockSizeException
-                | NoSuchPaddingException
-                | InvalidKeyException
-                | NoSuchAlgorithmException
-                | InvalidAlgorithmParameterException e) {
-            throw new RuntimeException("Error encrypting message", e);
-        }
-        mSKDeviceCounter += 1;
-        return messageCiphertextAndAuthTag;
-    }
-
-    @Override
-    public     byte @NonNull [] decryptMessageFromReader(byte @NonNull [] messageCiphertext)
-            throws MessageDecryptionException {
-        ensureSessionEncryptionKey();
-        ByteBuffer iv = ByteBuffer.allocate(12);
-        iv.putInt(0, 0x00000000);
-        iv.putInt(4, 0x00000000);
-        iv.putInt(8, mSKReaderCounter);
-        byte[] plainText = null;
-        try {
-            final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            cipher.init(Cipher.DECRYPT_MODE, mSKReader, new GCMParameterSpec(128,
-                    iv.array()));
-            plainText = cipher.doFinal(messageCiphertext);
-        } catch (BadPaddingException
-                | IllegalBlockSizeException
-                | InvalidAlgorithmParameterException
-                | InvalidKeyException
-                | NoSuchAlgorithmException
-                | NoSuchPaddingException e) {
-            throw new MessageDecryptionException("Error decrypting message", e);
-        }
-        mSKReaderCounter += 1;
-        return plainText;
-    }
-
-    @Override
-    public     @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain() {
-        return mData.getCredentialKeyCertificateChain();
-    }
-
-    private void ensureCryptoObject() {
-        String aliasForCryptoObject = mData.getPerReaderSessionKeyAlias();
-        if (aliasForCryptoObject.isEmpty()) {
-            // This happens if there are no ACPs with user-auth.
-            return;
-        }
-        try {
-            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            KeyStore.Entry entry = ks.getEntry(aliasForCryptoObject, null);
-            SecretKey perReaderSessionKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey();
-            Cipher perReaderSessionCipher = Cipher.getInstance("AES/GCM/NoPadding");
-            perReaderSessionCipher.init(Cipher.ENCRYPT_MODE, perReaderSessionKey);
-            mCryptoObject = new BiometricPrompt.CryptoObject(perReaderSessionCipher);
-        } catch (CertificateException
-                | NoSuchPaddingException
-                | InvalidKeyException
-                | IOException
-                | NoSuchAlgorithmException
-                | KeyStoreException
-                | UnrecoverableEntryException e) {
-            throw new RuntimeException("Error creating Cipher for perReaderSessionKey", e);
-        }
-    }
-
-    private void ensureAuthKey() throws NoAuthenticationKeyAvailableException {
-        if (mAuthKey != null) {
-            return;
-        }
-
-        Pair<PrivateKey, byte[]> keyAndStaticData = mData.selectAuthenticationKey(
-                mAllowUsingExhaustedKeys,
-                mAllowUsingExpiredKeys);
-        if (keyAndStaticData == null) {
-            throw new NoAuthenticationKeyAvailableException(
-                    "No authentication key available for signing");
-        }
-        mAuthKey = keyAndStaticData.first;
-        mAuthKeyAssociatedData = keyAndStaticData.second;
-    }
-
-    boolean mAllowUsingExhaustedKeys = true;
-    boolean mAllowUsingExpiredKeys = false;
-
-    @Override
-    public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
-        mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
-    }
-
-    @Override
-    public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
-        mAllowUsingExpiredKeys = allowUsingExpiredKeys;
-    }
-
-    @Override
-    public BiometricPrompt.@Nullable CryptoObject getCryptoObject() {
-        ensureCryptoObject();
-        return mCryptoObject;
-    }
-
-    private boolean hasEphemeralKeyInSessionTranscript(byte @NonNull [] sessionTranscript) {
-        if (mEphemeralKeyPair == null) {
-            return false;
-        }
-        // The place to search for X and Y is in the DeviceEngagementBytes which is
-        // the first bstr in the SessionTranscript array but it's just as good to just search
-        // in the given SessionTranscript bytes (just a bit more work).
-        ECPoint w = ((ECPublicKey) mEphemeralKeyPair.getPublic()).getW();
-
-        // X and Y are always positive so for interop we remove any leading zeroes
-        // inserted by the BigInteger encoder.
-        byte[] x = Util.stripLeadingZeroes(w.getAffineX().toByteArray());
-        byte[] y = Util.stripLeadingZeroes(w.getAffineY().toByteArray());
-        if (!Util.hasSubByteArray(sessionTranscript, x)
-                && !Util.hasSubByteArray(sessionTranscript, y)) {
-            return false;
-        }
-        return true;
-    }
-
-
-    private boolean mDidUserAuthResult = false;
-
-    private boolean mDidUserAuthAlreadyCalled = false;
-
-    // Returns true if the user authenticated using the cryptoObject we gave the
-    // app in the getCryptoObject() method.
-    //
-    private boolean didUserAuth() {
-        if (!mDidUserAuthAlreadyCalled) {
-            mDidUserAuthResult = didUserAuthNoCache();
-            mDidUserAuthAlreadyCalled = true;
-        }
-        return mDidUserAuthResult;
-    }
-
-    private boolean didUserAuthNoCache() {
-        if (mCryptoObject == null) {
-            // Certainly didn't auth since they didn't even get a cryptoObject (or no ACPs
-            // are using user-auth).
-            return false;
-        }
-        try {
-            Cipher cipher = mCryptoObject.getCipher();
-            byte[] clearText = new byte[16];
-            // We don't care about the cipherText, only whether the key is unlocked.
-            cipher.doFinal(clearText);
-        } catch (IllegalBlockSizeException
-                | BadPaddingException e) {
-            // If we get here, it's because the user didn't auth.
-            return false;
-        }
-        return true;
-    }
-
-
-    @Override
-    public @NonNull ResultData getEntries(
-            byte @Nullable [] requestMessage,
-            java.util.@NonNull Map<String, Collection<String>> entriesToRequest,
-            byte @Nullable [] readerSignature)
-            throws NoAuthenticationKeyAvailableException,
-            InvalidReaderSignatureException, InvalidRequestMessageException,
-            EphemeralPublicKeyNotFoundException {
-
-        if (mSessionTranscript != null && !hasEphemeralKeyInSessionTranscript(mSessionTranscript)) {
-            throw new EphemeralPublicKeyNotFoundException(
-                    "Did not find ephemeral public key X and Y coordinates in SessionTranscript "
-                            + "(make sure leading zeroes are not used)");
-        }
-
-        HashMap<String, Collection<String>> requestMessageMap = parseRequestMessage(requestMessage);
-
-        // Check reader signature, if requested.
-        Collection<X509Certificate> readerCertChain = null;
-        if (readerSignature != null) {
-            if (mSessionTranscript == null) {
-                throw new InvalidReaderSignatureException(
-                        "readerSignature non-null but sessionTranscript was null");
-            }
-            if (requestMessage == null) {
-                throw new InvalidReaderSignatureException(
-                        "readerSignature non-null but requestMessage was null");
-            }
-
-            DataItem readerSignatureItem = Util.cborDecode(readerSignature);
-            readerCertChain = Util.coseSign1GetX5Chain(readerSignatureItem);
-            if (readerCertChain.size() < 1) {
-                throw new InvalidReaderSignatureException("No x5chain element in reader signature");
-            }
-            if (!Util.validateCertificateChain(readerCertChain)) {
-                throw new InvalidReaderSignatureException("Error validating certificate chain");
-            }
-            PublicKey readerTopmostPublicKey = readerCertChain.iterator().next().getPublicKey();
-
-            byte[] readerAuthentication = Util.cborEncode(new CborBuilder()
-                    .addArray()
-                    .add("ReaderAuthentication")
-                    .add(Util.cborDecode(mSessionTranscript))
-                    .add(Util.cborBuildTaggedByteString(requestMessage))
-                    .end()
-                    .build().get(0));
-
-            byte[] readerAuthenticationBytes =
-                    Util.cborEncode(Util.cborBuildTaggedByteString(readerAuthentication));
-            if (!Util.coseSign1CheckSignature(
-                    Util.cborDecode(readerSignature),
-                    readerAuthenticationBytes,
-                    readerTopmostPublicKey)) {
-                throw new InvalidReaderSignatureException("Reader signature check failed");
-            }
-        }
-
-        SimpleResultData.Builder resultBuilder = new SimpleResultData.Builder();
-
-        CborBuilder deviceNameSpaceBuilder = new CborBuilder();
-        MapBuilder<CborBuilder> deviceNameSpacesMapBuilder = deviceNameSpaceBuilder.addMap();
-
-
-        retrieveValues(requestMessage,
-                requestMessageMap,
-                readerCertChain,
-                entriesToRequest,
-                resultBuilder,
-                deviceNameSpacesMapBuilder);
-
-        ByteArrayOutputStream adBaos = new ByteArrayOutputStream();
-        CborEncoder adEncoder = new CborEncoder(adBaos);
-        DataItem deviceNameSpace = deviceNameSpaceBuilder.build().get(0);
-        try {
-            adEncoder.encode(deviceNameSpace);
-        } catch (CborException e) {
-            throw new RuntimeException("Error encoding deviceNameSpace", e);
-        }
-        byte[] authenticatedData = adBaos.toByteArray();
-        resultBuilder.setAuthenticatedData(authenticatedData);
-
-        // If the sessionTranscript is available, create the ECDSA signature
-        // so the reader can authenticate the DeviceNamespaces CBOR. Also
-        // return the staticAuthenticationData associated with the key chosen
-        // to be used for signing.
-        //
-        // Unfortunately we can't do MACing because Android Keystore doesn't
-        // implement ECDH. So we resort to ECSDA signing instead.
-        if (mSessionTranscript != null) {
-            ensureAuthKey();
-            resultBuilder.setStaticAuthenticationData(mAuthKeyAssociatedData);
-
-            byte[] deviceAuthentication = Util.cborEncode(new CborBuilder()
-                    .addArray()
-                    .add("DeviceAuthentication")
-                    .add(Util.cborDecode(mSessionTranscript))
-                    .add(mData.getDocType())
-                    .add(Util.cborBuildTaggedByteString(authenticatedData))
-                    .end()
-                    .build().get(0));
-
-            byte[] deviceAuthenticationBytes =
-                    Util.cborEncode(Util.cborBuildTaggedByteString(deviceAuthentication));
-
-            try {
-                Signature authKeySignature = Signature.getInstance("SHA256withECDSA");
-                authKeySignature.initSign(mAuthKey);
-                resultBuilder.setEcdsaSignature(
-                        Util.cborEncode(Util.coseSign1Sign(authKeySignature,
-                                null,
-                                deviceAuthenticationBytes,
-                                null)));
-            } catch (NoSuchAlgorithmException
-                    | InvalidKeyException
-                    | CertificateEncodingException e) {
-                throw new RuntimeException("Error signing DeviceAuthentication CBOR", e);
-            }
-        }
-
-        return resultBuilder.build();
-    }
-
-    private void retrieveValues(
-            byte[] requestMessage,
-            HashMap<String, Collection<String>> requestMessageMap,
-            Collection<X509Certificate> readerCertChain,
-            java.util.Map<String, Collection<String>> entriesToRequest,
-            SimpleResultData.Builder resultBuilder,
-            MapBuilder<CborBuilder> deviceNameSpacesMapBuilder) {
-
-        for (String namespaceName : entriesToRequest.keySet()) {
-            Collection<String> entriesToRequestInNamespace = entriesToRequest.get(namespaceName);
-
-            PersonalizationData.NamespaceData loadedNamespace = mData.lookupNamespaceData(
-                    namespaceName);
-
-            Collection<String> requestMessageNamespace = requestMessageMap.get(namespaceName);
-
-            retrieveValuesForNamespace(resultBuilder,
-                    deviceNameSpacesMapBuilder,
-                    entriesToRequestInNamespace,
-                    requestMessage,
-                    requestMessageNamespace,
-                    readerCertChain,
-                    namespaceName,
-                    loadedNamespace);
-        }
-    }
-
-    private void retrieveValuesForNamespace(
-            SimpleResultData.Builder resultBuilder,
-            MapBuilder<CborBuilder> deviceNameSpacesMapBuilder,
-            Collection<String> entriesToRequestInNamespace,
-            byte[] requestMessage,
-            Collection<String> requestMessageNamespace,
-            Collection<X509Certificate> readerCertChain,
-            String namespaceName,
-            PersonalizationData.NamespaceData loadedNamespace) {
-        MapBuilder<MapBuilder<CborBuilder>> deviceNamespaceBuilder = null;
-
-        for (String requestedEntryName : entriesToRequestInNamespace) {
-
-            byte[] value = null;
-            if (loadedNamespace != null) {
-                value = loadedNamespace.getEntryValue(requestedEntryName);
-            }
-
-            if (value == null) {
-                resultBuilder.addErrorStatus(namespaceName,
-                        requestedEntryName,
-                        ResultData.STATUS_NO_SUCH_ENTRY);
-                continue;
-            }
-
-            if (requestMessage != null) {
-                if (requestMessageNamespace == null
-                        || !requestMessageNamespace.contains(requestedEntryName)) {
-                    resultBuilder.addErrorStatus(namespaceName,
-                            requestedEntryName,
-                            ResultData.STATUS_NOT_IN_REQUEST_MESSAGE);
-                    continue;
-                }
-            }
-
-            Collection<AccessControlProfileId> accessControlProfileIds =
-                    loadedNamespace.getAccessControlProfileIds(requestedEntryName);
-
-            @ResultData.Status
-            int status = checkAccess(accessControlProfileIds, readerCertChain);
-            if (status != ResultData.STATUS_OK) {
-                resultBuilder.addErrorStatus(namespaceName, requestedEntryName, status);
-                continue;
-            }
-
-            resultBuilder.addEntry(namespaceName, requestedEntryName, value);
-            if (deviceNamespaceBuilder == null) {
-                deviceNamespaceBuilder = deviceNameSpacesMapBuilder.putMap(
-                        namespaceName);
-            }
-            DataItem dataItem = Util.cborDecode(value);
-            deviceNamespaceBuilder.put(new UnicodeString(requestedEntryName), dataItem);
-        }
-    }
-
-    @ResultData.Status
-    private int checkAccessSingleProfile(AccessControlProfile profile,
-            Collection<X509Certificate> readerCertChain) {
-
-        if (profile.isUserAuthenticationRequired()) {
-            if (!mData.checkUserAuthentication(profile.getAccessControlProfileId(),
-                    didUserAuth())) {
-                return ResultData.STATUS_USER_AUTHENTICATION_FAILED;
-            }
-        }
-
-        X509Certificate profileCert = profile.getReaderCertificate();
-        if (profileCert != null) {
-            if (readerCertChain == null) {
-                return ResultData.STATUS_READER_AUTHENTICATION_FAILED;
-            }
-
-            // Need to check if the cert required by the profile is in the given chain.
-            boolean foundMatchingCert = false;
-            byte[] profilePublicKeyEncoded = profileCert.getPublicKey().getEncoded();
-            for (X509Certificate readerCert : readerCertChain) {
-                byte[] readerCertPublicKeyEncoded = readerCert.getPublicKey().getEncoded();
-                if (Arrays.equals(profilePublicKeyEncoded, readerCertPublicKeyEncoded)) {
-                    foundMatchingCert = true;
-                    break;
-                }
-            }
-            if (!foundMatchingCert) {
-                return ResultData.STATUS_READER_AUTHENTICATION_FAILED;
-            }
-        }
-
-        // Neither user auth nor reader auth required. This means access is always granted.
-        return ResultData.STATUS_OK;
-    }
-
-    @ResultData.Status
-    private int checkAccess(Collection<AccessControlProfileId> accessControlProfileIds,
-            Collection<X509Certificate> readerCertChain) {
-        // Access is granted if at least one of the profiles grants access.
-        //
-        // If an item is configured without any profiles, access is denied.
-        //
-        @ResultData.Status int lastStatus = ResultData.STATUS_NO_ACCESS_CONTROL_PROFILES;
-
-        for (AccessControlProfileId id : accessControlProfileIds) {
-            AccessControlProfile profile = mData.getAccessControlProfile(id);
-            lastStatus = checkAccessSingleProfile(profile, readerCertChain);
-            if (lastStatus == ResultData.STATUS_OK) {
-                return lastStatus;
-            }
-        }
-        return lastStatus;
-    }
-
-    @Override
-    public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
-        mData.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
-    }
-
-    @Override
-    public     @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() {
-        return mData.getAuthKeysNeedingCertification();
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public void storeStaticAuthenticationData(@NonNull X509Certificate authenticationKey,
-            byte @NonNull [] staticAuthData) throws UnknownAuthenticationKeyException {
-        mData.storeStaticAuthenticationData(authenticationKey, null, staticAuthData);
-    }
-
-    @Override
-    public void storeStaticAuthenticationData(
-            @NonNull X509Certificate authenticationKey,
-            @NonNull Calendar expirationDate,
-            byte @NonNull [] staticAuthData)
-            throws UnknownAuthenticationKeyException {
-        mData.storeStaticAuthenticationData(authenticationKey, expirationDate, staticAuthData);
-    }
-
-
-    @Override
-    public     int @NonNull [] getAuthenticationDataUsageCount() {
-        return mData.getAuthKeyUseCounts();
-    }
-
-    @Override
-    public byte @NonNull [] update(@NonNull PersonalizationData personalizationData) {
-        try {
-            String docType = mData.getDocType();
-            Collection<X509Certificate> certificates = mData.getCredentialKeyCertificateChain();
-            PrivateKey credentialKey = mData.getCredentialKeyPrivate();
-            int authKeyCount = mData.getAuthKeyCount();
-            int authMaxUsesPerKey = mData.getAuthMaxUsesPerKey();
-
-            DataItem signature =
-                    SoftwareWritableIdentityCredential.buildProofOfProvisioningWithSignature(
-                            docType,
-                            personalizationData,
-                            credentialKey);
-
-            byte[] proofOfProvisioning = Util.coseSign1GetData(signature);
-            byte[] proofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(
-                    proofOfProvisioning);
-
-            // Nuke all KeyStore keys except for CredentialKey (otherwise we leak them)
-            //
-            mData.deleteKeysForReplacement();
-
-            mData = CredentialData.createCredentialData(
-                    mContext,
-                    docType,
-                    mCredentialName,
-                    CredentialData.getAliasFromCredentialName(mCredentialName),
-                    certificates,
-                    personalizationData,
-                    proofOfProvisioningSha256,
-                    true);
-            // Configure with same settings as old object.
-            //
-            mData.setAvailableAuthenticationKeys(authKeyCount, authMaxUsesPerKey);
-
-            return Util.cborEncode(signature);
-
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("Error digesting ProofOfProvisioning", e);
-        }
-    }
-
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareIdentityCredentialStore.java b/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareIdentityCredentialStore.java
deleted file mode 100644
index 2dc6377..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareIdentityCredentialStore.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-
-import android.content.Context;
-
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-class SoftwareIdentityCredentialStore extends IdentityCredentialStore {
-
-    private static final String TAG = "SoftwareIdentityCredentialStore";
-
-    private Context mContext = null;
-
-    private SoftwareIdentityCredentialStore(@NonNull Context context) {
-        mContext = context;
-    }
-
-    @SuppressWarnings("deprecation")
-    public static @NonNull IdentityCredentialStore getInstance(@NonNull Context context) {
-        return new SoftwareIdentityCredentialStore(context);
-    }
-
-    @SuppressWarnings("deprecation")
-    public static @NonNull IdentityCredentialStore getDirectAccessInstance(
-            @NonNull Context context) {
-        throw new RuntimeException("Direct-access IdentityCredential is not supported");
-    }
-
-    @SuppressWarnings("deprecation")
-    public static boolean isDirectAccessSupported(@NonNull Context context) {
-        return false;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public String @NonNull [] getSupportedDocTypes() {
-        Set<String> docTypeSet = getCapabilities().getSupportedDocTypes();
-        String[] docTypes = new String[docTypeSet.size()];
-        int n = 0;
-        for (String docType : docTypeSet) {
-            docTypes[n++] = docType;
-        }
-        return docTypes;
-    }
-
-    SimpleIdentityCredentialStoreCapabilities mCapabilities = null;
-
-    @Override
-    public     @NonNull IdentityCredentialStoreCapabilities getCapabilities() {
-        if (mCapabilities == null) {
-            LinkedHashSet<String> supportedDocTypesSet = new LinkedHashSet<>();
-            mCapabilities = SimpleIdentityCredentialStoreCapabilities.getFeatureVersion202101(
-                    false,
-                    false,
-                    supportedDocTypesSet);
-        }
-        return mCapabilities;
-    }
-
-    @Override
-    public @NonNull WritableIdentityCredential createCredential(
-            @NonNull String credentialName,
-            @NonNull String docType) throws AlreadyPersonalizedException,
-            DocTypeNotSupportedException {
-        return new SoftwareWritableIdentityCredential(mContext, credentialName, docType);
-    }
-
-    @Override
-    public @Nullable IdentityCredential getCredentialByName(
-            @NonNull String credentialName,
-            @Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException {
-        SoftwareIdentityCredential credential =
-                new SoftwareIdentityCredential(mContext, credentialName, cipherSuite);
-        if (credential.loadData()) {
-            return credential;
-        }
-        return null;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public byte @Nullable [] deleteCredentialByName(@NonNull String credentialName) {
-        return SoftwareIdentityCredential.delete(mContext, credentialName);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareWritableIdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareWritableIdentityCredential.java
deleted file mode 100644
index c1c3d03..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/SoftwareWritableIdentityCredential.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.content.Context;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
-import org.jspecify.annotations.NonNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.builder.ArrayBuilder;
-import co.nstant.in.cbor.builder.MapBuilder;
-import co.nstant.in.cbor.model.DataItem;
-import co.nstant.in.cbor.model.UnicodeString;
-
-class SoftwareWritableIdentityCredential extends WritableIdentityCredential {
-
-    private static final String TAG = "SoftwareWritableIdentityCredential";
-
-    private KeyPair mKeyPair = null;
-    private Collection<X509Certificate> mCertificates = null;
-    private String mDocType;
-    private String mCredentialName;
-    private Context mContext;
-
-    SoftwareWritableIdentityCredential(Context context,
-            @NonNull String credentialName,
-            @NonNull String docType) throws AlreadyPersonalizedException {
-        mContext = context;
-        mDocType = docType;
-        mCredentialName = credentialName;
-        if (CredentialData.credentialAlreadyExists(context, credentialName)) {
-            throw new AlreadyPersonalizedException("Credential with given name already exists");
-        }
-    }
-
-    /**
-     * Generates CredentialKey.
-     *
-     * If called a second time on the same object, does nothing and returns null.
-     *
-     * @param challenge The attestation challenge.
-     * @return Attestation mCertificate chain or null if called a second time.
-     * @throws AlreadyPersonalizedException     if this credential has already been personalized.
-     * @throws CipherSuiteNotSupportedException if the cipher suite is not supported
-     * @throws IdentityCredentialException      if unable to communicate with secure hardware.
-     */
-    private Collection<X509Certificate> ensureCredentialKey(byte[] challenge) {
-
-        if (mKeyPair != null) {
-            return null;
-        }
-
-        String aliasForCredential = CredentialData.getAliasFromCredentialName(mCredentialName);
-
-        try {
-            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-            if (ks.containsAlias(aliasForCredential)) {
-                ks.deleteEntry(aliasForCredential);
-            }
-
-            // TODO: We most likely want to constrain the life of CredentialKey (through
-            // setKeyValidityStart() and setKeyValidityEnd()) so it's limited to e.g. 5 years
-            // or how long the credential might be valid. For US driving licenses it's typically
-            // up to 5 years, where it expires on your birth day).
-            //
-            // This is likely something the issuer would want to specify.
-
-            KeyPairGenerator kpg = KeyPairGenerator.getInstance(
-                    KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
-            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
-                    aliasForCredential,
-                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
-
-            // Attestation is only available in Nougat and onwards.
-            if (challenge == null) {
-                challenge = new byte[0];
-            }
-            builder.setAttestationChallenge(challenge);
-            kpg.initialize(builder.build());
-            mKeyPair = kpg.generateKeyPair();
-
-            Certificate[] certificates = ks.getCertificateChain(aliasForCredential);
-            mCertificates = new ArrayList<>();
-            for (Certificate certificate : certificates) {
-                mCertificates.add((X509Certificate) certificate);
-            }
-        } catch (InvalidAlgorithmParameterException
-                | NoSuchAlgorithmException
-                | NoSuchProviderException
-                | CertificateException
-                | KeyStoreException
-                | IOException e) {
-            throw new RuntimeException("Error creating CredentialKey", e);
-        }
-        return mCertificates;
-    }
-
-    @Override
-    public @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
-            byte @NonNull [] challenge) {
-        Collection<X509Certificate> certificates = ensureCredentialKey(challenge);
-        if (certificates == null) {
-            throw new RuntimeException(
-                    "getCredentialKeyCertificateChain() must be called before personalize()");
-        }
-        return certificates;
-    }
-
-    // Returns COSE_Sign1 with payload set to ProofOfProvisioning
-    static DataItem buildProofOfProvisioningWithSignature(String docType,
-            PersonalizationData personalizationData,
-            PrivateKey key) {
-
-        CborBuilder accessControlProfileBuilder = new CborBuilder();
-        ArrayBuilder<CborBuilder> arrayBuilder = accessControlProfileBuilder.addArray();
-        for (AccessControlProfile profile : personalizationData.getAccessControlProfiles()) {
-            arrayBuilder.add(Util.accessControlProfileToCbor(profile));
-        }
-
-        CborBuilder dataBuilder = new CborBuilder();
-        MapBuilder<CborBuilder> dataMapBuilder = dataBuilder.addMap();
-        for (PersonalizationData.NamespaceData namespaceData :
-                personalizationData.getNamespaceDatas()) {
-            dataMapBuilder.put(
-                    new UnicodeString(namespaceData.getNamespaceName()),
-                    Util.namespaceDataToCbor(namespaceData));
-        }
-
-        CborBuilder signedDataBuilder = new CborBuilder();
-        signedDataBuilder.addArray()
-                .add("ProofOfProvisioning")
-                .add(docType)
-                .add(accessControlProfileBuilder.build().get(0))
-                .add(dataBuilder.build().get(0))
-                .add(false);
-
-        DataItem signature;
-        try {
-            ByteArrayOutputStream dtsBaos = new ByteArrayOutputStream();
-            CborEncoder dtsEncoder = new CborEncoder(dtsBaos);
-            dtsEncoder.encode(signedDataBuilder.build().get(0));
-            byte[] dataToSign = dtsBaos.toByteArray();
-
-            signature = Util.coseSign1Sign(key,
-                    dataToSign,
-                    null,
-                    null);
-        } catch (NoSuchAlgorithmException
-                | InvalidKeyException
-                | CertificateEncodingException
-                | CborException e) {
-            throw new RuntimeException("Error building ProofOfProvisioning", e);
-        }
-        return signature;
-    }
-
-    @Override
-    public byte @NonNull [] personalize(@NonNull PersonalizationData personalizationData) {
-
-        try {
-            ensureCredentialKey(null);
-
-            DataItem signature = buildProofOfProvisioningWithSignature(mDocType,
-                    personalizationData,
-                    mKeyPair.getPrivate());
-
-            byte[] proofOfProvisioning = Util.coseSign1GetData(signature);
-            byte[] proofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(
-                    proofOfProvisioning);
-
-            CredentialData.createCredentialData(
-                    mContext,
-                    mDocType,
-                    mCredentialName,
-                    CredentialData.getAliasFromCredentialName(mCredentialName),
-                    mCertificates,
-                    personalizationData,
-                    proofOfProvisioningSha256,
-                    false);
-
-            return Util.cborEncode(signature);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("Error digesting ProofOfProvisioning", e);
-        }
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/UnknownAuthenticationKeyException.java b/security/security-identity-credential/src/main/java/androidx/security/identity/UnknownAuthenticationKeyException.java
deleted file mode 100644
index a825f40..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/UnknownAuthenticationKeyException.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import org.jspecify.annotations.NonNull;
-
-/**
- * Thrown if trying to certify an unknown dynamic authentication key.
- */
-public class UnknownAuthenticationKeyException extends IdentityCredentialException {
-    /**
-     * Constructs a new {@link UnknownAuthenticationKeyException} exception.
-     *
-     * @param message the detail message.
-     */
-    public UnknownAuthenticationKeyException(@NonNull String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new {@link UnknownAuthenticationKeyException} exception.
-     *
-     * @param message the detail message.
-     * @param cause   the cause.
-     */
-    public UnknownAuthenticationKeyException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/Util.java b/security/security-identity-credential/src/main/java/androidx/security/identity/Util.java
deleted file mode 100644
index 427a6be41..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/Util.java
+++ /dev/null
@@ -1,1574 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import android.content.Context;
-import android.icu.text.DateFormat;
-import android.icu.text.SimpleDateFormat;
-import android.icu.util.Calendar;
-import android.icu.util.GregorianCalendar;
-import android.icu.util.TimeZone;
-import android.security.keystore.KeyProperties;
-import android.util.Log;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECGenParameterSpec;
-import java.security.spec.ECPoint;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Formatter;
-import java.util.List;
-import java.util.Locale;
-
-import javax.crypto.KeyAgreement;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborDecoder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.builder.ArrayBuilder;
-import co.nstant.in.cbor.builder.MapBuilder;
-import co.nstant.in.cbor.model.AbstractFloat;
-import co.nstant.in.cbor.model.Array;
-import co.nstant.in.cbor.model.ByteString;
-import co.nstant.in.cbor.model.DataItem;
-import co.nstant.in.cbor.model.DoublePrecisionFloat;
-import co.nstant.in.cbor.model.MajorType;
-import co.nstant.in.cbor.model.Map;
-import co.nstant.in.cbor.model.NegativeInteger;
-import co.nstant.in.cbor.model.Number;
-import co.nstant.in.cbor.model.SimpleValue;
-import co.nstant.in.cbor.model.SimpleValueType;
-import co.nstant.in.cbor.model.SpecialType;
-import co.nstant.in.cbor.model.UnicodeString;
-import co.nstant.in.cbor.model.UnsignedInteger;
-
-class Util {
-    private static final String TAG = "Util";
-
-    // Not called.
-    private Util() {}
-
-    public static byte[] fromHex(String stringWithHex) {
-        int stringLength = stringWithHex.length();
-        if ((stringLength & 1) != 0) {
-            throw new IllegalArgumentException("Invalid length of hex string");
-        }
-        int numBytes = stringLength / 2;
-        byte[] data = new byte[numBytes];
-        for (int n = 0; n < numBytes; n++) {
-            data[n] = (byte) ((Character.digit(stringWithHex.charAt(n * 2), 16) << 4)
-                    + Character.digit(stringWithHex.charAt(n * 2 + 1), 16));
-        }
-        return data;
-    }
-
-    public static String toHex(byte[] bytes) {
-        StringBuilder sb = new StringBuilder();
-        for (int n = 0; n < bytes.length; n++) {
-            byte b = bytes[n];
-            sb.append(String.format("%02x", b));
-        }
-        return sb.toString();
-    }
-
-    static void hexdump(String name, byte[] data) {
-        int n, m, o;
-        StringBuilder sb = new StringBuilder();
-        Formatter fmt = new Formatter(sb);
-        for (n = 0; n < data.length; n += 16) {
-            fmt.format("%04x  ", n);
-            for (m = 0; m < 16 && n + m < data.length; m++) {
-                fmt.format("%02x ", data[n + m]);
-            }
-            for (o = m; o < 16; o++) {
-                sb.append("   ");
-            }
-            sb.append(" ");
-            for (m = 0; m < 16 && n + m < data.length; m++) {
-                int c = data[n + m] & 0xff;
-                fmt.format("%c", Character.isISOControl(c) ? '.' : c);
-            }
-            sb.append("\n");
-        }
-        sb.append("\n");
-        Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString());
-    }
-
-
-    public static byte[] cborEncode(DataItem dataItem) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            new CborEncoder(baos).encode(dataItem);
-        } catch (CborException e) {
-            // This should never happen and we don't want cborEncode() to throw since that
-            // would complicate all callers. Log it instead.
-            throw new IllegalStateException("Unexpected failure encoding data", e);
-        }
-        return baos.toByteArray();
-    }
-
-    static byte[] cborEncodeBoolean(boolean value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static byte[] cborEncodeString(@NonNull String value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static byte[] cborEncodeNumber(long value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static byte[] cborEncodeBytestring(byte @NonNull [] value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static byte[] cborEncodeDateTime(@NonNull Calendar calendar) {
-        return cborEncode(cborBuildDateTime(calendar));
-    }
-
-    // Returns #6.0(tstr) where tstr is the ISO 8601 encoding of the
-    // given point in time.
-    //
-    public static DataItem cborBuildDateTime(@NonNull Calendar calendar) {
-        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
-        if (calendar.isSet(Calendar.MILLISECOND) && calendar.get(Calendar.MILLISECOND) != 0) {
-            df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
-        }
-        df.setTimeZone(calendar.getTimeZone());
-        Date val = calendar.getTime();
-        String dateString = df.format(val);
-        DataItem dataItem = new UnicodeString(dateString);
-        dataItem.setTag(0);
-        return dataItem;
-    }
-
-    public static DataItem cborDecode(byte[] encodedBytes) {
-        ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
-        List<DataItem> dataItems = null;
-        try {
-            dataItems = new CborDecoder(bais).decode();
-        } catch (CborException e) {
-            throw new IllegalArgumentException("Error decoding CBOR", e);
-        }
-        if (dataItems.size() != 1) {
-            throw new IllegalArgumentException("Unexpected number of items, expected 1 got "
-                    + dataItems.size());
-        }
-        return dataItems.get(0);
-    }
-
-    static boolean cborDecodeBoolean(byte @NonNull [] data) {
-        SimpleValue simple = (SimpleValue) cborDecode(data);
-        return simple.getSimpleValueType() == SimpleValueType.TRUE;
-    }
-
-    static String cborDecodeString(byte @NonNull [] data) {
-        return ((co.nstant.in.cbor.model.UnicodeString) cborDecode(data)).getString();
-    }
-
-    static long cborDecodeLong(byte @NonNull [] data) {
-        return ((co.nstant.in.cbor.model.Number) cborDecode(data)).getValue().longValue();
-    }
-
-    static byte[] cborDecodeByteString(byte @NonNull [] data) {
-        return ((co.nstant.in.cbor.model.ByteString) cborDecode(data)).getBytes();
-    }
-
-    static Calendar cborDecodeDateTime(byte @NonNull [] data) {
-        DataItem di = cborDecode(data);
-        if (!(di instanceof co.nstant.in.cbor.model.UnicodeString)) {
-            throw new IllegalArgumentException("Passed in data is not a Unicode-string");
-        }
-        if (!di.hasTag() || di.getTag().getValue() != 0) {
-            throw new IllegalArgumentException("Passed in data is not tagged with tag 0");
-        }
-        String dateString = ((co.nstant.in.cbor.model.UnicodeString) di).getString();
-
-        // Manually parse the timezone
-        TimeZone parsedTz = TimeZone.getTimeZone("UTC");
-        java.util.TimeZone parsedTz2 = java.util.TimeZone.getTimeZone("UTC");
-        if (!dateString.endsWith("Z")) {
-            String timeZoneSubstr = dateString.substring(dateString.length() - 6);
-            parsedTz = TimeZone.getTimeZone("GMT" + timeZoneSubstr);
-            parsedTz2 = java.util.TimeZone.getTimeZone("GMT" + timeZoneSubstr);
-        }
-
-        java.text.DateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS",
-                Locale.US);
-        df.setTimeZone(parsedTz2);
-        Date date = null;
-        try {
-            date = df.parse(dateString);
-        } catch (ParseException e) {
-            // Try again, this time without the milliseconds
-            df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
-            df.setTimeZone(parsedTz2);
-            try {
-                date = df.parse(dateString);
-            } catch (ParseException e2) {
-                throw new RuntimeException("Error parsing string", e2);
-            }
-        }
-
-        Calendar c = new GregorianCalendar();
-        c.clear();
-        c.setTimeZone(parsedTz);
-        c.setTime(date);
-        return c;
-    }
-
-    static DataItem namespaceDataToCbor(PersonalizationData.NamespaceData entryNamespace) {
-        CborBuilder entryBuilder = new CborBuilder();
-        ArrayBuilder<CborBuilder> entryArrayBuilder = entryBuilder.addArray();
-        for (String entryName : entryNamespace.getEntryNames()) {
-            byte[] entryValue = entryNamespace.getEntryValue(entryName);
-            Collection<AccessControlProfileId> accessControlProfileIds =
-                    entryNamespace.getAccessControlProfileIds(
-                            entryName);
-
-            CborBuilder accessControlProfileIdsBuilder = new CborBuilder();
-            ArrayBuilder<CborBuilder> accessControlProfileIdsArrayBuilder =
-                    accessControlProfileIdsBuilder.addArray();
-            for (AccessControlProfileId id : accessControlProfileIds) {
-                accessControlProfileIdsArrayBuilder.add(id.getId());
-            }
-
-            MapBuilder<ArrayBuilder<CborBuilder>> entryMapBuilder = entryArrayBuilder.addMap();
-            entryMapBuilder.put("name", entryName);
-            entryMapBuilder.put(new UnicodeString("accessControlProfiles"),
-                    accessControlProfileIdsBuilder.build().get(0));
-            entryMapBuilder.put(new UnicodeString("value"), cborDecode(entryValue));
-        }
-        return entryBuilder.build().get(0);
-    }
-
-    public static PersonalizationData.NamespaceData namespaceDataFromCbor(String namespaceName,
-            DataItem dataItem) {
-        if (!(dataItem instanceof Array)) {
-            throw new IllegalArgumentException("Item is not an Array");
-        }
-        Array array = (Array) dataItem;
-
-        PersonalizationData.Builder builder = new PersonalizationData.Builder();
-
-        for (DataItem item : array.getDataItems()) {
-            if (!(item instanceof co.nstant.in.cbor.model.Map)) {
-                throw new IllegalArgumentException("Item is not a map");
-            }
-            co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) item;
-
-            String name = ((UnicodeString) map.get(new UnicodeString("name"))).getString();
-
-            Collection<AccessControlProfileId> accessControlProfileIds = new ArrayList<>();
-            co.nstant.in.cbor.model.Array accessControlProfileArray =
-                    (co.nstant.in.cbor.model.Array) map.get(
-                            new UnicodeString("accessControlProfiles"));
-            for (DataItem acpIdItem : accessControlProfileArray.getDataItems()) {
-                accessControlProfileIds.add(
-                        new AccessControlProfileId(((Number) acpIdItem).getValue().intValue()));
-            }
-
-            DataItem cborValue = map.get(new UnicodeString("value"));
-            byte[] data = cborEncode(cborValue);
-            builder.putEntry(namespaceName, name, accessControlProfileIds, data);
-        }
-
-        return builder.build().getNamespaceData(namespaceName);
-    }
-
-    public static AccessControlProfile accessControlProfileFromCbor(DataItem item) {
-        if (!(item instanceof co.nstant.in.cbor.model.Map)) {
-            throw new IllegalArgumentException("Item is not a map");
-        }
-        Map map = (Map) item;
-
-        int accessControlProfileId = ((Number) map.get(
-                new UnicodeString("id"))).getValue().intValue();
-        AccessControlProfile.Builder builder = new AccessControlProfile.Builder(
-                new AccessControlProfileId(accessControlProfileId));
-
-        item = map.get(new UnicodeString("readerCertificate"));
-        if (item != null) {
-            byte[] rcBytes = ((ByteString) item).getBytes();
-            CertificateFactory certFactory = null;
-            try {
-                certFactory = CertificateFactory.getInstance("X.509");
-                builder.setReaderCertificate((X509Certificate) certFactory.generateCertificate(
-                        new ByteArrayInputStream(rcBytes)));
-            } catch (CertificateException e) {
-                throw new IllegalArgumentException("Error decoding readerCertificate", e);
-            }
-        }
-
-        builder.setUserAuthenticationRequired(false);
-        item = map.get(new UnicodeString("capabilityType"));
-        if (item != null) {
-            // TODO: deal with -1 as per entryNamespaceToCbor()
-            builder.setUserAuthenticationRequired(true);
-            item = map.get(new UnicodeString("timeout"));
-            builder.setUserAuthenticationTimeout(
-                    item == null ? 0 : ((Number) item).getValue().intValue());
-        }
-        return builder.build();
-    }
-
-    static DataItem accessControlProfileToCbor(AccessControlProfile accessControlProfile) {
-        CborBuilder cborBuilder = new CborBuilder();
-        MapBuilder<CborBuilder> mapBuilder = cborBuilder.addMap();
-
-        mapBuilder.put("id", accessControlProfile.getAccessControlProfileId().getId());
-        X509Certificate readerCertificate = accessControlProfile.getReaderCertificate();
-        if (readerCertificate != null) {
-            try {
-                mapBuilder.put("readerCertificate", readerCertificate.getEncoded());
-            } catch (CertificateEncodingException e) {
-                throw new IllegalStateException("Error encoding reader mCertificate", e);
-            }
-        }
-        if (accessControlProfile.isUserAuthenticationRequired()) {
-            mapBuilder.put("capabilityType", 1); // TODO: what value to put here?
-            long timeout = accessControlProfile.getUserAuthenticationTimeout();
-            if (timeout != 0) {
-                mapBuilder.put("timeout", timeout);
-            }
-        }
-        return cborBuilder.build().get(0);
-    }
-
-    static @NonNull X509Certificate generateAuthenticationKeyCert(String authKeyAlias,
-            String credentialKeyAlias,
-            byte[] proofOfProvisioningSha256) {
-        KeyStore ks = null;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-
-            X509Certificate selfSignedCert = (X509Certificate) ks.getCertificate(authKeyAlias);
-            PublicKey publicKey = selfSignedCert.getPublicKey();
-
-            PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) ks.getEntry(credentialKeyAlias,
-                    null)).getPrivateKey();
-
-            X500Name issuer = new X500Name("CN=Android Identity Credential Key");
-            X500Name subject = new X500Name("CN=Android Identity Credential Authentication Key");
-
-            Date now = new Date();
-            final long kMilliSecsInOneYear = 365L * 24 * 60 * 60 * 1000;
-            Date expirationDate = new Date(now.getTime() + kMilliSecsInOneYear);
-            BigInteger serial = new BigInteger("1");
-            JcaX509v3CertificateBuilder builder =
-                    new JcaX509v3CertificateBuilder(issuer,
-                            serial,
-                            now,
-                            expirationDate,
-                            subject,
-                            publicKey);
-
-            if (proofOfProvisioningSha256 != null) {
-                byte[] encodedProofOfBinding = cborEncode(new CborBuilder()
-                        .addArray()
-                        .add("ProofOfBinding")
-                        .add(proofOfProvisioningSha256)
-                        .end()
-                        .build().get(0));
-                builder.addExtension(new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.26"), false,
-                        encodedProofOfBinding);
-            }
-
-            ContentSigner signer = new JcaContentSignerBuilder("SHA256withECDSA").build(privateKey);
-            byte[] encodedCert = builder.build(signer).getEncoded();
-
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert);
-            X509Certificate result = (X509Certificate) cf.generateCertificate(bais);
-            return result;
-        } catch (Exception e) {
-            throw new IllegalStateException("Error signing public key with private key", e);
-        }
-    }
-
-    /**
-     * Helper function to check if a given certificate chain is valid.
-     *
-     * NOTE NOTE NOTE: We only check that the certificates in the chain sign each other. We
-     * <em>specifically</em> don't check that each certificate is also a CA certificate.
-     *
-     * @param certificateChain the chain to validate.
-     * @return <code>true</code> if valid, <code>false</code> otherwise.
-     */
-    public static boolean validateCertificateChain(Collection<X509Certificate> certificateChain) {
-        // First check that each certificate signs the previous one...
-        X509Certificate prevCertificate = null;
-        for (X509Certificate certificate : certificateChain) {
-            if (prevCertificate != null) {
-                // We're not the leaf certificate...
-                //
-                // Check the previous certificate was signed by this one.
-                try {
-                    prevCertificate.verify(certificate.getPublicKey());
-                } catch (CertificateException
-                        | InvalidKeyException
-                        | NoSuchAlgorithmException
-                        | NoSuchProviderException
-                        | SignatureException e) {
-                    return false;
-                }
-            } else {
-                // we're the leaf certificate so we're not signing anything nor
-                // do we need to be e.g. a CA certificate.
-            }
-            prevCertificate = certificate;
-        }
-        return true;
-    }
-
-    /**
-     * Computes an HKDF.
-     *
-     * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
-     * /crypto/tink/subtle/Hkdf.java
-     * which is also Copyright (c) Google and also licensed under the Apache 2 license.
-     *
-     * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
-     *                     "HMACSHA256".
-     * @param ikm          the input keying material.
-     * @param salt         optional salt. A possibly non-secret random value. If no salt is
-     *                     provided (i.e. if
-     *                     salt has length 0) then an array of 0s of the same size as the hash
-     *                     digest is used as salt.
-     * @param info         optional context and application specific information.
-     * @param size         The length of the generated pseudorandom string in bytes. The maximal
-     *                     size is
-     *                     255.DigestSize, where DigestSize is the size of the underlying HMAC.
-     * @return size pseudorandom bytes.
-     */
-    static byte[] computeHkdf(
-            String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
-        Mac mac = null;
-        try {
-            mac = Mac.getInstance(macAlgorithm);
-        } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("No such algorithm: " + macAlgorithm, e);
-        }
-        if (size > 255 * mac.getMacLength()) {
-            throw new IllegalArgumentException("size too large");
-        }
-        try {
-            if (salt == null || salt.length == 0) {
-                // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
-                // then HKDF uses a salt that is an array of zeros of the same length as the hash
-                // digest.
-                mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
-            } else {
-                mac.init(new SecretKeySpec(salt, macAlgorithm));
-            }
-            byte[] prk = mac.doFinal(ikm);
-            byte[] result = new byte[size];
-            int ctr = 1;
-            int pos = 0;
-            mac.init(new SecretKeySpec(prk, macAlgorithm));
-            byte[] digest = new byte[0];
-            while (true) {
-                mac.update(digest);
-                mac.update(info);
-                mac.update((byte) ctr);
-                digest = mac.doFinal();
-                if (pos + digest.length < size) {
-                    System.arraycopy(digest, 0, result, pos, digest.length);
-                    pos += digest.length;
-                    ctr++;
-                } else {
-                    System.arraycopy(digest, 0, result, pos, size - pos);
-                    break;
-                }
-            }
-            return result;
-        } catch (InvalidKeyException e) {
-            throw new IllegalStateException("Error MACing", e);
-        }
-    }
-
-    private static byte[] coseBuildToBeSigned(byte[] encodedProtectedHeaders,
-            byte[] payload,
-            byte[] detachedContent) {
-        CborBuilder sigStructure = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = sigStructure.addArray();
-
-        array.add("Signature1");
-        array.add(encodedProtectedHeaders);
-
-        // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
-        // so external_aad is the empty bstr
-        byte[] emptyExternalAad = new byte[0];
-        array.add(emptyExternalAad);
-
-        // Next field is the payload, independently of how it's transported (RFC
-        // 8152 section 4.4). Since our API specifies only one of |data| and
-        // |detachedContent| can be non-empty, it's simply just the non-empty one.
-        if (payload != null && payload.length > 0) {
-            array.add(payload);
-        } else {
-            array.add(detachedContent);
-        }
-        array.end();
-        return cborEncode(sigStructure.build().get(0));
-    }
-
-    private static final int COSE_LABEL_ALG = 1;
-    private static final int COSE_LABEL_X5CHAIN = 33;  // temporary identifier
-
-    // From "COSE Algorithms" registry
-    private static final int COSE_ALG_ECDSA_256 = -7;
-    private static final int COSE_ALG_HMAC_256_256 = 5;
-
-    private static byte[] signatureDerToCose(byte[] signature) {
-        if (signature.length > 128) {
-            throw new IllegalArgumentException(
-                    "Unexpected length " + signature.length + ", expected less than 128");
-        }
-        if (signature[0] != 0x30) {
-            throw new IllegalArgumentException("Unexpected first byte " + signature[0]
-                    + ", expected 0x30");
-        }
-        if ((signature[1] & 0x80) != 0x00) {
-            throw new IllegalArgumentException(
-                    "Unexpected second byte " + signature[1] + ", bit 7 shouldn't be set");
-        }
-        int rOffset = 2;
-        int rSize = signature[rOffset + 1];
-        byte[] rBytes = stripLeadingZeroes(
-                Arrays.copyOfRange(signature, rOffset + 2, rOffset + rSize + 2));
-
-        int sOffset = rOffset + 2 + rSize;
-        int sSize = signature[sOffset + 1];
-        byte[] sBytes = stripLeadingZeroes(
-                Arrays.copyOfRange(signature, sOffset + 2, sOffset + sSize + 2));
-
-        if (rBytes.length > 32) {
-            throw new IllegalArgumentException(
-                    "rBytes.length is " + rBytes.length + " which is > 32");
-        }
-        if (sBytes.length > 32) {
-            throw new IllegalArgumentException(
-                    "sBytes.length is " + sBytes.length + " which is > 32");
-        }
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            for (int n = 0; n < 32 - rBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(rBytes);
-            for (int n = 0; n < 32 - sBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(sBytes);
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-        return baos.toByteArray();
-    }
-
-    // Adds leading 0x00 if the first encoded byte MSB is set.
-    private static byte[] encodePositiveBigInteger(BigInteger i) {
-        byte[] bytes = i.toByteArray();
-        if ((bytes[0] & 0x80) != 0) {
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            try {
-                baos.write(0x00);
-                baos.write(bytes);
-            } catch (IOException e) {
-                throw new IllegalStateException("Failed writing data", e);
-            }
-            bytes = baos.toByteArray();
-        }
-        return bytes;
-    }
-
-    private static byte[] signatureCoseToDer(byte[] signature) {
-        if (signature.length != 64) {
-            throw new IllegalArgumentException(
-                    "signature.length is " + signature.length + ", expected 64");
-        }
-        // r and s are always positive and may use all 256 bits so use the constructor which
-        // parses them as unsigned.
-        BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature, 0, 32));
-        BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature, 32, 64));
-        byte[] rBytes = encodePositiveBigInteger(r);
-        byte[] sBytes = encodePositiveBigInteger(s);
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            baos.write(0x30);
-            baos.write(2 + rBytes.length + 2 + sBytes.length);
-            baos.write(0x02);
-            baos.write(rBytes.length);
-            baos.write(rBytes);
-            baos.write(0x02);
-            baos.write(sBytes.length);
-            baos.write(sBytes);
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-        return baos.toByteArray();
-    }
-
-    public static DataItem coseSign1Sign(Signature s,
-            byte @Nullable [] data,
-            byte[] detachedContent,
-            @Nullable Collection<X509Certificate> certificateChain)
-            throws CertificateEncodingException {
-
-        int dataLen = (data != null ? data.length : 0);
-        int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
-        if (dataLen > 0 && detachedContentLen > 0) {
-            throw new IllegalArgumentException("data and detachedContent cannot both be non-empty");
-        }
-
-        CborBuilder protectedHeaders = new CborBuilder();
-        MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
-        protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_ECDSA_256);
-        byte[] protectedHeadersBytes = cborEncode(protectedHeaders.build().get(0));
-
-        byte[] toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent);
-
-        byte[] coseSignature = null;
-        try {
-            s.update(toBeSigned);
-            byte[] derSignature = s.sign();
-            coseSignature = signatureDerToCose(derSignature);
-        } catch (SignatureException e) {
-            throw new IllegalStateException("Error signing data", e);
-        }
-
-        CborBuilder builder = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = builder.addArray();
-        array.add(protectedHeadersBytes);
-        MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap();
-        if (certificateChain != null && certificateChain.size() > 0) {
-            if (certificateChain.size() == 1) {
-                X509Certificate cert = certificateChain.iterator().next();
-                unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.getEncoded());
-            } else {
-                ArrayBuilder<MapBuilder<ArrayBuilder<CborBuilder>>> x5chainsArray =
-                        unprotectedHeaders.putArray(COSE_LABEL_X5CHAIN);
-                for (X509Certificate cert : certificateChain) {
-                    x5chainsArray.add(cert.getEncoded());
-                }
-            }
-        }
-        if (data == null || data.length == 0) {
-            array.add(new SimpleValue(SimpleValueType.NULL));
-        } else {
-            array.add(data);
-        }
-        array.add(coseSignature);
-
-        return builder.build().get(0);
-    }
-
-    public static DataItem coseSign1Sign(PrivateKey key,
-            byte @Nullable [] data,
-            byte[] additionalData,
-            @Nullable Collection<X509Certificate> certificateChain)
-            throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException {
-
-        Signature s = Signature.getInstance("SHA256withECDSA");
-        s.initSign(key);
-        return coseSign1Sign(s, data, additionalData, certificateChain);
-    }
-
-    public static boolean coseSign1CheckSignature(DataItem coseSign1,
-            byte[] detachedContent, PublicKey publicKey)  {
-        if (coseSign1.getMajorType() != MajorType.ARRAY) {
-            throw new IllegalArgumentException("Data item is not an array");
-        }
-        List<DataItem> items = ((co.nstant.in.cbor.model.Array) coseSign1).getDataItems();
-        if (items.size() < 4) {
-            throw new IllegalArgumentException("Expected at least four items in COSE_Sign1 array");
-        }
-        if (items.get(0).getMajorType() != MajorType.BYTE_STRING) {
-            throw new IllegalArgumentException("Item 0 (protected headers) is not a byte-string");
-        }
-        byte[] encodedProtectedHeaders = ((co.nstant.in.cbor.model.ByteString) items.get(
-                0)).getBytes();
-        byte[] payload = new byte[0];
-        if (items.get(2).getMajorType() == MajorType.SPECIAL) {
-            if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
-                    != SpecialType.SIMPLE_VALUE) {
-                throw new IllegalArgumentException(
-                        "Item 2 (payload) is a special but not a simple value");
-            }
-            SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
-            if (simple.getSimpleValueType() != SimpleValueType.NULL) {
-                throw new IllegalArgumentException(
-                        "Item 2 (payload) is a simple but not the value null");
-            }
-        } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
-            payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
-        } else {
-            throw new IllegalArgumentException("Item 2 (payload) is not nil or byte-string");
-        }
-        if (items.get(3).getMajorType() != MajorType.BYTE_STRING) {
-            throw new IllegalArgumentException("Item 3 (signature) is not a byte-string");
-        }
-        byte[] coseSignature = ((co.nstant.in.cbor.model.ByteString) items.get(3)).getBytes();
-
-        byte[] derSignature = signatureCoseToDer(coseSignature);
-
-        int dataLen = payload.length;
-        int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
-        if (dataLen > 0 && detachedContentLen > 0) {
-            throw new IllegalArgumentException("data and detachedContent cannot both be non-empty");
-        }
-
-        byte[] toBeSigned = Util.coseBuildToBeSigned(encodedProtectedHeaders, payload,
-                detachedContent);
-
-        try {
-            Signature verifier = Signature.getInstance("SHA256withECDSA");
-            verifier.initVerify(publicKey);
-            verifier.update(toBeSigned);
-            return verifier.verify(derSignature);
-        } catch (SignatureException | NoSuchAlgorithmException | InvalidKeyException e) {
-            throw new IllegalStateException("Error verifying signature", e);
-        }
-    }
-
-    private static byte[] coseBuildToBeMACed(byte[] encodedProtectedHeaders,
-            byte[] payload,
-            byte[] detachedContent) {
-        CborBuilder macStructure = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = macStructure.addArray();
-
-        array.add("MAC0");
-        array.add(encodedProtectedHeaders);
-
-        // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
-        // so external_aad is the empty bstr
-        byte[] emptyExternalAad = new byte[0];
-        array.add(emptyExternalAad);
-
-        // Next field is the payload, independently of how it's transported (RFC
-        // 8152 section 4.4). Since our API specifies only one of |data| and
-        // |detachedContent| can be non-empty, it's simply just the non-empty one.
-        if (payload != null && payload.length > 0) {
-            array.add(payload);
-        } else {
-            array.add(detachedContent);
-        }
-
-        return cborEncode(macStructure.build().get(0));
-    }
-
-    public static DataItem coseMac0(SecretKey key,
-            byte @Nullable [] data,
-            byte[] detachedContent) {
-
-        int dataLen = (data != null ? data.length : 0);
-        int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
-        if (dataLen > 0 && detachedContentLen > 0) {
-            throw new IllegalArgumentException("data and detachedContent cannot both be non-empty");
-        }
-
-        CborBuilder protectedHeaders = new CborBuilder();
-        MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
-        protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256);
-        byte[] protectedHeadersBytes = cborEncode(protectedHeaders.build().get(0));
-
-        byte[] toBeMACed = coseBuildToBeMACed(protectedHeadersBytes, data, detachedContent);
-
-        byte[] mac;
-        try {
-            Mac m = Mac.getInstance("HmacSHA256");
-            m.init(key);
-            m.update(toBeMACed);
-            mac = m.doFinal();
-        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
-            throw new IllegalStateException("Unexpected error", e);
-        }
-
-        CborBuilder builder = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = builder.addArray();
-        array.add(protectedHeadersBytes);
-        /* MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = */ array.addMap();
-        if (data == null || data.length == 0) {
-            array.add(new SimpleValue(SimpleValueType.NULL));
-        } else {
-            array.add(data);
-        }
-        array.add(mac);
-
-        return builder.build().get(0);
-    }
-
-    public static byte[] coseMac0GetTag(DataItem coseMac0) {
-        if (!(coseMac0 instanceof Array)) {
-            throw new IllegalArgumentException("coseMac0 is not an array");
-        }
-        List<DataItem> items = ((Array) coseMac0).getDataItems();
-        if (items.size() < 4) {
-            throw new IllegalArgumentException("coseMac0 have less than 4 elements");
-        }
-        DataItem tagItem = items.get(3);
-        if (!(tagItem instanceof ByteString)) {
-            throw new IllegalArgumentException("tag in coseMac0 is not a ByteString");
-        }
-        return ((ByteString) tagItem).getBytes();
-    }
-
-
-    // Brute-force but good enough since users will only pass relatively small amounts of data.
-    static boolean hasSubByteArray(byte[] haystack, byte[] needle) {
-        int n = 0;
-        while (needle.length + n <= haystack.length) {
-            boolean found = true;
-            for (int m = 0; m < needle.length; m++) {
-                if (needle[m] != haystack[n + m]) {
-                    found = false;
-                    break;
-                }
-            }
-            if (found) {
-                return true;
-            }
-            n++;
-        }
-        return false;
-    }
-
-    static byte[] stripLeadingZeroes(byte[] value) {
-        int n = 0;
-        while (n < value.length && value[n] == 0) {
-            n++;
-        }
-        int newLen = value.length - n;
-        byte[] ret = new byte[newLen];
-        int m = 0;
-        while (n < value.length) {
-            ret[m++] = value[n++];
-        }
-        return ret;
-    }
-
-    static final int CBOR_SEMANTIC_TAG_ENCODED_CBOR = 24;
-
-    // Returns #6.24(bstr) of the given already encoded CBOR
-    //
-    public static @NonNull DataItem cborBuildTaggedByteString(byte @NonNull [] encodedCbor) {
-        DataItem item = new ByteString(encodedCbor);
-        item.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
-        return item;
-    }
-
-    // For a #6.24(bstr), extracts the bytes and decodes it and returns
-    // the decoded CBOR as a DataItem.
-    //
-    public static DataItem cborExtractTaggedAndEncodedCbor(DataItem item) {
-        if (item == null || !(item instanceof ByteString)) {
-            throw new IllegalArgumentException("Item is not a ByteString");
-        }
-        if (!item.hasTag() || item.getTag().getValue() != CBOR_SEMANTIC_TAG_ENCODED_CBOR) {
-            throw new IllegalArgumentException("ByteString is not tagged with tag 24");
-        }
-        byte[] encodedCbor = ((ByteString) item).getBytes();
-        DataItem embeddedItem = cborDecode(encodedCbor);
-        return embeddedItem;
-    }
-
-    // Returns the empty byte-array if no data is included in the structure.
-    //
-    public static byte[] coseSign1GetData(DataItem coseSign1) {
-        if (coseSign1.getMajorType() != MajorType.ARRAY) {
-            throw new IllegalArgumentException("Data item is not an array");
-        }
-        List<DataItem> items = ((co.nstant.in.cbor.model.Array) coseSign1).getDataItems();
-        if (items.size() < 4) {
-            throw new IllegalArgumentException("Expected at least four items in COSE_Sign1 array");
-        }
-        byte[] payload = new byte[0];
-        if (items.get(2).getMajorType() == MajorType.SPECIAL) {
-            if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
-                    != SpecialType.SIMPLE_VALUE) {
-                throw new IllegalArgumentException(
-                        "Item 2 (payload) is a special but not a simple value");
-            }
-            SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
-            if (simple.getSimpleValueType() != SimpleValueType.NULL) {
-                throw new IllegalArgumentException(
-                        "Item 2 (payload) is a simple but not the value null");
-            }
-        } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
-            payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
-        } else {
-            throw new IllegalArgumentException("Item 2 (payload) is not nil or byte-string");
-        }
-        return payload;
-    }
-
-    // Returns the empty collection if no x5chain is included in the structure.
-    //
-    // Throws exception if the given bytes aren't valid COSE_Sign1.
-    //
-    public static Collection<X509Certificate> coseSign1GetX5Chain(DataItem coseSign1) {
-        ArrayList<X509Certificate> ret = new ArrayList<>();
-        if (coseSign1.getMajorType() != MajorType.ARRAY) {
-            throw new IllegalArgumentException("Data item is not an array");
-        }
-        List<DataItem> items = ((co.nstant.in.cbor.model.Array) coseSign1).getDataItems();
-        if (items.size() < 4) {
-            throw new IllegalArgumentException("Expected at least four items in COSE_Sign1 array");
-        }
-        if (items.get(1).getMajorType() != MajorType.MAP) {
-            throw new IllegalArgumentException("Item 1 (unprotected headers) is not a map");
-        }
-        co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) items.get(1);
-        DataItem x5chainItem = map.get(new UnsignedInteger(COSE_LABEL_X5CHAIN));
-        if (x5chainItem != null) {
-            try {
-                CertificateFactory factory = CertificateFactory.getInstance("X.509");
-                if (x5chainItem instanceof ByteString) {
-                    ByteArrayInputStream certBais = new ByteArrayInputStream(
-                            ((ByteString) x5chainItem).getBytes());
-                    ret.add((X509Certificate) factory.generateCertificate(certBais));
-                } else if (x5chainItem instanceof Array) {
-                    for (DataItem certItem : ((Array) x5chainItem).getDataItems()) {
-                        if (!(certItem instanceof ByteString)) {
-                            throw new IllegalArgumentException(
-                                    "Unexpected type for array item in x5chain value");
-                        }
-                        ByteArrayInputStream certBais = new ByteArrayInputStream(
-                                ((ByteString) certItem).getBytes());
-                        ret.add((X509Certificate) factory.generateCertificate(certBais));
-                    }
-                } else {
-                    throw new IllegalArgumentException("Unexpected type for x5chain value");
-                }
-            } catch (CertificateException e) {
-                throw new IllegalArgumentException("Unexpected error", e);
-            }
-        }
-        return ret;
-    }
-
-    public static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey,
-            PrivateKey ephemeralReaderPrivateKey,
-            byte[] encodedSessionTranscript) {
-        try {
-            KeyAgreement ka = KeyAgreement.getInstance("ECDH");
-            ka.init(ephemeralReaderPrivateKey);
-            ka.doPhase(authenticationPublicKey, true);
-            byte[] sharedSecret = ka.generateSecret();
-
-            byte[] sessionTranscriptBytes =
-                    Util.cborEncode(Util.cborBuildTaggedByteString(encodedSessionTranscript));
-
-            byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
-            byte[] info = new byte[] {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
-            byte[] derivedKey = computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-
-            SecretKey secretKey = new SecretKeySpec(derivedKey, "");
-            return secretKey;
-        } catch (InvalidKeyException
-                | NoSuchAlgorithmException e) {
-            throw new IllegalStateException("Error performing key agreement", e);
-        }
-    }
-
-
-    public static String cborPrettyPrint(DataItem dataItem) {
-        StringBuilder sb = new StringBuilder();
-        cborPrettyPrintDataItem(sb, 0, dataItem);
-        return sb.toString();
-    }
-
-    public static String cborPrettyPrint(byte[] encodedBytes) {
-        StringBuilder sb = new StringBuilder();
-
-        ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
-        List<DataItem> dataItems = null;
-        try {
-            dataItems = new CborDecoder(bais).decode();
-        } catch (CborException e) {
-            throw new IllegalStateException(e);
-        }
-        int count = 0;
-        for (DataItem dataItem : dataItems) {
-            if (count > 0) {
-                sb.append(",\n");
-            }
-            cborPrettyPrintDataItem(sb, 0, dataItem);
-            count++;
-        }
-
-        return sb.toString();
-    }
-
-    // Returns true iff all elements in |items| are not compound (e.g. an array or a map).
-    private static boolean cborAreAllDataItemsNonCompound(List<DataItem> items) {
-        for (DataItem item : items) {
-            switch (item.getMajorType()) {
-                case ARRAY:
-                case MAP:
-                    return false;
-                default:
-                    // Do nothing
-                    break;
-            }
-        }
-        return true;
-    }
-
-    private static void cborPrettyPrintDataItem(StringBuilder sb, int indent, DataItem dataItem) {
-        StringBuilder indentBuilder = new StringBuilder();
-        for (int n = 0; n < indent; n++) {
-            indentBuilder.append(' ');
-        }
-        String indentString = indentBuilder.toString();
-
-        if (dataItem.hasTag()) {
-            sb.append(String.format("tag %d ", dataItem.getTag().getValue()));
-        }
-
-        switch (dataItem.getMajorType()) {
-            case INVALID:
-                // TODO: throw
-                sb.append("<invalid>");
-                break;
-            case UNSIGNED_INTEGER: {
-                // Major type 0: an unsigned integer.
-                BigInteger value = ((UnsignedInteger) dataItem).getValue();
-                sb.append(value);
-            }
-            break;
-            case NEGATIVE_INTEGER: {
-                // Major type 1: a negative integer.
-                BigInteger value = ((NegativeInteger) dataItem).getValue();
-                sb.append(value);
-            }
-            break;
-            case BYTE_STRING: {
-                // Major type 2: a byte string.
-                byte[] value = ((ByteString) dataItem).getBytes();
-                sb.append("[");
-                int count = 0;
-                for (byte b : value) {
-                    if (count > 0) {
-                        sb.append(", ");
-                    }
-                    sb.append(String.format("0x%02x", b));
-                    count++;
-                }
-                sb.append("]");
-            }
-            break;
-            case UNICODE_STRING: {
-                // Major type 3: string of Unicode characters that is encoded as UTF-8 [RFC3629].
-                String value = ((UnicodeString) dataItem).getString();
-                // TODO: escape ' in |value|
-                sb.append("'" + value + "'");
-            }
-            break;
-            case ARRAY: {
-                // Major type 4: an array of data items.
-                List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
-                if (items.size() == 0) {
-                    sb.append("[]");
-                } else if (cborAreAllDataItemsNonCompound(items)) {
-                    // The case where everything fits on one line.
-                    sb.append("[");
-                    int count = 0;
-                    for (DataItem item : items) {
-                        cborPrettyPrintDataItem(sb, indent, item);
-                        if (++count < items.size()) {
-                            sb.append(", ");
-                        }
-                    }
-                    sb.append("]");
-                } else {
-                    sb.append("[\n" + indentString);
-                    int count = 0;
-                    for (DataItem item : items) {
-                        sb.append("  ");
-                        cborPrettyPrintDataItem(sb, indent + 2, item);
-                        if (++count < items.size()) {
-                            sb.append(",");
-                        }
-                        sb.append("\n" + indentString);
-                    }
-                    sb.append("]");
-                }
-            }
-            break;
-            case MAP: {
-                // Major type 5: a map of pairs of data items.
-                Collection<DataItem> keys = ((co.nstant.in.cbor.model.Map) dataItem).getKeys();
-                if (keys.size() == 0) {
-                    sb.append("{}");
-                } else {
-                    sb.append("{\n" + indentString);
-                    int count = 0;
-                    for (DataItem key : keys) {
-                        sb.append("  ");
-                        DataItem value = ((co.nstant.in.cbor.model.Map) dataItem).get(key);
-                        cborPrettyPrintDataItem(sb, indent + 2, key);
-                        sb.append(" : ");
-                        cborPrettyPrintDataItem(sb, indent + 2, value);
-                        if (++count < keys.size()) {
-                            sb.append(",");
-                        }
-                        sb.append("\n" + indentString);
-                    }
-                    sb.append("}");
-                }
-            }
-            break;
-            case TAG:
-                // Major type 6: optional semantic tagging of other major types
-                //
-                // We never encounter this one since it's automatically handled via the
-                // DataItem that is tagged.
-                throw new IllegalStateException("Semantic tag data item not expected");
-
-            case SPECIAL:
-                // Major type 7: floating point numbers and simple data types that need no
-                // content, as well as the "break" stop code.
-                if (dataItem instanceof SimpleValue) {
-                    switch (((SimpleValue) dataItem).getSimpleValueType()) {
-                        case FALSE:
-                            sb.append("false");
-                            break;
-                        case TRUE:
-                            sb.append("true");
-                            break;
-                        case NULL:
-                            sb.append("null");
-                            break;
-                        case UNDEFINED:
-                            sb.append("undefined");
-                            break;
-                        case RESERVED:
-                            sb.append("reserved");
-                            break;
-                        case UNALLOCATED:
-                            sb.append("unallocated");
-                            break;
-                    }
-                } else if (dataItem instanceof DoublePrecisionFloat) {
-                    DecimalFormat df = new DecimalFormat("0",
-                            DecimalFormatSymbols.getInstance(Locale.ENGLISH));
-                    df.setMaximumFractionDigits(340);
-                    sb.append(df.format(((DoublePrecisionFloat) dataItem).getValue()));
-                } else if (dataItem instanceof AbstractFloat) {
-                    DecimalFormat df = new DecimalFormat("0",
-                            DecimalFormatSymbols.getInstance(Locale.ENGLISH));
-                    df.setMaximumFractionDigits(340);
-                    sb.append(df.format(((AbstractFloat) dataItem).getValue()));
-                } else {
-                    sb.append("break");
-                }
-                break;
-        }
-    }
-
-    static byte[] canonicalizeCbor(byte[] encodedCbor) throws CborException {
-        ByteArrayInputStream bais = new ByteArrayInputStream(encodedCbor);
-        List<DataItem> dataItems = new CborDecoder(bais).decode();
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        for (DataItem dataItem : dataItems) {
-            CborEncoder encoder = new CborEncoder(baos);
-            encoder.encode(dataItem);
-        }
-        return baos.toByteArray();
-    }
-
-    public static String replaceLine(String text, int lineNumber, String replacementLine) {
-        @SuppressWarnings("StringSplitter")
-        String[] lines = text.split("\n");
-        int numLines = lines.length;
-        if (lineNumber < 0) {
-            lineNumber = numLines - -lineNumber;
-        }
-        StringBuilder sb = new StringBuilder();
-        for (int n = 0; n < numLines; n++) {
-            if (n == lineNumber) {
-                sb.append(replacementLine);
-            } else {
-                sb.append(lines[n]);
-            }
-            // Only add terminating newline if passed-in string ends in a newline.
-            if (n == numLines - 1) {
-                if (text.endsWith("\n")) {
-                    sb.append('\n');
-                }
-            } else {
-                sb.append('\n');
-            }
-        }
-        return sb.toString();
-    }
-
-    // This returns a SessionTranscript which satisfy the requirement
-    // that the uncompressed X and Y coordinates of the public key for the
-    // mDL's ephemeral key-pair appear somewhere in the encoded
-    // DeviceEngagement.
-    static byte[] buildSessionTranscript(KeyPair ephemeralKeyPair) {
-        // Make the coordinates appear in an already encoded bstr - this
-        // mimics how the mDL COSE_Key appear as encoded data inside the
-        // encoded DeviceEngagement
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
-            // X and Y are always positive so for interop we remove any leading zeroes
-            // inserted by the BigInteger encoder.
-            byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
-            byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
-            baos.write(new byte[]{42});
-            baos.write(x);
-            baos.write(y);
-            baos.write(new byte[]{43, 44});
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-        byte[] blobWithCoords = baos.toByteArray();
-
-        DataItem encodedDeviceEngagementItem = cborBuildTaggedByteString(
-                cborEncode(new CborBuilder()
-                        .addArray()
-                        .add(blobWithCoords)
-                        .end()
-                        .build().get(0)));
-        DataItem encodedEReaderKeyItem =
-                cborBuildTaggedByteString(Util.cborEncodeString("doesn't matter"));
-
-        baos = new ByteArrayOutputStream();
-        try {
-            byte[] handoverSelectBytes = new byte[] {0x01, 0x02, 0x03};
-            DataItem handover = new CborBuilder()
-                    .addArray()
-                    .add(handoverSelectBytes)
-                    .add(SimpleValue.NULL)
-                    .end()
-                    .build().get(0);
-            new CborEncoder(baos).encode(new CborBuilder()
-                    .addArray()
-                    .add(encodedDeviceEngagementItem)
-                    .add(encodedEReaderKeyItem)
-                    .add(handover)
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            e.printStackTrace();
-            return null;
-        }
-        return baos.toByteArray();
-    }
-
-    static IdentityCredentialStore getIdentityCredentialStore(@NonNull Context context) {
-        // We generally want to run all tests against the software implementation since
-        // hardware-based implementations are already tested against CTS and VTS and the bulk
-        // of the code in the Jetpack is the software implementation. This also helps avoid
-        // whatever bugs or flakiness that may exist in hardware implementations.
-        //
-        // Occasionally it's useful for a developer to test that the hardware-backed paths
-        // (HardwareIdentityCredentialStore + friends) work as intended. This can be done by
-        // uncommenting the line below and making sure it runs on a device with the appropriate
-        // hardware support.
-        //
-        // See b/164480361 for more discussion.
-        //
-        //return IdentityCredentialStore.getHardwareInstance(context);
-        return IdentityCredentialStore.getSoftwareInstance(context);
-    }
-
-        /*
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 1 (0x1)
-    Signature Algorithm: ecdsa-with-SHA256
-        Issuer: CN=fake
-        Validity
-            Not Before: Jan  1 00:00:00 1970 GMT
-            Not After : Jan  1 00:00:00 2048 GMT
-        Subject: CN=fake
-        Subject Public Key Info:
-            Public Key Algorithm: id-ecPublicKey
-                Public-Key: (256 bit)
-                00000000  04 9b 60 70 8a 99 b6 bf  e3 b8 17 02 9e 93 eb 48  |..`p...........H|
-                00000010  23 b9 39 89 d1 00 bf a0  0f d0 2f bd 6b 11 bc d1  |#.9......./.k...|
-                00000020  19 53 54 28 31 00 f5 49  db 31 fb 9f 7d 99 bf 23  |.ST(1..I.1..}..#|
-                00000030  fb 92 04 6b 23 63 55 98  ad 24 d2 68 c4 83 bf 99  |...k#cU..$.h....|
-                00000040  62                                                |b|
-    Signature Algorithm: ecdsa-with-SHA256
-         30:45:02:20:67:ad:d1:34:ed:a5:68:3f:5b:33:ee:b3:18:a2:
-         eb:03:61:74:0f:21:64:4a:a3:2e:82:b3:92:5c:21:0f:88:3f:
-         02:21:00:b7:38:5c:9b:f2:9c:b1:27:86:37:44:df:eb:4a:b2:
-         6c:11:9a:c1:ff:b2:80:95:ce:fc:5f:26:b4:20:6e:9b:0d
-     */
-    static @NonNull X509Certificate signPublicKeyWithPrivateKey(String keyToSignAlias,
-            String keyToSignWithAlias) {
-
-        KeyStore ks = null;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-
-            /* First note that KeyStore.getCertificate() returns a self-signed X.509 certificate
-             * for the key in question. As per RFC 5280, section 4.1 an X.509 certificate has the
-             * following structure:
-             *
-             *   Certificate  ::=  SEQUENCE  {
-             *        tbsCertificate       TBSCertificate,
-             *        signatureAlgorithm   AlgorithmIdentifier,
-             *        signatureValue       BIT STRING  }
-             *
-             * Conveniently, the X509Certificate class has a getTBSCertificate() method which
-             * returns the tbsCertificate blob. So all we need to do is just sign that and build
-             * signatureAlgorithm and signatureValue and combine it with tbsCertificate. We don't
-             * need a full-blown ASN.1/DER encoder to do this.
-             */
-            X509Certificate selfSignedCert = (X509Certificate) ks.getCertificate(keyToSignAlias);
-            byte[] tbsCertificate = selfSignedCert.getTBSCertificate();
-
-            KeyStore.Entry keyToSignWithEntry = ks.getEntry(keyToSignWithAlias, null);
-            Signature s = Signature.getInstance("SHA256withECDSA");
-            s.initSign(((KeyStore.PrivateKeyEntry) keyToSignWithEntry).getPrivateKey());
-            s.update(tbsCertificate);
-            byte[] signatureValue = s.sign();
-
-            /* The DER encoding for a SEQUENCE of length 128-65536 - the length is updated below.
-             *
-             * We assume - and test for below - that the final length is always going to be in
-             * this range. This is a sound assumption given we're using 256-bit EC keys.
-             */
-            byte[] sequence = new byte[]{
-                    0x30, (byte) 0x82, 0x00, 0x00
-            };
-
-            /* The DER encoding for the ECDSA with SHA-256 signature algorithm:
-             *
-             *   SEQUENCE (1 elem)
-             *      OBJECT IDENTIFIER 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA
-             *      algorithm with SHA256)
-             */
-            byte[] signatureAlgorithm = new byte[]{
-                    0x30, 0x0a, 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x04, 0x03,
-                    0x02
-            };
-
-            /* The DER encoding for a BIT STRING with one element - the length is updated below.
-             *
-             * We assume the length of signatureValue is always going to be less than 128. This
-             * assumption works since we know ecdsaWithSHA256 signatures are always 69, 70, or
-             * 71 bytes long when DER encoded.
-             */
-            byte[] bitStringForSignature = new byte[]{0x03, 0x00, 0x00};
-
-            // Calculate sequence length and set it in |sequence|.
-            int sequenceLength = tbsCertificate.length
-                    + signatureAlgorithm.length
-                    + bitStringForSignature.length
-                    + signatureValue.length;
-            if (sequenceLength < 128 || sequenceLength > 65535) {
-                throw new Exception("Unexpected sequenceLength " + sequenceLength);
-            }
-            sequence[2] = (byte) (sequenceLength >> 8);
-            sequence[3] = (byte) (sequenceLength & 0xff);
-
-            // Calculate signatureValue length and set it in |bitStringForSignature|.
-            int signatureValueLength = signatureValue.length + 1;
-            if (signatureValueLength >= 128) {
-                throw new Exception("Unexpected signatureValueLength " + signatureValueLength);
-            }
-            bitStringForSignature[1] = (byte) signatureValueLength;
-
-            // Finally concatenate everything together.
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            baos.write(sequence);
-            baos.write(tbsCertificate);
-            baos.write(signatureAlgorithm);
-            baos.write(bitStringForSignature);
-            baos.write(signatureValue);
-            byte[] resultingCertBytes = baos.toByteArray();
-
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            ByteArrayInputStream bais = new ByteArrayInputStream(resultingCertBytes);
-            X509Certificate result = (X509Certificate) cf.generateCertificate(bais);
-            return result;
-        } catch (Exception e) {
-            throw new IllegalStateException("Error signing public key with private key", e);
-        }
-    }
-
-    /*
-     * Helper function to create a CBOR data for requesting data items. The IntentToRetain
-     * value will be set to false for all elements.
-     *
-     * <p>The returned CBOR data conforms to the following CDDL schema:</p>
-     *
-     * <pre>
-     *   ItemsRequest = {
-     *     ? "docType" : DocType,
-     *     "nameSpaces" : NameSpaces,
-     *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
-     *   }
-     *
-     *   NameSpaces = {
-     *     + NameSpace => DataElements     ; Requested data elements for each NameSpace
-     *   }
-     *
-     *   DataElements = {
-     *     + DataElement => IntentToRetain
-     *   }
-     *
-     *   DocType = tstr
-     *
-     *   DataElement = tstr
-     *   IntentToRetain = bool
-     *   NameSpace = tstr
-     * </pre>
-     *
-     * @param entriesToRequest       The entries to request, organized as a map of namespace
-     *                               names with each value being a collection of data elements
-     *                               in the given namespace.
-     * @param docType                  The document type or {@code null} if there is no document
-     *                                 type.
-     * @return CBOR data conforming to the CDDL mentioned above.
-     */
-    static byte @NonNull [] createItemsRequest(
-            java.util.@NonNull Map<String, Collection<String>> entriesToRequest,
-            @Nullable String docType) {
-        CborBuilder builder = new CborBuilder();
-        MapBuilder<CborBuilder> mapBuilder = builder.addMap();
-        if (docType != null) {
-            mapBuilder.put("docType", docType);
-        }
-
-        MapBuilder<MapBuilder<CborBuilder>> nsMapBuilder = mapBuilder.putMap("nameSpaces");
-        for (String namespaceName : entriesToRequest.keySet()) {
-            Collection<String> entryNames = entriesToRequest.get(namespaceName);
-            MapBuilder<MapBuilder<MapBuilder<CborBuilder>>> entryNameMapBuilder =
-                    nsMapBuilder.putMap(namespaceName);
-            for (String entryName : entryNames) {
-                entryNameMapBuilder.put(entryName, false);
-            }
-        }
-        return cborEncode(builder.build().get(0));
-    }
-
-    static KeyPair createEphemeralKeyPair() {
-        try {
-            KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
-            ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
-            kpg.initialize(ecSpec);
-            KeyPair keyPair = kpg.generateKeyPair();
-            return keyPair;
-        } catch (NoSuchAlgorithmException
-                | InvalidAlgorithmParameterException e) {
-            throw new IllegalStateException("Error generating ephemeral key-pair", e);
-        }
-    }
-
-    static byte[] getPopSha256FromAuthKeyCert(X509Certificate cert) {
-        byte[] octetString = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26");
-        if (octetString == null) {
-            return null;
-        }
-        try {
-            ASN1InputStream asn1InputStream = new ASN1InputStream(octetString);
-            byte[] cborBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
-
-            ByteArrayInputStream bais = new ByteArrayInputStream(cborBytes);
-            List<DataItem> dataItems = new CborDecoder(bais).decode();
-            if (dataItems.size() != 1) {
-                throw new IllegalArgumentException("Expected 1 item, found " + dataItems.size());
-            }
-            if (!(dataItems.get(0) instanceof Array)) {
-                throw new IllegalArgumentException("Item is not a map");
-            }
-            Array array = (Array) dataItems.get(0);
-            List<DataItem> items = array.getDataItems();
-            if (items.size() < 2) {
-                throw new IllegalArgumentException(
-                        "Expected at least 2 array items, found " + items.size());
-            }
-            if (!(items.get(0) instanceof UnicodeString)) {
-                throw new IllegalArgumentException("First array item is not a string");
-            }
-            String id = ((UnicodeString) items.get(0)).getString();
-            if (!id.equals("ProofOfBinding")) {
-                throw new IllegalArgumentException("Expected ProofOfBinding, got " + id);
-            }
-            if (!(items.get(1) instanceof ByteString)) {
-                throw new IllegalArgumentException("Second array item is not a bytestring");
-            }
-            byte[] popSha256 = ((ByteString) items.get(1)).getBytes();
-            if (popSha256.length != 32) {
-                throw new IllegalArgumentException(
-                        "Expected bstr to be 32 bytes, it is " + popSha256.length);
-            }
-            return popSha256;
-        } catch (IOException e) {
-            throw new IllegalArgumentException("Error decoding extension data", e);
-        } catch (CborException e) {
-            throw new IllegalArgumentException("Error decoding data", e);
-        }
-    }
-}
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/WritableIdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/WritableIdentityCredential.java
deleted file mode 100644
index d7da98c..0000000
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/WritableIdentityCredential.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.security.identity;
-
-import androidx.annotation.RestrictTo;
-
-import org.jspecify.annotations.NonNull;
-
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-
-/**
- * Class used to personalize a new identity credential.
- *
- * <p>Note that the credential is not persisted until calling
- * {@link #personalize(PersonalizationData)}.
- *
- * <p>Once persisted, the PII in a credential can be updated using
- * {@link IdentityCredential#update(PersonalizationData)}.
- * <p>
- * Use {@link IdentityCredentialStore#createCredential(String, String)} to create a new credential.
- */
-public abstract class WritableIdentityCredential {
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    protected WritableIdentityCredential() {}
-
-    /**
-     * Generates and returns an X.509 certificate chain for the CredentialKey which identifies this
-     * credential to the issuing authority. The certificate contains an
-     * <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a>
-     * attestation extension which describes the key and the security hardware in which it lives.
-     *
-     * <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not
-     * limited to) checking that the root certificate is well-known, whether the tag
-     * Tag::IDENTITY_CREDENTIAL_KEY is present, the passed in challenge is present, the tag
-     * Tag::ATTESTATION_APPLICATION_ID is set to the expected Android application, the device
-     * has verified boot enabled, each certificate in the chain is signed by its successor,
-     * none of the certificates have been revoked, and so on.
-     *
-     * <p>If {@link WritableIdentityCredential} is not hardware-backed the credential is
-     * implemented using Android Keystore and the attestation extension will
-     * not contain the tag Tag::IDENTITY_CREDENTIAL_KEY. Otherwise if this tag is present
-     * it signals that {@link WritableIdentityCredential} is hardware-backed and CredentialKey
-     * and corresponding authentication keys can only sign/MAC very specific
-     * messages. This is in contrast to Android Keystore key which can be used to
-     * sign/MAC anything.
-     *
-     * <p>It is not strictly necessary to use this method to provision a credential if the issuing
-     * authority doesn't care about the nature of the security hardware. If called, however, this
-     * method must be called before {@link #personalize(PersonalizationData)}.
-     *
-     * <p>Note that the credential is not persisted until calling
-     * {@link #personalize(PersonalizationData)}.
-     *
-     * @param challenge is a non-empty byte array whose contents should be unique, fresh and
-     *                  provided by the issuing authority. The value provided is embedded in the
-     *                  attestation extension and enables the issuing authority to verify that the
-     *                  attestation certificate is fresh.
-     * @return the X.509 certificate for this credential's CredentialKey.
-     */
-    public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
-            byte @NonNull [] challenge);
-
-    /**
-     * Stores all of the data in the credential, with the specified access control profiles.
-     *
-     * <p>The credential is persisted only after this method returns successfully.
-     *
-     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey with payload
-     * set to {@code ProofOfProvisioning} as defined below.
-     *
-     * <pre>
-     *     ProofOfProvisioning = [
-     *          "ProofOfProvisioning",        ; tstr
-     *          tstr,                         ; DocType
-     *          [ * AccessControlProfile ],
-     *          ProvisionedData,
-     *          bool                          ; true if this is a test credential, should
-     *                                        ; always be false.
-     *      ]
-     *
-     *      AccessControlProfile = {
-     *          "id": uint,
-     *          ? "readerCertificate" : bstr,
-     *          ? (
-     *               "userAuthenticationRequired" : bool,
-     *               "timeoutMillis" : uint,
-     *          )
-     *      }
-     *
-     *      ProvisionedData = {
-     *          * Namespace =&gt; [ + Entry ]
-     *      },
-     *
-     *      Namespace = tstr
-     *
-     *      Entry = {
-     *          "name" : tstr,
-     *          "value" : any,
-     *          "accessControlProfiles" : [ * uint ],
-     *      }
-     * </pre>
-     *
-     * <p>This data structure provides a guarantee to the issuer about the data which may be
-     * returned in the CBOR returned by
-     * {@link ResultData#getAuthenticatedData()} during a credential
-     * presentation.
-     *
-     * @param personalizationData   The data to provision, including access control profiles
-     *                              and data elements and their values, grouped into namespaces.
-     * @return A COSE_Sign1 data structure, see above.
-     */
-    public abstract byte @NonNull [] personalize(
-            @NonNull PersonalizationData personalizationData);
-}
diff --git a/settings.gradle b/settings.gradle
index b80017f..7bdea24 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -970,7 +970,6 @@
 includeProject(":security:security-biometric", [BuildType.MAIN])
 includeProject(":security:security-crypto", [BuildType.MAIN])
 includeProject(":security:security-crypto-ktx", [BuildType.MAIN])
-includeProject(":security:security-identity-credential", [BuildType.MAIN])
 includeProject(":security:security-mls", [BuildType.MAIN])
 includeProject(":security:security-state", [BuildType.MAIN])
 includeProject(":security:security-state-provider", [BuildType.MAIN])