Merge "Rename VirtualSessionState to be CaptureSessionState and update comments." into androidx-main
diff --git a/annotation/annotation-experimental/lint-baseline.xml b/annotation/annotation-experimental/lint-baseline.xml
new file mode 100644
index 0000000..47688b5
--- /dev/null
+++ b/annotation/annotation-experimental/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+        errorLine1="@java.lang.annotation.Target("
+        errorLine2="                      ~~~~~~">
+        <location
+            file="src/main/java/androidx/annotation/OptIn.kt"/>
+    </issue>
+
+</issues>
diff --git a/annotation/annotation/lint-baseline.xml b/annotation/annotation/lint-baseline.xml
new file mode 100644
index 0000000..a148b58
--- /dev/null
+++ b/annotation/annotation/lint-baseline.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+        errorLine1="@java.lang.annotation.Target("
+        errorLine2="                      ~~~~~~">
+        <location
+            file="src/main/java/androidx/annotation/Keep.kt"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+        errorLine1="@java.lang.annotation.Target("
+        errorLine2="                      ~~~~~~">
+        <location
+            file="src/main/java/androidx/annotation/NonNull.kt"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+        errorLine1="@java.lang.annotation.Target(METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE)"
+        errorLine2="                      ~~~~~~">
+        <location
+            file="src/main/java/androidx/annotation/Nullable.kt"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+        errorLine1="@java.lang.annotation.Target(TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE)"
+        errorLine2="                      ~~~~~~">
+        <location
+            file="src/main/java/androidx/annotation/RequiresApi.kt"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Do not use `@java.lang.annotation.Target` here; it will cause the annotation to not be allowed on **any** element types from Java"
+        errorLine1="@java.lang.annotation.Target("
+        errorLine2="                      ~~~~~~">
+        <location
+            file="src/main/java/androidx/annotation/RestrictTo.kt"/>
+    </issue>
+
+</issues>
diff --git a/appsearch/appsearch/lint-baseline.xml b/appsearch/appsearch/lint-baseline.xml
new file mode 100644
index 0000000..9eebc3d
--- /dev/null
+++ b/appsearch/appsearch/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="This annotation does not apply for type java.util.Set&lt;java.util.Set&lt;java.lang.Integer>>; expected int"
+        errorLine1="                @SetSchemaRequest.AppSearchSupportedPermission @NonNull"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/appsearch/app/GetSchemaResponse.java"/>
+    </issue>
+
+</issues>
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index 7466d0f..fb018a7 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -51,6 +51,8 @@
             " Use `adb root`."
     }
 
+    getInstalledPackageInfo(packageName) // throws clearly if not installed
+
     val startTime = System.nanoTime()
     val scope = MacrobenchmarkScope(packageName, launchWithClearTask = true)
 
@@ -72,10 +74,12 @@
     killProcessBlock.invoke()
     try {
         userspaceTrace("compile $packageName") {
+            var iteration = 1
             compilationMode.resetAndCompile(
                 packageName = packageName,
                 killProcessBlock = killProcessBlock
             ) {
+                scope.iteration = iteration++
                 profileBlock(scope)
             }
         }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index e63fb5f..fe156f1 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -16,6 +16,7 @@
 
 package androidx.benchmark.macro
 
+import android.content.pm.ApplicationInfo
 import android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE
 import android.content.pm.PackageManager
 import android.os.Build
@@ -39,18 +40,24 @@
 import androidx.tracing.trace
 import java.io.File
 
+/**
+ * Get package ApplicationInfo, throw if not found
+ */
 @Suppress("DEPRECATION")
-internal fun checkErrors(packageName: String): ConfigurationError.SuppressionState? {
+internal fun getInstalledPackageInfo(packageName: String): ApplicationInfo {
     val pm = InstrumentationRegistry.getInstrumentation().context.packageManager
-
-    val applicationInfo = try {
-        pm.getApplicationInfo(packageName, 0)
+    try {
+        return pm.getApplicationInfo(packageName, 0)
     } catch (notFoundException: PackageManager.NameNotFoundException) {
         throw AssertionError(
             "Unable to find target package $packageName, is it installed?",
             notFoundException
         )
     }
+}
+
+internal fun checkErrors(packageName: String): ConfigurationError.SuppressionState? {
+    val applicationInfo = getInstalledPackageInfo(packageName)
 
     val errorNotProfileable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
         applicationInfo.isNotProfileableByShell()
diff --git a/benchmark/integration-tests/macrobenchmark/build.gradle b/benchmark/integration-tests/macrobenchmark/build.gradle
index b9738c4..e823480 100644
--- a/benchmark/integration-tests/macrobenchmark/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark/build.gradle
@@ -41,6 +41,7 @@
     androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
     androidTestImplementation(project(":internal-testutils-macrobenchmark"))
     androidTestImplementation(project(":tracing:tracing-ktx"))
+    androidTestImplementation(libs.kotlinTest)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
similarity index 77%
rename from benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt
rename to benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
index 0ae84d3..cb321f5 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileFilterTest.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
@@ -25,6 +25,10 @@
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import java.io.File
+import kotlin.test.assertFailsWith
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
 import org.junit.Assume.assumeTrue
 import org.junit.Rule
 import org.junit.Test
@@ -32,24 +36,41 @@
 @LargeTest
 @SdkSuppress(minSdkVersion = 29)
 @OptIn(ExperimentalBaselineProfilesApi::class)
-class BaselineProfileFilterTest {
+class BaselineProfileRuleTest {
 
     @get:Rule
     val baselineRule = BaselineProfileRule()
 
     @Test
-    fun baselineProfilesFilter() {
+    fun appNotInstalled() {
+        val error = assertFailsWith<AssertionError> {
+            baselineRule.collectBaselineProfile(
+                packageName = "fake.package.not.installed",
+                profileBlock = {
+                    fail("not expected")
+                }
+            )
+        }
+        println(error.message)
+        assertTrue(error.message!!.contains("Unable to find target package"))
+    }
+
+    @Test
+    fun filter() {
         assumeTrue(Shell.isSessionRooted())
 
+        var nextIteration = 1
         // Collects the baseline profile
         baselineRule.collectBaselineProfile(
             packageName = PACKAGE_NAME,
             packageFilters = listOf(PACKAGE_NAME),
             profileBlock = {
+                assertEquals(nextIteration++, iteration)
                 startActivityAndWait(Intent(ACTION))
                 device.waitForIdle()
             }
         )
+        assertEquals(4, nextIteration)
 
         // Asserts the output of the baseline profile
         val lines = File(Outputs.outputDirectory, BASELINE_PROFILE_OUTPUT_FILE_NAME).readLines()
@@ -72,6 +93,6 @@
         // according to the patter `<class>_<method>-baseline-prof.txt`. Changes for class and
         // method names should be reflected here in order for the test to succeed.
         private const val BASELINE_PROFILE_OUTPUT_FILE_NAME =
-            "BaselineProfileFilterTest_baselineProfilesFilter-baseline-prof.txt"
+            "BaselineProfileRuleTest_filter-baseline-prof.txt"
     }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 0a8392a..26c4b7d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -202,9 +202,6 @@
         // Broken in 7.0.0-alpha15 due to b/180408990
         disable.add("RestrictedApi")
 
-        // Reenable after upgradingto 7.1.0-beta01
-        disable.add("SupportAnnotationUsage")
-
         // Provide stricter enforcement for project types intended to run on a device.
         if (extension.type.compilationTarget == CompilationTarget.DEVICE) {
             fatal.add("Assert")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
index 344be48..c0ca9a5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/studio/StudioTask.kt
@@ -198,8 +198,8 @@
         check(vmOptions.exists()) {
             "Invalid Studio vm options file location: ${vmOptions.canonicalPath}"
         }
-        val processBuilder = ProcessBuilder().apply {
-            redirectErrorStream(true)
+        ProcessBuilder().apply {
+            inheritIO()
             with(platformUtilities) { command(launchCommandArguments) }
 
             val additionalStudioEnvironmentProperties = mapOf(
@@ -218,14 +218,7 @@
 
             // Append to the existing environment variables set by gradlew and the user.
             environment().putAll(additionalStudioEnvironmentProperties)
-        }
-        val process = processBuilder.start()
-        process.waitFor()
-        // Can't just use inheritIO due to https://github.com/gradle/gradle/issues/16719
-        val outputText = process.stdOutText()
-        println(outputText)
-        check(process.exitValue() == 0) {
-            "Studio failed to start:\n$outputText"
+            start()
         }
     }
 
@@ -319,10 +312,3 @@
     override val vmOptions
         get() = supportRootFolder.resolve("playground-common/studio.vmoptions")
 }
-
-/**
- * Returns the contents of the process's standard output stream as a string
- */
-fun Process.stdOutText(): String {
-    return inputStream.bufferedReader().use { it.readText() }
-}
diff --git a/busytown/androidx-studio-integration.sh b/busytown/androidx-studio-integration.sh
index d575250ea..ea45943 100755
--- a/busytown/androidx-studio-integration.sh
+++ b/busytown/androidx-studio-integration.sh
@@ -8,6 +8,7 @@
   -x lintDebug \
   -x lintWithExpandProjectionDebug \
   -x lintWithoutExpandProjectionDebug \
+  -x lintWithNullAwareTypeConverterDebug \
   -x lintReport \
   -x verifyDependencyVersions \
   listTaskOutputs \
diff --git a/camera/camera-camera2-pipe-integration/lint-baseline.xml b/camera/camera-camera2-pipe-integration/lint-baseline.xml
new file mode 100644
index 0000000..acec75e
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+        errorLine1="    @VisibleForTesting"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt"/>
+    </issue>
+
+</issues>
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceEffectTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
similarity index 84%
rename from camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceEffectTest.kt
rename to camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
index d80c7b3..49f2220 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceEffectTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
@@ -20,8 +20,8 @@
 import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
 import android.util.Size
 import android.view.Surface
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.ImageFormatConstants
@@ -51,7 +51,7 @@
 @RunWith(AndroidJUnit4::class)
 @LargeTest
 @SdkSuppress(minSdkVersion = 21)
-class DefaultSurfaceEffectTest {
+class DefaultSurfaceProcessorTest {
 
     companion object {
         private const val WIDTH = 640
@@ -88,7 +88,7 @@
     @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest(testCameraRule, testCameraIdListRule)
 
-    private lateinit var surfaceEffect: DefaultSurfaceEffect
+    private lateinit var surfaceProcessor: DefaultSurfaceProcessor
     private lateinit var cameraDeviceHolder: CameraUtil.CameraDeviceHolder
     private lateinit var renderOutput: RenderOutput<*>
     private val inputSurfaceRequestsToClose = mutableListOf<SurfaceRequest>()
@@ -102,9 +102,9 @@
         if (::renderOutput.isInitialized) {
             renderOutput.release()
         }
-        if (::surfaceEffect.isInitialized) {
-            surfaceEffect.release()
-            surfaceEffect.awaitReleased(10_000L)
+        if (::surfaceProcessor.isInitialized) {
+            surfaceProcessor.release()
+            surfaceProcessor.awaitReleased(10_000L)
         }
         for (surfaceRequest in inputSurfaceRequestsToClose) {
             surfaceRequest.deferrableSurface.close()
@@ -114,18 +114,18 @@
     @Test
     fun release_closeAllSurfaceOutputs(): Unit = runBlocking {
         // Arrange.
-        createSurfaceEffect()
+        createSurfaceProcessor()
         val surfaceOutput1 = createSurfaceOutput()
-        surfaceEffect.onOutputSurface(surfaceOutput1)
+        surfaceProcessor.onOutputSurface(surfaceOutput1)
 
         val surfaceOutput2 = createSurfaceOutput()
-        surfaceEffect.onOutputSurface(surfaceOutput2)
+        surfaceProcessor.onOutputSurface(surfaceOutput2)
 
-        surfaceEffect.idle()
+        surfaceProcessor.idle()
 
         // Act.
-        surfaceEffect.release()
-        surfaceEffect.awaitReleased(5000)
+        surfaceProcessor.release()
+        surfaceProcessor.awaitReleased(5000)
 
         // Assert.
         assertThat(surfaceOutput1.isClosed).isTrue()
@@ -135,12 +135,12 @@
     @Test
     fun callOnInputSurfaceAfterReleased_willNotProvideSurface() {
         // Arrange.
-        createSurfaceEffect()
+        createSurfaceProcessor()
         val surfaceRequest = createInputSurfaceRequest()
 
         // Act.
-        surfaceEffect.release()
-        surfaceEffect.onInputSurface(surfaceRequest)
+        surfaceProcessor.release()
+        surfaceProcessor.onInputSurface(surfaceRequest)
 
         // Assert.
         try {
@@ -154,13 +154,13 @@
     @Test
     fun callOnOutputSurfaceAfterReleased_closeSurfaceOutput(): Unit = runBlocking {
         // Arrange.
-        createSurfaceEffect()
+        createSurfaceProcessor()
         val surfaceOutput = createSurfaceOutput()
 
         // Act.
-        surfaceEffect.release()
-        surfaceEffect.awaitReleased()
-        surfaceEffect.onOutputSurface(surfaceOutput)
+        surfaceProcessor.release()
+        surfaceProcessor.awaitReleased()
+        surfaceProcessor.onOutputSurface(surfaceOutput)
 
         // Assert.
         assertThat(surfaceOutput.isClosed).isTrue()
@@ -169,14 +169,14 @@
     @Test
     fun requestCloseAfterOnOutputSurface_closeSurfaceOutput() {
         // Arrange.
-        createSurfaceEffect()
+        createSurfaceProcessor()
         val surfaceOutput = createSurfaceOutput()
 
         // Act.
-        surfaceEffect.onOutputSurface(surfaceOutput)
-        surfaceEffect.idle()
+        surfaceProcessor.onOutputSurface(surfaceOutput)
+        surfaceProcessor.idle()
         surfaceOutput.requestClose()
-        surfaceEffect.idle()
+        surfaceProcessor.idle()
 
         // Assert.
         assertThat(surfaceOutput.isClosed).isTrue()
@@ -185,13 +185,13 @@
     @Test
     fun requestCloseBeforeOnOutputSurface_closeSurfaceOutput() {
         // Arrange.
-        createSurfaceEffect()
+        createSurfaceProcessor()
         val surfaceOutput = createSurfaceOutput()
 
         // Act.
         surfaceOutput.requestClose()
-        surfaceEffect.onOutputSurface(surfaceOutput)
-        surfaceEffect.idle()
+        surfaceProcessor.onOutputSurface(surfaceOutput)
+        surfaceProcessor.idle()
 
         // Assert.
         assertThat(surfaceOutput.isClosed).isTrue()
@@ -225,7 +225,7 @@
     fun createByInvalidShaderString_throwException() {
         val shaderProvider = createCustomShaderProvider(shaderString = "Invalid shader")
         assertThrows(IllegalArgumentException::class.java) {
-            createSurfaceEffect(shaderProvider)
+            createSurfaceProcessor(shaderProvider)
         }
     }
 
@@ -234,7 +234,7 @@
         val shaderProvider =
             createCustomShaderProvider(exceptionToThrow = RuntimeException("Failed Shader"))
         assertThrows(IllegalArgumentException::class.java) {
-            createSurfaceEffect(shaderProvider)
+            createSurfaceProcessor(shaderProvider)
         }
     }
 
@@ -242,7 +242,7 @@
     fun createByIncorrectSamplerName_throwException() {
         val shaderProvider = createCustomShaderProvider(samplerVarName = "_mySampler_")
         assertThrows(IllegalArgumentException::class.java) {
-            createSurfaceEffect(shaderProvider)
+            createSurfaceProcessor(shaderProvider)
         }
     }
 
@@ -250,7 +250,7 @@
     fun createByIncorrectFragCoordsName_throwException() {
         val shaderProvider = createCustomShaderProvider(fragCoordsVarName = "_myFragCoords_")
         assertThrows(IllegalArgumentException::class.java) {
-            createSurfaceEffect(shaderProvider)
+            createSurfaceProcessor(shaderProvider)
         }
     }
 
@@ -258,10 +258,10 @@
         outputType: OutputType,
         shaderProvider: ShaderProvider = ShaderProvider.DEFAULT
     ) {
-        createSurfaceEffect(shaderProvider)
+        createSurfaceProcessor(shaderProvider)
         // Prepare input
         val inputSurfaceRequest = createInputSurfaceRequest()
-        surfaceEffect.onInputSurface(inputSurfaceRequest)
+        surfaceProcessor.onInputSurface(inputSurfaceRequest)
         val inputDeferrableSurface = inputSurfaceRequest.deferrableSurface
         val inputSurface = inputDeferrableSurface.surface.await()
         openCameraAndSetRepeating(inputSurface)
@@ -272,7 +272,7 @@
         // Prepare output
         renderOutput = RenderOutput.createRenderOutput(outputType)
         val surfaceOutput = createSurfaceOutput(renderOutput.surface)
-        surfaceEffect.onOutputSurface(surfaceOutput)
+        surfaceProcessor.onOutputSurface(surfaceOutput)
 
         // Assert.
         assertThat(renderOutput.await(/*imageCount=*/5, /*timeoutInMs=*/10_000L)).isTrue()
@@ -289,8 +289,10 @@
         )
     }
 
-    private fun createSurfaceEffect(shaderProvider: ShaderProvider = ShaderProvider.DEFAULT) {
-        surfaceEffect = DefaultSurfaceEffect(shaderProvider)
+    private fun createSurfaceProcessor(shaderProvider: ShaderProvider = ShaderProvider.DEFAULT) {
+        surfaceProcessor = DefaultSurfaceProcessor(
+            shaderProvider
+        )
     }
 
     private fun createInputSurfaceRequest(): SurfaceRequest {
@@ -302,7 +304,7 @@
     private fun createSurfaceOutput(surface: Surface = mock(Surface::class.java)) =
         SurfaceOutputImpl(
             surface,
-            SurfaceEffect.PREVIEW,
+            SurfaceProcessor.PREVIEW,
             ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
             Size(WIDTH, HEIGHT),
             USE_SURFACE_TEXTURE_TRANSFORM,
@@ -334,11 +336,11 @@
         }
     }
 
-    private fun DefaultSurfaceEffect.idle() {
+    private fun DefaultSurfaceProcessor.idle() {
         HandlerUtil.waitForLooperToIdle(mGlHandler)
     }
 
-    private suspend fun DefaultSurfaceEffect.awaitReleased(timeoutMs: Long = 5000L) {
+    private suspend fun DefaultSurfaceProcessor.awaitReleased(timeoutMs: Long = 5000L) {
         withTimeoutOrNull(timeoutMs) {
             while (true) {
                 delay(500L)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
index b4d7822..2445aa0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
@@ -39,7 +39,7 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    @IntDef(flag = true, value = {SurfaceEffect.PREVIEW, SurfaceEffect.VIDEO_CAPTURE,
+    @IntDef(flag = true, value = {SurfaceProcessor.PREVIEW, SurfaceProcessor.VIDEO_CAPTURE,
             ImageEffect.IMAGE_CAPTURE})
     @interface Targets {
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java b/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
index 1025eb6..ba6b61f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java
@@ -16,7 +16,7 @@
 
 package androidx.camera.core;
 
-import static androidx.camera.core.SurfaceEffect.PREVIEW;
+import static androidx.camera.core.SurfaceProcessor.PREVIEW;
 import static androidx.core.util.Preconditions.checkArgument;
 
 import androidx.annotation.NonNull;
@@ -81,7 +81,7 @@
          * Adds a {@link CameraEffect} with its targets.
          *
          * @param targets      on which the effect will be applied. CameraX only supports
-         *                     {@link SurfaceEffect#PREVIEW} for now.
+         *                     {@link SurfaceProcessor#PREVIEW} for now.
          * @param cameraEffect the effect implementation.
          * @throws IllegalArgumentException if the configuration is illegal.
          */
@@ -91,11 +91,11 @@
                 @NonNull CameraEffect cameraEffect) {
             checkArgument(!mEffects.containsKey(targets), "The target already has an effect");
             checkArgument(targets == PREVIEW, "Only allows PREVIEW target.");
-            if (cameraEffect instanceof SurfaceEffect) {
+            if (cameraEffect instanceof SurfaceProcessor) {
                 mEffects.put(targets, cameraEffect);
             } else {
                 throw new UnsupportedOperationException(
-                        "CameraX only supports SurfaceEffect for now.");
+                        "CameraX only supports SurfaceProcessor for now.");
             }
             return this;
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index fd52b2c..062cdea 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -95,8 +95,8 @@
 import androidx.camera.core.processing.Node;
 import androidx.camera.core.processing.SettableSurface;
 import androidx.camera.core.processing.SurfaceEdge;
-import androidx.camera.core.processing.SurfaceEffectInternal;
-import androidx.camera.core.processing.SurfaceEffectNode;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
+import androidx.camera.core.processing.SurfaceProcessorNode;
 import androidx.core.util.Consumer;
 import androidx.core.util.Preconditions;
 import androidx.lifecycle.LifecycleOwner;
@@ -194,10 +194,10 @@
     private Size mSurfaceSize;
 
     @Nullable
-    private SurfaceEffectInternal mSurfaceEffect;
+    private SurfaceProcessorInternal mSurfaceProcessor;
 
     @Nullable
-    private SurfaceEffectNode mNode;
+    private SurfaceProcessorNode mNode;
 
     /**
      * Creates a new preview use case from the given configuration.
@@ -214,9 +214,9 @@
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     SessionConfig.Builder createPipeline(@NonNull String cameraId, @NonNull PreviewConfig config,
             @NonNull Size resolution) {
-        // Build pipeline with node if effect is set. Eventually we will move all the code to
+        // Build pipeline with node if processor is set. Eventually we will move all the code to
         // createPipelineWithNode.
-        if (mSurfaceEffect != null) {
+        if (mSurfaceProcessor != null) {
             return createPipelineWithNode(cameraId, config, resolution);
         }
 
@@ -303,16 +303,16 @@
             @NonNull Size resolution) {
         // Check arguments
         Threads.checkMainThread();
-        Preconditions.checkNotNull(mSurfaceEffect);
+        Preconditions.checkNotNull(mSurfaceProcessor);
         CameraInternal camera = getCamera();
         Preconditions.checkNotNull(camera);
 
         clearPipeline();
 
         // Create nodes and edges.
-        mNode = new SurfaceEffectNode(camera, USE_SURFACE_TEXTURE_TRANSFORM, mSurfaceEffect);
+        mNode = new SurfaceProcessorNode(camera, USE_SURFACE_TEXTURE_TRANSFORM, mSurfaceProcessor);
         SettableSurface cameraSurface = new SettableSurface(
-                SurfaceEffect.PREVIEW,
+                SurfaceProcessor.PREVIEW,
                 resolution,
                 ImageFormat.PRIVATE,
                 new Matrix(),
@@ -339,7 +339,7 @@
     }
 
     /**
-     * Sets a {@link SurfaceEffectInternal}.
+     * Sets a {@link SurfaceProcessorInternal}.
      *
      * <p>Internal API invoked by {@link CameraUseCaseAdapter}. {@link #createPipeline} uses the
      * value to setup post-processing pipeline.
@@ -347,20 +347,20 @@
      * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
-    public void setEffect(@Nullable SurfaceEffectInternal surfaceEffect) {
-        mSurfaceEffect = surfaceEffect;
+    public void setProcessor(@Nullable SurfaceProcessorInternal surfaceProcessor) {
+        mSurfaceProcessor = surfaceProcessor;
     }
 
     /**
-     * Gets the {@link SurfaceEffectInternal} for testing.
+     * Gets the {@link SurfaceProcessorInternal} for testing.
      *
      * @hide
      */
     @Nullable
     @VisibleForTesting
     @RestrictTo(Scope.LIBRARY_GROUP)
-    public SurfaceEffectInternal getEffect() {
-        return mSurfaceEffect;
+    public SurfaceProcessorInternal getProcessor() {
+        return mSurfaceProcessor;
     }
 
     /**
@@ -372,7 +372,7 @@
             cameraSurface.close();
             mSessionDeferrableSurface = null;
         }
-        SurfaceEffectNode node = mNode;
+        SurfaceProcessorNode node = mNode;
         if (node != null) {
             node.release();
             mNode = null;
@@ -390,7 +390,7 @@
 
         // Not to add deferrable surface if the surface provider is not set, as that means the
         // surface will never be provided. For simplicity, the same rule also applies to
-        // SurfaceEffectNode and CaptureProcessor cases, since no surface provider also means no
+        // SurfaceProcessorNode and CaptureProcessor cases, since no surface provider also means no
         // output target for these two cases.
         if (mSurfaceProvider != null) {
             sessionConfigBuilder.addSurface(mSessionDeferrableSurface);
@@ -447,7 +447,7 @@
         SurfaceRequest surfaceRequest = mCurrentSurfaceRequest;
         if (cameraInternal != null && surfaceProvider != null && cropRect != null
                 && surfaceRequest != null) {
-            // TODO: when SurfaceEffectNode exists, use SettableSurface.setRotationDegrees(int)
+            // TODO: when SurfaceProcessorNode exists, use SettableSurface.setRotationDegrees(int)
             //  instead. However, this requires PreviewView to rely on relative rotation but not
             //  target rotation.
             surfaceRequest.updateTransformationInfo(SurfaceRequest.TransformationInfo.of(cropRect,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
index 9930739..6bf27c5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
@@ -38,7 +38,7 @@
  * lifecycle of the {@link Surface}.
  *
  * @hide
- * @see SurfaceEffect#onOutputSurface(SurfaceOutput)
+ * @see SurfaceProcessor#onOutputSurface(SurfaceOutput)
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface SurfaceOutput {
@@ -63,10 +63,10 @@
      * This field indicates that what purpose the {@link Surface} will be used for.
      *
      * <ul>
-     * <li>{@link SurfaceEffect#PREVIEW} if the {@link Surface} will be used for {@link Preview}.
-     * <li>{@link SurfaceEffect#VIDEO_CAPTURE} if the {@link Surface} will be used for video
+     * <li>{@link SurfaceProcessor#PREVIEW} if the {@link Surface} will be used for {@link Preview}.
+     * <li>{@link SurfaceProcessor#VIDEO_CAPTURE} if the {@link Surface} will be used for video
      * capture.
-     * <li>{@link SurfaceEffect#PREVIEW} | {@link SurfaceEffect#VIDEO_CAPTURE} if the output
+     * <li>{@link SurfaceProcessor#PREVIEW} | {@link SurfaceProcessor#VIDEO_CAPTURE} if the output
      * {@link Surface} will be used for sharing a single stream for both preview and video capture.
      * </ul>
      */
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
similarity index 98%
rename from camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java
rename to camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
index 5367124..fd55b33 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
@@ -34,7 +34,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface SurfaceEffect extends CameraEffect {
+public interface SurfaceProcessor extends CameraEffect {
 
     /**
      * Bitmask option to indicate that CameraX applies this effect to {@link Preview}.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
index 44c76b4..d5a76f2 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
@@ -109,7 +109,7 @@
         /**
          * Sets the {@link EffectBundle} for the {@link UseCase}s.
          *
-         * <p>Once set, CameraX will use the {@link SurfaceEffect}s to process the outputs of
+         * <p>Once set, CameraX will use the {@link SurfaceProcessor}s to process the outputs of
          * the {@link UseCase}s.
          *
          * @hide
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index c8ea8f4..a19cccb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -37,7 +37,7 @@
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Logger;
 import androidx.camera.core.Preview;
-import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.impl.AttachedSurfaceInfo;
@@ -52,8 +52,8 @@
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.processing.SurfaceEffectInternal;
-import androidx.camera.core.processing.SurfaceEffectWithExecutor;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
+import androidx.camera.core.processing.SurfaceProcessorWithExecutor;
 import androidx.core.util.Preconditions;
 
 import java.util.ArrayList;
@@ -451,17 +451,18 @@
             for (Map.Entry<Integer, CameraEffect> entry : effectBundle.getEffects().entrySet()) {
                 CameraEffect effect = entry.getValue();
                 int targets = entry.getKey();
-                if (effect instanceof SurfaceEffect) {
-                    effectsWithExecutors.put(targets, new SurfaceEffectWithExecutor(
-                            (SurfaceEffect) effect, executor));
+                if (effect instanceof SurfaceProcessor) {
+                    effectsWithExecutors.put(targets, new SurfaceProcessorWithExecutor(
+                            (SurfaceProcessor) effect, executor));
                 }
             }
         }
         // Set effects on the UseCases. This also removes existing effects if necessary.
         for (UseCase useCase : useCases) {
             if (useCase instanceof Preview) {
-                ((Preview) useCase).setEffect(
-                        (SurfaceEffectInternal) effectsWithExecutors.get(SurfaceEffect.PREVIEW));
+                ((Preview) useCase).setProcessor(
+                        (SurfaceProcessorInternal) effectsWithExecutors.get(
+                                SurfaceProcessor.PREVIEW));
             }
         }
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
similarity index 92%
rename from camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java
rename to camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
index 7d127aa..8be1285 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/DefaultSurfaceProcessor.java
@@ -25,8 +25,8 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
-import androidx.camera.core.SurfaceEffect;
 import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.SurfaceRequest;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -40,13 +40,13 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * A default implementation of {@link SurfaceEffect}.
+ * A default implementation of {@link SurfaceProcessor}.
  *
  * <p> This implementation simply copies the frame from the source to the destination with the
  * transformation defined in {@link SurfaceOutput#updateTransformMatrix}.
  */
 @RequiresApi(21)
-public class DefaultSurfaceEffect implements SurfaceEffectInternal,
+public class DefaultSurfaceProcessor implements SurfaceProcessorInternal,
         SurfaceTexture.OnFrameAvailableListener {
     private final OpenGlRenderer mGlRenderer;
     @VisibleForTesting
@@ -64,18 +64,18 @@
     // Only access this on GL thread.
     private int mInputSurfaceCount = 0;
 
-    /** Constructs DefaultSurfaceEffect */
-    public DefaultSurfaceEffect() {
+    /** Constructs {@link DefaultSurfaceProcessor} with default shaders. */
+    public DefaultSurfaceProcessor() {
         this(ShaderProvider.DEFAULT);
     }
 
     /**
-     * Constructs DefaultSurfaceEffect
+     * Constructs {@link DefaultSurfaceProcessor} with custom shaders.
      *
      * @param shaderProvider custom shader provider for OpenGL rendering.
      * @throws IllegalArgumentException if the shaderProvider provides invalid shader.
      */
-    public DefaultSurfaceEffect(@NonNull ShaderProvider shaderProvider) {
+    public DefaultSurfaceProcessor(@NonNull ShaderProvider shaderProvider) {
         mGlThread = new HandlerThread("GL Thread");
         mGlThread.start();
         mGlHandler = new Handler(mGlThread.getLooper());
@@ -133,7 +133,7 @@
     }
 
     /**
-     * Release the DefaultSurfaceEffect
+     * Release the {@link DefaultSurfaceProcessor}.
      */
     @Override
     public void release() {
@@ -199,7 +199,7 @@
             if (cause instanceof RuntimeException) {
                 throw (RuntimeException) cause;
             } else {
-                throw new IllegalStateException("Failed to create DefaultSurfaceEffect", cause);
+                throw new IllegalStateException("Failed to create DefaultSurfaceProcessor", cause);
             }
         }
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
index 7fe2ce6..dad0080 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
@@ -36,9 +36,9 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.CameraEffect;
-import androidx.camera.core.SurfaceEffect;
 import androidx.camera.core.SurfaceOutput;
 import androidx.camera.core.SurfaceOutput.GlTransformOptions;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.SurfaceRequest;
 import androidx.camera.core.SurfaceRequest.TransformationInfo;
 import androidx.camera.core.UseCase;
@@ -61,7 +61,7 @@
  * an external surface consumer:
  * <pre>
  * {@code PreviewView}(surface provider) <--> {@link SurfaceRequest} <--> {@link SettableSurface}
- *     <--> {@link SurfaceOutput} --> {@link SurfaceEffect}(surface consumer)
+ *     <--> {@link SurfaceOutput} --> {@link SurfaceProcessor}(surface consumer)
  * </pre>
  *
  * <p>For the full workflow, please see {@code SettableSurfaceTest
@@ -240,7 +240,7 @@
      * Creates a {@link SurfaceOutput} that is linked to this {@link SettableSurface}.
      *
      * <p>The {@link SurfaceOutput} is for providing a surface to an external target such
-     * as {@link SurfaceEffect}.
+     * as {@link SurfaceProcessor}.
      *
      * <p>This method returns a {@link ListenableFuture} that completes when the
      * {@link SettableSurface#getSurface()} completes. The {@link SurfaceOutput} contains the
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java
index 3edf244..cc64c21 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/ShaderProvider.java
@@ -59,7 +59,7 @@
         return null;
     }
 
-    /** A default provider that will use the default shader code without any effect. */
+    /** A default provider that will use the default shader code without any post-processing. */
     ShaderProvider DEFAULT = new ShaderProvider() {
         // Use default implementation.
     };
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java
deleted file mode 100644
index ea3640d1..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectInternal.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core.processing;
-
-import androidx.camera.core.SurfaceEffect;
-
-/**
- * An internal {@link SurfaceEffect} that is releasable.
- *
- * <p>Note: the implementation of this interface must be thread-safe. e.g. methods can be
- * safely invoked on any thread.
- */
-public interface SurfaceEffectInternal extends SurfaceEffect {
-
-    /**
-     * Releases all the resources allocated by the effect.
-     *
-     * <p>An effect created by CameraX should be released by CameraX when it's no longer needed.
-     * On the other hand, an external effect should not be released by CameraX, because CameraX
-     * not does know if the effect will be needed again. In that case, the app is responsible for
-     * releasing the effect. It should be able to keep the effect alive across multiple
-     * attach/detach cycles if it's necessary.
-     *
-     * @see Node#release()
-     */
-    void release();
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java
deleted file mode 100644
index c3113f2..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectWithExecutor.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core.processing;
-
-import static androidx.core.util.Preconditions.checkState;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.camera.core.SurfaceEffect;
-import androidx.camera.core.SurfaceOutput;
-import androidx.camera.core.SurfaceRequest;
-
-import java.util.concurrent.Executor;
-
-/**
- * A wrapper of a pair of {@link SurfaceEffect} and {@link Executor}.
- *
- * <p> Wraps the external {@link SurfaceEffect} and {@link Executor} provided by the app. It
- * makes sure that CameraX always invoke the {@link SurfaceEffect} on the correct {@link Executor}.
- */
-public class SurfaceEffectWithExecutor implements SurfaceEffectInternal {
-
-    @NonNull
-    private final SurfaceEffect mSurfaceEffect;
-    @NonNull
-    private final Executor mExecutor;
-
-    public SurfaceEffectWithExecutor(
-            @NonNull SurfaceEffect surfaceEffect,
-            @NonNull Executor executor) {
-        checkState(!(surfaceEffect instanceof SurfaceEffectInternal),
-                "SurfaceEffectInternal should always be thread safe. Do not wrap.");
-        mSurfaceEffect = surfaceEffect;
-        mExecutor = executor;
-    }
-
-    @NonNull
-    @VisibleForTesting
-    public SurfaceEffect getSurfaceEffect() {
-        return mSurfaceEffect;
-    }
-
-    @NonNull
-    @VisibleForTesting
-    public Executor getExecutor() {
-        return mExecutor;
-    }
-
-    @Override
-    public void onInputSurface(@NonNull SurfaceRequest request) {
-        mExecutor.execute(() -> mSurfaceEffect.onInputSurface(request));
-    }
-
-    @Override
-    public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) {
-        mExecutor.execute(() -> mSurfaceEffect.onOutputSurface(surfaceOutput));
-    }
-
-    @Override
-    public void release() {
-        // No-op. External SurfaceEffect should not be released by CameraX.
-    }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
index e5dd98b..6a2894a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
@@ -36,8 +36,8 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceEffect;
 import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 import androidx.core.util.Consumer;
 
@@ -144,7 +144,7 @@
     }
 
     /**
-     * Asks the {@link SurfaceEffect} implementation to stopping writing to the {@link Surface}.
+     * Asks the {@link SurfaceProcessor} implementation to stopping writing to the {@link Surface}.
      */
     public void requestClose() {
         AtomicReference<Consumer<Event>> eventListenerRef = new AtomicReference<>();
@@ -168,7 +168,7 @@
                 // The executor might be invoked after the SurfaceOutputImpl is closed. This
                 // happens if the #close() is called after the synchronized block above but
                 // before the line below.
-                Logger.d(TAG, "Effect executor closed. Close request not posted.", e);
+                Logger.d(TAG, "Processor executor closed. Close request not posted.", e);
             }
         }
     }
@@ -207,7 +207,7 @@
     }
 
     /**
-     * This method can be invoked by the effect implementation on any thread.
+     * This method can be invoked by the processor implementation on any thread.
      *
      * @inheritDoc
      */
@@ -243,7 +243,7 @@
     }
 
     /**
-     * This method can be invoked by the effect implementation on any thread.
+     * This method can be invoked by the processor implementation on any thread.
      */
     @AnyThread
     @Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorInternal.java
new file mode 100644
index 0000000..47cbe62
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorInternal.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.processing;
+
+import androidx.camera.core.SurfaceProcessor;
+
+/**
+ * An internal {@link SurfaceProcessor} that is releasable.
+ *
+ * <p>Note: the implementation of this interface must be thread-safe. e.g. methods can be
+ * safely invoked on any thread.
+ */
+public interface SurfaceProcessorInternal extends SurfaceProcessor {
+
+    /**
+     * Releases all the resources allocated by the processor.
+     *
+     * <p>An processor created by CameraX should be released by CameraX when it's no longer needed.
+     * On the other hand, an external processor should not be released by CameraX, because CameraX
+     * not does know if the processor will be needed again. In that case, the app is responsible for
+     * releasing the processor. It should be able to keep the processor alive across multiple
+     * attach/detach cycles if it's necessary.
+     *
+     * @see Node#release()
+     */
+    void release();
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
similarity index 87%
rename from camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java
rename to camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
index 90ceb7a..fd1e4bcf8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEffectNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
@@ -35,9 +35,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
-import androidx.camera.core.SurfaceEffect;
 import androidx.camera.core.SurfaceOutput;
 import androidx.camera.core.SurfaceOutput.GlTransformOptions;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.SurfaceRequest;
 import androidx.camera.core.impl.CameraInternal;
 import androidx.camera.core.impl.utils.Threads;
@@ -46,11 +46,11 @@
 import androidx.core.util.Preconditions;
 
 /**
- * A {@link Node} implementation that wraps around the public {@link SurfaceEffect} interface.
+ * A {@link Node} implementation that wraps around the public {@link SurfaceProcessor} interface.
  *
  * <p>Responsibilities:
  * <ul>
- * <li>Calculating transformation and passing it to the {@link SurfaceEffect}.
+ * <li>Calculating transformation and passing it to the {@link SurfaceProcessor}.
  * <li>Tracking the state of previously calculate specification and only recreate the pipeline
  * when necessary.
  * </ul>
@@ -58,11 +58,11 @@
 @RequiresApi(api = 21)
 // TODO(b/233627260): remove once implemented.
 @SuppressWarnings("UnusedVariable")
-public class SurfaceEffectNode implements Node<SurfaceEdge, SurfaceEdge> {
+public class SurfaceProcessorNode implements Node<SurfaceEdge, SurfaceEdge> {
 
     private final GlTransformOptions mGlTransformOptions;
     @NonNull
-    final SurfaceEffectInternal mSurfaceEffect;
+    final SurfaceProcessorInternal mSurfaceProcessor;
     @NonNull
     final CameraInternal mCameraInternal;
     // Guarded by main thread.
@@ -72,18 +72,18 @@
     private SurfaceEdge mInputEdge;
 
     /**
-     * Constructs the surface effect node
+     * Constructs the {@link SurfaceProcessorNode}.
      *
      * @param cameraInternal     the associated camera instance.
      * @param glTransformOptions the OpenGL transformation options.
-     * @param surfaceEffect      the interface to wrap around.
+     * @param surfaceProcessor   the interface to wrap around.
      */
-    public SurfaceEffectNode(@NonNull CameraInternal cameraInternal,
+    public SurfaceProcessorNode(@NonNull CameraInternal cameraInternal,
             @NonNull GlTransformOptions glTransformOptions,
-            @NonNull SurfaceEffectInternal surfaceEffect) {
+            @NonNull SurfaceProcessorInternal surfaceProcessor) {
         mCameraInternal = cameraInternal;
         mGlTransformOptions = glTransformOptions;
-        mSurfaceEffect = surfaceEffect;
+        mSurfaceProcessor = surfaceProcessor;
     }
 
     /**
@@ -99,7 +99,7 @@
         mInputEdge = inputEdge;
         SettableSurface inputSurface = inputEdge.getSurfaces().get(0);
         SettableSurface outputSurface = createOutputSurface(inputSurface);
-        sendSurfacesToEffectWhenReady(inputSurface, outputSurface);
+        sendSurfacesToProcessorWhenReady(inputSurface, outputSurface);
         mOutputEdge = SurfaceEdge.create(singletonList(outputSurface));
         return mOutputEdge;
     }
@@ -156,7 +156,7 @@
         return outputSurface;
     }
 
-    private void sendSurfacesToEffectWhenReady(@NonNull SettableSurface input,
+    private void sendSurfacesToProcessorWhenReady(@NonNull SettableSurface input,
             @NonNull SettableSurface output) {
         SurfaceRequest surfaceRequest = input.createSurfaceRequest(mCameraInternal);
         Futures.addCallback(output.createSurfaceOutputFuture(mGlTransformOptions,
@@ -166,16 +166,16 @@
                     @Override
                     public void onSuccess(@Nullable SurfaceOutput surfaceOutput) {
                         Preconditions.checkNotNull(surfaceOutput);
-                        mSurfaceEffect.onOutputSurface(surfaceOutput);
-                        mSurfaceEffect.onInputSurface(surfaceRequest);
+                        mSurfaceProcessor.onOutputSurface(surfaceOutput);
+                        mSurfaceProcessor.onInputSurface(surfaceRequest);
                         setupSurfaceUpdatePipeline(input, surfaceRequest, output, surfaceOutput);
                     }
 
                     @Override
                     public void onFailure(@NonNull Throwable t) {
-                        // Do not send surfaces to effect if the downstream provider (e.g. the app)
-                        // fails to provide a Surface. Instead, notify the consumer that the
-                        // Surface will not be provided.
+                        // Do not send surfaces to the processor if the downstream provider (e.g.
+                        // the app) fails to provide a Surface. Instead, notify the consumer that
+                        // the Surface will not be provided.
                         surfaceRequest.willNotProvideSurface();
                     }
                 }, mainThreadExecutor());
@@ -201,11 +201,11 @@
      */
     @Override
     public void release() {
-        mSurfaceEffect.release();
+        mSurfaceProcessor.release();
         mainThreadExecutor().execute(() -> {
             if (mOutputEdge != null) {
                 for (SettableSurface surface : mOutputEdge.getSurfaces()) {
-                    // The output DeferrableSurface will later be terminated by the effect.
+                    // The output DeferrableSurface will later be terminated by the processor.
                     surface.close();
                 }
             }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorWithExecutor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorWithExecutor.java
new file mode 100644
index 0000000..87a8644
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorWithExecutor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.processing;
+
+import static androidx.core.util.Preconditions.checkState;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
+import androidx.camera.core.SurfaceRequest;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A wrapper of a pair of {@link SurfaceProcessor} and {@link Executor}.
+ *
+ * <p> Wraps the external {@link SurfaceProcessor} and {@link Executor} provided by the app. It
+ * makes sure that CameraX always invoke the {@link SurfaceProcessor} on the correct
+ * {@link Executor}.
+ */
+public class SurfaceProcessorWithExecutor implements SurfaceProcessorInternal {
+
+    @NonNull
+    private final SurfaceProcessor mSurfaceProcessor;
+    @NonNull
+    private final Executor mExecutor;
+
+    public SurfaceProcessorWithExecutor(
+            @NonNull SurfaceProcessor surfaceProcessor,
+            @NonNull Executor executor) {
+        checkState(!(surfaceProcessor instanceof SurfaceProcessorInternal),
+                "SurfaceProcessorInternal should always be thread safe. Do not wrap.");
+        mSurfaceProcessor = surfaceProcessor;
+        mExecutor = executor;
+    }
+
+    @NonNull
+    @VisibleForTesting
+    public SurfaceProcessor getProcessor() {
+        return mSurfaceProcessor;
+    }
+
+    @NonNull
+    @VisibleForTesting
+    public Executor getExecutor() {
+        return mExecutor;
+    }
+
+    @Override
+    public void onInputSurface(@NonNull SurfaceRequest request) {
+        mExecutor.execute(() -> mSurfaceProcessor.onInputSurface(request));
+    }
+
+    @Override
+    public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) {
+        mExecutor.execute(() -> mSurfaceProcessor.onOutputSurface(surfaceOutput));
+    }
+
+    @Override
+    public void release() {
+        // No-op. External SurfaceProcessor should not be released by CameraX.
+    }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt
index f23fbd7..621932c 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraSelectorTest.kt
@@ -23,6 +23,7 @@
 import androidx.camera.testing.fakes.FakeCameraFactory
 import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.ExecutionException
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,8 +31,6 @@
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.LinkedHashSet
-import java.util.concurrent.ExecutionException
 
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
index 616fb90..de2b346 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt
@@ -17,9 +17,9 @@
 package androidx.camera.core
 
 import android.os.Build
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -42,21 +42,24 @@
 
     @Test(expected = IllegalArgumentException::class)
     fun addMoreThanOnePreviewEffect_throwsException() {
-        val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+        val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
         EffectBundle.Builder(mainThreadExecutor())
-            .addEffect(PREVIEW, surfaceEffect)
-            .addEffect(PREVIEW, surfaceEffect)
+            .addEffect(PREVIEW, surfaceProcessor)
+            .addEffect(PREVIEW, surfaceProcessor)
     }
 
     @Test
     fun addPreviewEffect_hasPreviewEffect() {
         // Arrange.
-        val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+        val surfaceProcessor =
+            FakeSurfaceProcessor(mainThreadExecutor())
         // Act.
         val effectBundle = EffectBundle.Builder(mainThreadExecutor())
-            .addEffect(PREVIEW, surfaceEffect)
+            .addEffect(PREVIEW, surfaceProcessor)
             .build()
         // Assert.
-        assertThat(effectBundle.effects.values.first() as SurfaceEffect).isEqualTo(surfaceEffect)
+        assertThat(effectBundle.effects.values.first() as SurfaceProcessor).isEqualTo(
+            surfaceProcessor
+        )
     }
 }
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 06db2e8..5f64956 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -35,14 +35,14 @@
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.internal.CameraUseCaseAdapter
-import androidx.camera.core.processing.SurfaceEffectInternal
+import androidx.camera.core.processing.SurfaceProcessorInternal
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraXUtil
 import androidx.camera.testing.fakes.FakeAppConfig
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.fakes.FakeCameraFactory
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
@@ -217,10 +217,13 @@
     @Test
     fun bindAndUnbindPreview_surfacesPropagated() {
         // Arrange.
-        val effect = FakeSurfaceEffectInternal(mainThreadExecutor(), false)
+        val processor = FakeSurfaceProcessorInternal(
+            mainThreadExecutor(),
+            false
+        )
 
         // Act: create pipeline in Preview and provide Surface.
-        val preview = createPreviewPipelineAndAttachEffect(effect)
+        val preview = createPreviewPipelineAndAttachProcessor(processor)
         val surfaceRequest = preview.mCurrentSurfaceRequest!!
         var appSurfaceReadyToRelease = false
         surfaceRequest.provideSurface(appSurface, mainThreadExecutor()) {
@@ -229,26 +232,27 @@
         shadowOf(getMainLooper()).idle()
 
         // Assert: surfaceOutput received.
-        assertThat(effect.surfaceOutput).isNotNull()
-        assertThat(effect.isReleased).isFalse()
-        assertThat(effect.isOutputSurfaceRequestedToClose).isFalse()
-        assertThat(effect.isInputSurfaceReleased).isFalse()
+        assertThat(processor.surfaceOutput).isNotNull()
+        assertThat(processor.isReleased).isFalse()
+        assertThat(processor.isOutputSurfaceRequestedToClose).isFalse()
+        assertThat(processor.isInputSurfaceReleased).isFalse()
         assertThat(appSurfaceReadyToRelease).isFalse()
-        // effect surface is provided to camera.
-        assertThat(preview.sessionConfig.surfaces[0].surface.get()).isEqualTo(effect.inputSurface)
+        // processor surface is provided to camera.
+        assertThat(preview.sessionConfig.surfaces[0].surface.get())
+            .isEqualTo(processor.inputSurface)
 
         // Act: unbind Preview.
         preview.onDetached()
         shadowOf(getMainLooper()).idle()
 
-        // Assert: effect and effect surface is released.
-        assertThat(effect.isReleased).isTrue()
-        assertThat(effect.isOutputSurfaceRequestedToClose).isTrue()
-        assertThat(effect.isInputSurfaceReleased).isTrue()
+        // Assert: processor and processor surface is released.
+        assertThat(processor.isReleased).isTrue()
+        assertThat(processor.isOutputSurfaceRequestedToClose).isTrue()
+        assertThat(processor.isInputSurfaceReleased).isTrue()
         assertThat(appSurfaceReadyToRelease).isFalse()
 
         // Act: close SurfaceOutput
-        effect.surfaceOutput!!.close()
+        processor.surfaceOutput!!.close()
         shadowOf(getMainLooper()).idle()
         assertThat(appSurfaceReadyToRelease).isTrue()
     }
@@ -256,8 +260,10 @@
     @Test
     fun invokedErrorListener_recreatePipeline() {
         // Arrange: create pipeline and get a reference of the SessionConfig.
-        val effect = FakeSurfaceEffectInternal(mainThreadExecutor())
-        val preview = createPreviewPipelineAndAttachEffect(effect)
+        val processor = FakeSurfaceProcessorInternal(
+            mainThreadExecutor()
+        )
+        val preview = createPreviewPipelineAndAttachProcessor(processor)
         val originalSessionConfig = preview.sessionConfig
 
         // Act: invoke the error listener.
@@ -432,13 +438,13 @@
         return Pair(surfaceRequest!!, transformationInfo!!)
     }
 
-    private fun createPreviewPipelineAndAttachEffect(
-        surfaceEffect: SurfaceEffectInternal?
+    private fun createPreviewPipelineAndAttachProcessor(
+        surfaceProcessor: SurfaceProcessorInternal?
     ): Preview {
         val preview = Preview.Builder()
             .setTargetRotation(Surface.ROTATION_0)
             .build()
-        preview.effect = surfaceEffect
+        preview.processor = surfaceProcessor
         preview.setSurfaceProvider(CameraXExecutors.directExecutor()) {}
         val previewConfig = PreviewConfig(
             cameraXConfig.getUseCaseConfigFactoryProvider(null)!!.newInstance(context).getConfig(
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt
index 27af2cf..7c67cdd 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/VideoCaptureTest.kt
@@ -26,6 +26,7 @@
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraFactory
 import androidx.test.core.app.ApplicationProvider
+import java.io.File
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -39,7 +40,6 @@
 import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.io.File
 
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt
index ac6541c..01bd6ee 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifDataTest.kt
@@ -22,12 +22,12 @@
 import androidx.exifinterface.media.ExifInterface.FLAG_FLASH_FIRED
 import androidx.exifinterface.media.ExifInterface.FLAG_FLASH_NO_FLASH_FUNCTION
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.TimeUnit
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.TimeUnit
 
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index c3e5bda..bd9048e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -25,7 +25,7 @@
 import androidx.camera.core.EffectBundle
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.UseCase
 import androidx.camera.core.ViewPort
 import androidx.camera.core.impl.CameraConfig
@@ -36,10 +36,10 @@
 import androidx.camera.core.impl.OptionsBundle
 import androidx.camera.core.impl.UseCaseConfigFactory
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
-import androidx.camera.core.processing.SurfaceEffectWithExecutor
+import androidx.camera.core.processing.SurfaceProcessorWithExecutor
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.camera.testing.fakes.FakeUseCaseConfig
 import androidx.camera.testing.fakes.FakeUseCaseConfigFactory
@@ -69,7 +69,7 @@
 @org.robolectric.annotation.Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class CameraUseCaseAdapterTest {
 
-    private lateinit var surfaceEffect: FakeSurfaceEffect
+    private lateinit var surfaceProcessor: FakeSurfaceProcessor
     private lateinit var effectBundle: EffectBundle
     private lateinit var executor: ExecutorService
 
@@ -84,14 +84,14 @@
         fakeCamera = FakeCamera(CAMERA_ID)
         useCaseConfigFactory = FakeUseCaseConfigFactory()
         fakeCameraSet.add(fakeCamera)
-        surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+        surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
         executor = Executors.newSingleThreadExecutor()
-        effectBundle = EffectBundle.Builder(executor).addEffect(PREVIEW, surfaceEffect).build()
+        effectBundle = EffectBundle.Builder(executor).addEffect(PREVIEW, surfaceProcessor).build()
     }
 
     @After
     fun tearDown() {
-        surfaceEffect.cleanUp()
+        surfaceProcessor.cleanUp()
         executor.shutdown()
     }
 
@@ -619,14 +619,14 @@
         val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
         // Act: update use cases with effects bundle
         CameraUseCaseAdapter.updateEffects(effectBundle, listOf(preview))
-        // Assert: preview has effect wrapped with the right executor.
-        val previewEffect = preview.effect as SurfaceEffectWithExecutor
-        assertThat(previewEffect.surfaceEffect).isEqualTo(surfaceEffect)
-        assertThat(previewEffect.executor).isEqualTo(executor)
+        // Assert: preview has processor wrapped with the right executor.
+        val previewProcessor = preview.processor as SurfaceProcessorWithExecutor
+        assertThat(previewProcessor.processor).isEqualTo(surfaceProcessor)
+        assertThat(previewProcessor.executor).isEqualTo(executor)
         // Act: update again with null effects bundle
         CameraUseCaseAdapter.updateEffects(null, listOf(preview))
-        // Assert: preview no longer has effects.
-        assertThat(preview.effect).isNull()
+        // Assert: preview no longer has processors.
+        assertThat(preview.processor).isNull()
     }
 
     private fun createCoexistingRequiredRuleCameraConfig(): CameraConfig {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
index 1514784..a409ec67 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt
@@ -24,12 +24,12 @@
 import android.os.Looper.getMainLooper
 import android.util.Size
 import android.view.Surface
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceOutput
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.SurfaceRequest
-import androidx.camera.core.SurfaceRequest.TransformationInfo
 import androidx.camera.core.SurfaceRequest.Result.RESULT_REQUEST_CANCELLED
+import androidx.camera.core.SurfaceRequest.TransformationInfo
 import androidx.camera.core.impl.DeferrableSurface.SurfaceClosedException
 import androidx.camera.core.impl.DeferrableSurface.SurfaceUnavailableException
 import androidx.camera.core.impl.ImmediateSurface
@@ -70,7 +70,7 @@
     @Before
     fun setUp() {
         settableSurface = SettableSurface(
-            SurfaceEffect.PREVIEW, Size(640, 480), ImageFormat.PRIVATE,
+            SurfaceProcessor.PREVIEW, Size(640, 480), ImageFormat.PRIVATE,
             Matrix(), true, Rect(), 0, false
         )
         fakeSurfaceTexture = SurfaceTexture(0)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
index f056808..e6d0b85 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
@@ -22,9 +22,9 @@
 import android.os.Looper
 import android.util.Size
 import android.view.Surface
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceOutput.GlTransformOptions
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.impl.utils.TransformUtils.sizeToRect
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import com.google.common.truth.Truth.assertThat
@@ -46,7 +46,7 @@
 class SurfaceOutputImplTest {
 
     companion object {
-        private const val TARGET = SurfaceEffect.PREVIEW
+        private const val TARGET = SurfaceProcessor.PREVIEW
         private const val FORMAT = PixelFormat.RGBA_8888
         private val OUTPUT_SIZE = Size(640, 480)
         private val INPUT_SIZE = Size(640, 480)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
similarity index 83%
rename from camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
rename to camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
index dea1462..abbbda5 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
@@ -23,10 +23,10 @@
 import android.os.Looper.getMainLooper
 import android.util.Size
 import android.view.Surface
-import androidx.camera.core.SurfaceEffect.PREVIEW
 import androidx.camera.core.SurfaceOutput.GlTransformOptions
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.APPLY_CROP_ROTATE_AND_MIRRORING
 import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM
+import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.SurfaceRequest.TransformationInfo
 import androidx.camera.core.impl.utils.TransformUtils.is90or270
@@ -34,7 +34,7 @@
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.testing.fakes.FakeCamera
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -46,12 +46,12 @@
 import org.robolectric.annotation.internal.DoNotInstrument
 
 /**
- * Unit tests for [SurfaceEffectNode].
+ * Unit tests for [SurfaceProcessorNode].
  */
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class SurfaceEffectNodeTest {
+class SurfaceProcessorNodeTest {
 
     companion object {
         private const val TARGET = PREVIEW
@@ -61,10 +61,10 @@
         private val CROP_RECT = Rect(0, 0, 600, 400)
     }
 
-    private lateinit var surfaceEffectInternal: FakeSurfaceEffectInternal
+    private lateinit var surfaceProcessorInternal: FakeSurfaceProcessorInternal
     private lateinit var appSurface: Surface
     private lateinit var appSurfaceTexture: SurfaceTexture
-    private lateinit var node: SurfaceEffectNode
+    private lateinit var node: SurfaceProcessorNode
     private lateinit var inputEdge: SurfaceEdge
     private lateinit var outputSurfaceRequest: SurfaceRequest
     private var outputTransformInfo: TransformationInfo? = null
@@ -73,14 +73,14 @@
     fun setup() {
         appSurfaceTexture = SurfaceTexture(0)
         appSurface = Surface(appSurfaceTexture)
-        surfaceEffectInternal = FakeSurfaceEffectInternal(mainThreadExecutor())
+        surfaceProcessorInternal = FakeSurfaceProcessorInternal(mainThreadExecutor())
     }
 
     @After
     fun tearDown() {
         appSurfaceTexture.release()
         appSurface.release()
-        surfaceEffectInternal.release()
+        surfaceProcessorInternal.release()
         if (::node.isInitialized) {
             node.release()
         }
@@ -96,7 +96,7 @@
     @Test
     fun transformInput_useSurfaceTextureTransform_outputHasTheSameProperty() {
         // Arrange.
-        createSurfaceEffectNode()
+        createSurfaceProcessorNode()
         createInputEdge()
         val inputSurface = inputEdge.surfaces[0]
 
@@ -120,7 +120,7 @@
         val cropRect = Rect(200, 100, 600, 400)
         for (rotationDegrees in arrayOf(0, 90, 180, 270)) {
             // Arrange.
-            createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+            createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
             createInputEdge(
                 size = rectToSize(cropRect),
                 cropRect = cropRect,
@@ -153,7 +153,7 @@
     fun transformInput_applyCropRotateAndMirroring_outputHasNoMirroring() {
         for (mirroring in arrayOf(false, true)) {
             // Arrange.
-            createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+            createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
             createInputEdge(mirroring = mirroring)
 
             // Act.
@@ -173,7 +173,7 @@
     @Test
     fun transformInput_applyCropRotateAndMirroring_initialTransformInfoIsPropagated() {
         // Arrange.
-        createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+        createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
         createInputEdge(rotationDegrees = 90, cropRect = Rect(0, 0, 600, 400))
 
         // Act.
@@ -182,9 +182,9 @@
         createOutputSurfaceRequestAndProvideSurface(outputSurface)
         shadowOf(getMainLooper()).idle()
 
-        // Assert: surfaceOutput of SurfaceEffect will consume the initial rotation degrees and
+        // Assert: surfaceOutput of SurfaceProcessor will consume the initial rotation degrees and
         // output surface will receive 0 degrees.
-        assertThat(surfaceEffectInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
+        assertThat(surfaceProcessorInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
         assertThat(outputTransformInfo!!.rotationDegrees).isEqualTo(0)
         assertThat(outputTransformInfo!!.cropRect).isEqualTo(Rect(0, 0, 400, 600))
     }
@@ -192,7 +192,7 @@
     @Test
     fun setRotationToInput_applyCropRotateAndMirroring_rotationIsPropagated() {
         // Arrange.
-        createSurfaceEffectNode(APPLY_CROP_ROTATE_AND_MIRRORING)
+        createSurfaceProcessorNode(APPLY_CROP_ROTATE_AND_MIRRORING)
         createInputEdge(rotationDegrees = 90)
         val inputSurface = inputEdge.surfaces[0]
         val outputEdge = node.transform(inputEdge)
@@ -204,16 +204,16 @@
         inputSurface.rotationDegrees = 270
         shadowOf(getMainLooper()).idle()
 
-        // Assert: surfaceOutput of SurfaceEffect will consume the initial rotation degrees and
+        // Assert: surfaceOutput of SurfaceProcessor will consume the initial rotation degrees and
         // output surface will receive the remaining degrees.
-        assertThat(surfaceEffectInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
+        assertThat(surfaceProcessorInternal.surfaceOutput!!.rotationDegrees).isEqualTo(90)
         assertThat(outputTransformInfo!!.rotationDegrees).isEqualTo(180)
     }
 
     @Test
     fun provideSurfaceToOutput_surfaceIsPropagatedE2E() {
         // Arrange.
-        createSurfaceEffectNode()
+        createSurfaceProcessorNode()
         createInputEdge()
         val inputSurface = inputEdge.surfaces[0]
         val outputEdge = node.transform(inputEdge)
@@ -223,15 +223,15 @@
         outputSurface.setProvider(Futures.immediateFuture(appSurface))
         shadowOf(getMainLooper()).idle()
 
-        // Assert: effect receives app Surface. CameraX receives effect Surface.
-        assertThat(surfaceEffectInternal.outputSurface).isEqualTo(appSurface)
-        assertThat(inputSurface.surface.get()).isEqualTo(surfaceEffectInternal.inputSurface)
+        // Assert: processor receives app Surface. CameraX receives processor Surface.
+        assertThat(surfaceProcessorInternal.outputSurface).isEqualTo(appSurface)
+        assertThat(inputSurface.surface.get()).isEqualTo(surfaceProcessorInternal.inputSurface)
     }
 
     @Test
-    fun releaseNode_effectIsReleased() {
+    fun releaseNode_processorIsReleased() {
         // Arrange.
-        createSurfaceEffectNode()
+        createSurfaceProcessorNode()
         createInputEdge()
         val outputSurface = node.transform(inputEdge).surfaces[0]
         outputSurface.setProvider(Futures.immediateFuture(appSurface))
@@ -241,9 +241,9 @@
         node.release()
         shadowOf(getMainLooper()).idle()
 
-        // Assert: effect is released and has requested effect to close the SurfaceOutput
-        assertThat(surfaceEffectInternal.isReleased).isTrue()
-        assertThat(surfaceEffectInternal.isOutputSurfaceRequestedToClose).isTrue()
+        // Assert: processor is released and has requested processor to close the SurfaceOutput
+        assertThat(surfaceProcessorInternal.isReleased).isTrue()
+        assertThat(surfaceProcessorInternal.isOutputSurfaceRequestedToClose).isTrue()
     }
 
     private fun createInputEdge(
@@ -269,10 +269,14 @@
         inputEdge = SurfaceEdge.create(listOf(surface))
     }
 
-    private fun createSurfaceEffectNode(
+    private fun createSurfaceProcessorNode(
         glTransformOptions: GlTransformOptions = USE_SURFACE_TEXTURE_TRANSFORM
     ) {
-        node = SurfaceEffectNode(FakeCamera(), glTransformOptions, surfaceEffectInternal)
+        node = SurfaceProcessorNode(
+            FakeCamera(),
+            glTransformOptions,
+            surfaceProcessorInternal
+        )
     }
 
     private fun createOutputSurfaceRequestAndProvideSurface(
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
similarity index 71%
rename from camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
rename to camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
index 6a33afe..b07183a 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEffectWithExecutorTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
@@ -21,13 +21,13 @@
 import android.os.HandlerThread
 import android.os.Looper.getMainLooper
 import android.util.Size
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceOutput
+import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.testing.fakes.FakeCamera
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
 import com.google.common.truth.Truth.assertThat
 import java.lang.Thread.currentThread
 import java.util.concurrent.Executor
@@ -42,12 +42,12 @@
 import org.robolectric.annotation.internal.DoNotInstrument
 
 /**
- * Unit tests for [SurfaceEffectWithExecutor].
+ * Unit tests for [SurfaceProcessorWithExecutor].
  */
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class SurfaceEffectWithExecutorTest {
+class SurfaceProcessorWithExecutorTest {
 
     companion object {
         private val SIZE = Size(640, 480)
@@ -69,30 +69,32 @@
     }
 
     @Test(expected = IllegalStateException::class)
-    fun initWithSurfaceEffectInternal_throwsException() {
-        SurfaceEffectWithExecutor(
-            FakeSurfaceEffectInternal(mainThreadExecutor()),
+    fun initWithSurfaceProcessorInternal_throwsException() {
+        SurfaceProcessorWithExecutor(
+            FakeSurfaceProcessorInternal(mainThreadExecutor()),
             mainThreadExecutor()
         )
     }
 
     @Test
-    fun invokeEffect_invokedOnEffectExecutor() {
+    fun invokeProcessor_invokedOnProcessorExecutor() {
         // Arrange: track which thread the methods are invoked on.
         var onInputSurfaceInvokedThread: Thread? = null
         var onOutputSurfaceInvokedThread: Thread? = null
-        val effectWithExecutor = SurfaceEffectWithExecutor(object : SurfaceEffect {
-            override fun onInputSurface(request: SurfaceRequest) {
-                onInputSurfaceInvokedThread = currentThread()
-            }
+        val processorWithExecutor =
+            SurfaceProcessorWithExecutor(object :
+                SurfaceProcessor {
+                override fun onInputSurface(request: SurfaceRequest) {
+                    onInputSurfaceInvokedThread = currentThread()
+                }
 
-            override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
-                onOutputSurfaceInvokedThread = currentThread()
-            }
-        }, executor)
+                override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
+                    onOutputSurfaceInvokedThread = currentThread()
+                }
+            }, executor)
         // Act: invoke methods.
-        effectWithExecutor.onInputSurface(SurfaceRequest(SIZE, FakeCamera(), false))
-        effectWithExecutor.onOutputSurface(mock(SurfaceOutput::class.java))
+        processorWithExecutor.onInputSurface(SurfaceRequest(SIZE, FakeCamera(), false))
+        processorWithExecutor.onOutputSurface(mock(SurfaceOutput::class.java))
         shadowOf(getMainLooper()).idle()
         shadowOf(executorThread.looper).idle()
         // Assert: it's the executor thread.
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index 35e8e77..0bb2cbe 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -26,18 +26,18 @@
 import androidx.camera.core.CameraXConfig
 import androidx.camera.core.EffectBundle
 import androidx.camera.core.Preview
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.UseCaseGroup
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
-import androidx.camera.core.processing.SurfaceEffectWithExecutor
+import androidx.camera.core.processing.SurfaceProcessorWithExecutor
 import androidx.camera.testing.fakes.FakeAppConfig
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.fakes.FakeCameraFactory
 import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import androidx.camera.testing.fakes.FakeLifecycleOwner
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
 import androidx.camera.testing.fakes.FakeUseCaseConfigFactory
 import androidx.concurrent.futures.await
 import androidx.test.core.app.ApplicationProvider
@@ -79,9 +79,9 @@
     fun bindUseCaseGroupWithEffect_effectIsSetOnUseCase() {
         // Arrange.
         ProcessCameraProvider.configureInstance(FakeAppConfig.create())
-        val surfaceEffect = FakeSurfaceEffect(mainThreadExecutor())
+        val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
         val effectBundle =
-            EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceEffect).build()
+            EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceProcessor).build()
         val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
         val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview)
             .setEffectBundle(effectBundle).build()
@@ -95,8 +95,8 @@
             )
 
             // Assert.
-            val useCaseEffect = (preview.effect as SurfaceEffectWithExecutor).surfaceEffect
-            assertThat(useCaseEffect).isEqualTo(surfaceEffect)
+            val useCaseProcessor = (preview.processor as SurfaceProcessorWithExecutor).processor
+            assertThat(useCaseProcessor).isEqualTo(surfaceProcessor)
         }
     }
 
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessor.java
similarity index 90%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java
rename to camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessor.java
index 8a3d05d..b8d6e160 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffect.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessor.java
@@ -23,18 +23,18 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
-import androidx.camera.core.SurfaceEffect;
 import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.SurfaceRequest;
 import androidx.camera.core.impl.DeferrableSurface;
 
 import java.util.concurrent.Executor;
 
 /**
- * Fake {@link SurfaceEffect} used in tests.
+ * Fake {@link SurfaceProcessor} used in tests.
  */
 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class FakeSurfaceEffect implements SurfaceEffect {
+public class FakeSurfaceProcessor implements SurfaceProcessor {
 
     final SurfaceTexture mSurfaceTexture;
     final Surface mInputSurface;
@@ -52,9 +52,9 @@
     Surface mOutputSurface;
 
     /**
-     * Creates a {@link SurfaceEffect} that closes the {@link SurfaceOutput} automatically.
+     * Creates a {@link SurfaceProcessor} that closes the {@link SurfaceOutput} automatically.
      */
-    public FakeSurfaceEffect(@NonNull Executor executor) {
+    public FakeSurfaceProcessor(@NonNull Executor executor) {
         this(executor, true);
     }
 
@@ -65,7 +65,7 @@
      *                               {@link SurfaceOutput#close()} to avoid the "Completer GCed"
      *                               error in {@link DeferrableSurface}.
      */
-    FakeSurfaceEffect(@NonNull Executor executor, boolean autoCloseSurfaceOutput) {
+    FakeSurfaceProcessor(@NonNull Executor executor, boolean autoCloseSurfaceOutput) {
         mSurfaceTexture = new SurfaceTexture(0);
         mInputSurface = new Surface(mSurfaceTexture);
         mExecutor = executor;
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessorInternal.java
similarity index 74%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java
rename to camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessorInternal.java
index 933db05..d1a7e5c 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceEffectInternal.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSurfaceProcessorInternal.java
@@ -20,29 +20,31 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
-import androidx.camera.core.processing.SurfaceEffectInternal;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
 
 import java.util.concurrent.Executor;
 
 /**
- * Fake {@link SurfaceEffectInternal} used in tests.
+ * Fake {@link SurfaceProcessorInternal} used in tests.
  */
 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class FakeSurfaceEffectInternal extends FakeSurfaceEffect implements SurfaceEffectInternal {
+public class FakeSurfaceProcessorInternal extends FakeSurfaceProcessor implements
+        SurfaceProcessorInternal {
 
     private boolean mIsReleased;
 
     /**
      * {@inheritDoc}
      */
-    public FakeSurfaceEffectInternal(@NonNull Executor executor) {
+    public FakeSurfaceProcessorInternal(@NonNull Executor executor) {
         this(executor, true);
     }
 
     /**
      * {@inheritDoc}
      */
-    public FakeSurfaceEffectInternal(@NonNull Executor executor, boolean autoCloseSurfaceOutput) {
+    public FakeSurfaceProcessorInternal(@NonNull Executor executor,
+            boolean autoCloseSurfaceOutput) {
         super(executor, autoCloseSurfaceOutput);
         mIsReleased = false;
     }
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
index 9c73beb..10e77fd 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
@@ -29,9 +29,9 @@
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import java.io.File
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.io.File
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
index b0406800..5c7b847 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.rule.GrantPermissionRule
+import java.util.concurrent.Callable
 import org.junit.After
 import org.junit.Assume.assumeTrue
 import org.junit.Before
@@ -35,7 +36,6 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.timeout
 import org.mockito.Mockito.verify
-import java.util.concurrent.Callable
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt
index 45ae669..af3ed21 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/SharedByteBufferTest.kt
@@ -24,6 +24,9 @@
 import androidx.test.filters.SmallTest
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
+import java.lang.ref.PhantomReference
+import java.lang.ref.ReferenceQueue
+import java.nio.ByteBuffer
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
@@ -34,9 +37,6 @@
 import kotlinx.coroutines.withTimeout
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.lang.ref.PhantomReference
-import java.lang.ref.ReferenceQueue
-import java.nio.ByteBuffer
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 69d84b3..b23e20e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -65,7 +65,7 @@
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.SurfaceRequest;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
@@ -94,11 +94,11 @@
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.core.internal.ThreadConfig;
-import androidx.camera.core.processing.DefaultSurfaceEffect;
+import androidx.camera.core.processing.DefaultSurfaceProcessor;
 import androidx.camera.core.processing.SettableSurface;
 import androidx.camera.core.processing.SurfaceEdge;
-import androidx.camera.core.processing.SurfaceEffectInternal;
-import androidx.camera.core.processing.SurfaceEffectNode;
+import androidx.camera.core.processing.SurfaceProcessorInternal;
+import androidx.camera.core.processing.SurfaceProcessorNode;
 import androidx.camera.video.StreamInfo.StreamState;
 import androidx.camera.video.impl.VideoCaptureConfig;
 import androidx.camera.video.internal.compat.quirk.DeviceQuirks;
@@ -158,22 +158,22 @@
     private static final boolean HAS_IMAGE_CAPTURE_QUIRK =
             DeviceQuirks.get(ImageCaptureFailedWhenVideoCaptureIsBoundQuirk.class) != null;
 
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @SuppressWarnings("WeakerAccess") // Synthetic access
     DeferrableSurface mDeferrableSurface;
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @SuppressWarnings("WeakerAccess") // Synthetic access
     StreamInfo mStreamInfo = StreamInfo.STREAM_INFO_ANY_INACTIVE;
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @SuppressWarnings("WeakerAccess") // Synthetic access
     @NonNull
     SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder();
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @SuppressWarnings("WeakerAccess") // Synthetic access
     ListenableFuture<Void> mSurfaceUpdateFuture = null;
     private SurfaceRequest mSurfaceRequest;
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @SuppressWarnings("WeakerAccess") // Synthetic access
     VideoOutput.SourceState mSourceState = VideoOutput.SourceState.INACTIVE;
     @Nullable
-    private SurfaceEffectInternal mSurfaceEffect;
+    private SurfaceProcessorInternal mSurfaceProcessor;
     @Nullable
-    private SurfaceEffectNode mNode;
+    private SurfaceProcessorNode mNode;
     @Nullable
     private VideoEncoderInfo mVideoEncoderInfo;
 
@@ -217,7 +217,6 @@
      * has been attached to a camera.
      *
      * @return The rotation of the intended target.
-     *
      * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -329,9 +328,9 @@
     }
 
     /**
-     * Sets a {@link SurfaceEffectInternal}.
+     * Sets a {@link SurfaceProcessorInternal}.
      *
-     * <p>The effect is used to setup post-processing pipeline.
+     * <p>The processor is used to setup post-processing pipeline.
      *
      * <p>Note: the value will only be used when VideoCapture is bound. Calling this method after
      * VideoCapture is bound takes no effect until VideoCapture is rebound.
@@ -339,8 +338,8 @@
      * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
-    public void setEffect(@Nullable SurfaceEffectInternal surfaceEffect) {
-        mSurfaceEffect = surfaceEffect;
+    public void setProcessor(@Nullable SurfaceProcessorInternal surfaceProcessor) {
+        mSurfaceProcessor = surfaceProcessor;
     }
 
     /**
@@ -502,7 +501,7 @@
                             VideoCapabilities.from(camera.getCameraInfo()), timebase, mediaSpec,
                             resolution, targetFpsRange));
             SettableSurface cameraSurface = new SettableSurface(
-                    SurfaceEffect.VIDEO_CAPTURE,
+                    SurfaceProcessor.VIDEO_CAPTURE,
                     resolution,
                     ImageFormat.PRIVATE,
                     getSensorToBufferTransformMatrix(),
@@ -697,19 +696,19 @@
     }
 
     @Nullable
-    private SurfaceEffectNode createNodeIfNeeded() {
-        if (mSurfaceEffect != null || HAS_PREVIEW_DELAY_QUIRK || HAS_IMAGE_CAPTURE_QUIRK) {
+    private SurfaceProcessorNode createNodeIfNeeded() {
+        if (mSurfaceProcessor != null || HAS_PREVIEW_DELAY_QUIRK || HAS_IMAGE_CAPTURE_QUIRK) {
             Logger.d(TAG, "SurfaceEffect is enabled.");
-            return new SurfaceEffectNode(requireNonNull(getCamera()),
+            return new SurfaceProcessorNode(requireNonNull(getCamera()),
                     APPLY_CROP_ROTATE_AND_MIRRORING,
-                    mSurfaceEffect != null ? mSurfaceEffect : new DefaultSurfaceEffect());
+                    mSurfaceProcessor != null ? mSurfaceProcessor : new DefaultSurfaceProcessor());
         }
         return null;
     }
 
     @VisibleForTesting
     @Nullable
-    SurfaceEffectNode getNode() {
+    SurfaceProcessorNode getNode() {
         return mNode;
     }
 
@@ -1002,7 +1001,7 @@
      * by the {@link QualitySelector} in VideoOutput.
      *
      * @throws IllegalArgumentException if not able to find a resolution by the QualitySelector
-     * in VideoOutput.
+     *                                  in VideoOutput.
      */
     private void updateSupportedResolutionsByQuality(@NonNull CameraInfoInternal cameraInfo,
             @NonNull UseCaseConfig.Builder<?, ?, ?> builder) throws IllegalArgumentException {
@@ -1088,9 +1087,9 @@
      * will never return a {@code null} value. The observable could contain exact {@code null}
      * value.
      *
-     * @param observable the observable
+     * @param observable     the observable
      * @param valueIfMissing if the observable doesn't contain value.
-     * @param <T> the value type
+     * @param <T>            the value type
      * @return the snapshot value of the given {@link Observable}.
      */
     @Nullable
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt
index a7940f6..a1c7f27 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/FileOutputOptionsTest.kt
@@ -18,12 +18,12 @@
 
 import android.os.Build
 import com.google.common.truth.Truth.assertThat
+import java.io.File
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.io.File
 
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index a2d7fc1..af83bd4 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -40,7 +40,7 @@
 import androidx.camera.core.impl.utils.TransformUtils.rotateSize
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.internal.CameraUseCaseAdapter
-import androidx.camera.core.processing.SurfaceEffectInternal
+import androidx.camera.core.processing.SurfaceProcessorInternal
 import androidx.camera.testing.CamcorderProfileUtil
 import androidx.camera.testing.CamcorderProfileUtil.PROFILE_1080P
 import androidx.camera.testing.CamcorderProfileUtil.PROFILE_2160P
@@ -58,7 +58,7 @@
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.fakes.FakeCameraFactory
 import androidx.camera.testing.fakes.FakeCameraInfoInternal
-import androidx.camera.testing.fakes.FakeSurfaceEffectInternal
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
 import androidx.camera.video.StreamInfo.StreamState
 import androidx.camera.video.impl.VideoCaptureConfig
 import androidx.camera.video.internal.encoder.FakeVideoEncoderInfo
@@ -142,40 +142,48 @@
     }
 
     @Test
-    fun enableEffect_sensorRotationIs0AndSetTargetRotation_sendCorrectResolution() {
+    fun enableProcessor_sensorRotationIs0AndSetTargetRotation_sendCorrectResolution() {
         testSetRotationWillSendCorrectResolution(
             sensorRotation = 0,
-            effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+            processor = FakeSurfaceProcessorInternal(
+                CameraXExecutors.mainThreadExecutor()
+            )
         )
     }
 
     @Test
-    fun enableEffect_sensorRotationIs90AndSetTargetRotation_sendCorrectResolution() {
+    fun enableProcessor_sensorRotationIs90AndSetTargetRotation_sendCorrectResolution() {
         testSetRotationWillSendCorrectResolution(
             sensorRotation = 90,
-            effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+            processor = FakeSurfaceProcessorInternal(
+                CameraXExecutors.mainThreadExecutor()
+            )
         )
     }
 
     @Test
-    fun enableEffect_sensorRotationIs180AndSetTargetRotation_sendCorrectResolution() {
+    fun enableProcessor_sensorRotationIs180AndSetTargetRotation_sendCorrectResolution() {
         testSetRotationWillSendCorrectResolution(
             sensorRotation = 180,
-            effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+            processor = FakeSurfaceProcessorInternal(
+                CameraXExecutors.mainThreadExecutor()
+            )
         )
     }
 
     @Test
-    fun enableEffect_sensorRotationIs270AndSetTargetRotation_sendCorrectResolution() {
+    fun enableProcessor_sensorRotationIs270AndSetTargetRotation_sendCorrectResolution() {
         testSetRotationWillSendCorrectResolution(
             sensorRotation = 270,
-            effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
+            processor = FakeSurfaceProcessorInternal(
+                CameraXExecutors.mainThreadExecutor()
+            )
         )
     }
 
     private fun testSetRotationWillSendCorrectResolution(
         sensorRotation: Int = 0,
-        effect: SurfaceEffectInternal? = null
+        processor: SurfaceProcessorInternal? = null
     ) {
         setupCamera(sensorRotation = sensorRotation)
         createCameraUseCaseAdapter()
@@ -196,14 +204,14 @@
                     surfaceRequest = request
                 })
             val videoCapture = createVideoCapture(videoOutput)
-            effect?.let { videoCapture.setEffect(it) }
+            processor?.let { videoCapture.setProcessor(it) }
             videoCapture.targetRotation = targetRotation
 
             // Act.
             addAndAttachUseCases(videoCapture)
 
             // Assert.
-            val expectedResolution = if (effect != null) {
+            val expectedResolution = if (processor != null) {
                 rotateSize(RESOLUTION_720P, cameraInfo.getSensorRotationDegrees(targetRotation))
             } else {
                 RESOLUTION_720P
@@ -227,25 +235,29 @@
     }
 
     @Test
-    fun addUseCasesWithSurfaceEffect_cameraIsUptime_requestIsUptime() {
+    fun addUseCasesWithSurfaceProcessor_cameraIsUptime_requestIsUptime() {
         testTimebase(
-            effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor()),
+            processor = FakeSurfaceProcessorInternal(
+                CameraXExecutors.mainThreadExecutor()
+            ),
             cameraTimebase = Timebase.UPTIME,
             expectedTimebase = Timebase.UPTIME
         )
     }
 
     @Test
-    fun addUseCasesWithSurfaceEffect_cameraIsRealtime_requestIsRealtime() {
+    fun addUseCasesWithSurfaceProcessor_cameraIsRealtime_requestIsRealtime() {
         testTimebase(
-            effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor()),
+            processor = FakeSurfaceProcessorInternal(
+                CameraXExecutors.mainThreadExecutor()
+            ),
             cameraTimebase = Timebase.REALTIME,
             expectedTimebase = Timebase.REALTIME
         )
     }
 
     private fun testTimebase(
-        effect: SurfaceEffectInternal? = null,
+        processor: SurfaceProcessorInternal? = null,
         cameraTimebase: Timebase,
         expectedTimebase: Timebase
     ) {
@@ -260,7 +272,7 @@
         val videoCapture = VideoCapture.Builder(videoOutput)
             .setSessionOptionUnpacker { _, _ -> }
             .build()
-        effect?.let { videoCapture.setEffect(it) }
+        processor?.let { videoCapture.setProcessor(it) }
 
         // Act.
         addAndAttachUseCases(videoCapture)
@@ -551,7 +563,10 @@
         // Arrange.
         setupCamera()
         createCameraUseCaseAdapter()
-        val effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor(), false)
+        val processor = FakeSurfaceProcessorInternal(
+            CameraXExecutors.mainThreadExecutor(),
+            false
+        )
         var appSurfaceReadyToRelease = false
         val videoOutput = createVideoOutput(surfaceRequestListener = { surfaceRequest, _ ->
             surfaceRequest.provideSurface(
@@ -564,30 +579,30 @@
         val videoCapture = createVideoCapture(videoOutput)
 
         // Act: bind and provide Surface.
-        videoCapture.setEffect(effect)
+        videoCapture.setProcessor(processor)
         addAndAttachUseCases(videoCapture)
 
         // Assert: surfaceOutput received.
-        assertThat(effect.surfaceOutput).isNotNull()
-        assertThat(effect.isReleased).isFalse()
-        assertThat(effect.isOutputSurfaceRequestedToClose).isFalse()
-        assertThat(effect.isInputSurfaceReleased).isFalse()
+        assertThat(processor.surfaceOutput).isNotNull()
+        assertThat(processor.isReleased).isFalse()
+        assertThat(processor.isOutputSurfaceRequestedToClose).isFalse()
+        assertThat(processor.isInputSurfaceReleased).isFalse()
         assertThat(appSurfaceReadyToRelease).isFalse()
-        // effect surface is provided to camera.
+        // processor surface is provided to camera.
         assertThat(videoCapture.sessionConfig.surfaces[0].surface.get())
-            .isEqualTo(effect.inputSurface)
+            .isEqualTo(processor.inputSurface)
 
         // Act: unbind.
         detachAndRemoveUseCases(videoCapture)
 
-        // Assert: effect and effect surface is released.
-        assertThat(effect.isReleased).isTrue()
-        assertThat(effect.isOutputSurfaceRequestedToClose).isTrue()
-        assertThat(effect.isInputSurfaceReleased).isTrue()
+        // Assert: processor and processor surface is released.
+        assertThat(processor.isReleased).isTrue()
+        assertThat(processor.isOutputSurfaceRequestedToClose).isTrue()
+        assertThat(processor.isInputSurfaceReleased).isTrue()
         assertThat(appSurfaceReadyToRelease).isFalse()
 
         // Act: close SurfaceOutput
-        effect.surfaceOutput!!.close()
+        processor.surfaceOutput!!.close()
         shadowOf(Looper.getMainLooper()).idle()
         assertThat(appSurfaceReadyToRelease).isTrue()
     }
@@ -680,8 +695,10 @@
         val videoCapture = createVideoCapture(
             videoOutput, videoEncoderInfoFinder = { videoEncoderInfo }
         )
-        val effect = FakeSurfaceEffectInternal(CameraXExecutors.mainThreadExecutor())
-        videoCapture.setEffect(effect)
+        val processor = FakeSurfaceProcessorInternal(
+            CameraXExecutors.mainThreadExecutor()
+        )
+        videoCapture.setProcessor(processor)
         videoCapture.setViewPortCropRect(cropRect)
 
         // Act.
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
index 8fa89c7..33cc6eb 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
@@ -21,13 +21,13 @@
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_UNKNOWN
 import com.google.common.truth.Truth.assertThat
+import java.io.File
 import org.junit.Assert
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.io.File
 
 private const val INVALID_FILE_PATH = "/invalid/file/path"
 private val TEST_OUTPUT_OPTION = FileOutputOptions.Builder(File(INVALID_FILE_PATH)).build()
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
index 43a0597..8abbab7 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
@@ -28,7 +28,7 @@
 import androidx.camera.core.CameraXConfig
 import androidx.camera.core.EffectBundle
 import androidx.camera.core.ImageCapture
-import androidx.camera.core.SurfaceEffect.PREVIEW
+import androidx.camera.core.SurfaceProcessor.PREVIEW
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.CameraPipeConfigTestRule
@@ -37,7 +37,7 @@
 import androidx.camera.testing.CoreAppTestUtil
 import androidx.camera.testing.fakes.FakeActivity
 import androidx.camera.testing.fakes.FakeLifecycleOwner
-import androidx.camera.testing.fakes.FakeSurfaceEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
@@ -131,13 +131,17 @@
         instrumentation.runOnMainSync {
             controller!!.setEffectBundle(
                 EffectBundle.Builder(mainThreadExecutor())
-                    .addEffect(PREVIEW, FakeSurfaceEffect(mainThreadExecutor()))
+                    .addEffect(PREVIEW,
+                        FakeSurfaceProcessor(
+                            mainThreadExecutor()
+                        )
+                    )
                     .build()
             )
         }
 
         // Assert: preview has effect
-        assertThat(controller!!.mPreview.effect).isNotNull()
+        assertThat(controller!!.mPreview.processor).isNotNull()
 
         // Act: clear the EffectBundle
         instrumentation.runOnMainSync {
@@ -145,7 +149,7 @@
         }
 
         // Assert: preview no longer has the effect.
-        assertThat(controller!!.mPreview.effect).isNull()
+        assertThat(controller!!.mPreview.processor).isNull()
     }
 
     @Test
diff --git a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
index 1c8e0e8..5de58b5 100644
--- a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
@@ -15,7 +15,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
 
     <uses-feature android:name="android.hardware.camera" />
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index b9f5851..773eb0e 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -41,6 +41,7 @@
 import android.hardware.display.DisplayManager;
 import android.media.MediaScannerConnection;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -154,12 +155,25 @@
  */
 public class CameraXActivity extends AppCompatActivity {
     private static final String TAG = "CameraXActivity";
-    private static final String[] REQUIRED_PERMISSIONS =
-            new String[]{
+    private static final String[] REQUIRED_PERMISSIONS;
+
+    static {
+
+        //WRITE_EXTERNAL_STORAGE permission is not needed for SDK 33 or later to store media
+        if (Build.VERSION.SDK_INT >= 33) {
+            REQUIRED_PERMISSIONS = new String[]{
+                    Manifest.permission.CAMERA,
+                    Manifest.permission.RECORD_AUDIO
+            };
+        } else {
+            REQUIRED_PERMISSIONS = new String[]{
                     Manifest.permission.CAMERA,
                     Manifest.permission.RECORD_AUDIO,
                     Manifest.permission.WRITE_EXTERNAL_STORAGE
             };
+        }
+    }
+
     // Possible values for this intent key: "backward" or "forward".
     private static final String INTENT_EXTRA_CAMERA_DIRECTION = "camera_direction";
     // Possible values for this intent key: "switch_test_case", "preview_test_case" or
@@ -632,7 +646,7 @@
                     } else if (outputOptions instanceof FileOutputOptions) {
                         videoFilePath = ((FileOutputOptions) outputOptions).getFile().getPath();
                         MediaScannerConnection.scanFile(this,
-                                new String[] { videoFilePath }, null,
+                                new String[]{videoFilePath}, null,
                                 (path, uri1) -> {
                                     Log.i(TAG, "Scanned " + path + " -> uri= " + uri1);
                                     updateVideoSavedSessionData(uri1);
@@ -1395,8 +1409,8 @@
                                 for (String permission : REQUIRED_PERMISSIONS) {
                                     if (!Objects.requireNonNull(result.get(permission))) {
                                         Toast.makeText(getApplicationContext(),
-                                                "Camera permission denied.",
-                                                Toast.LENGTH_SHORT)
+                                                        "Camera permission denied.",
+                                                        Toast.LENGTH_SHORT)
                                                 .show();
                                         finish();
                                         return;
@@ -1581,10 +1595,10 @@
         cameraInfo.getZoomState().removeObservers(this);
         cameraInfo.getZoomState().observe(this,
                 state -> {
-                String str = String.format("%.2fx", state.getZoomRatio());
-                mZoomRatioLabel.setText(str);
-                mZoomSeekBar.setProgress((int) (MAX_SEEKBAR_VALUE * state.getLinearZoom()));
-            });
+                    String str = String.format("%.2fx", state.getZoomRatio());
+                    mZoomRatioLabel.setText(str);
+                    mZoomSeekBar.setProgress((int) (MAX_SEEKBAR_VALUE * state.getLinearZoom()));
+                });
     }
 
     private boolean is2XZoomSupported() {
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index 5519596..f949eb5 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -134,7 +134,7 @@
     fun enableEffect_effectIsEnabled() {
         // Arrange: launch app and verify effect is inactive.
         fragment.assertPreviewIsStreaming()
-        assertThat(fragment.mSurfaceEffect.isSurfaceRequestedAndProvided()).isFalse()
+        assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isFalse()
 
         // Act: turn on effect.
         val effectToggleId = "androidx.camera.integration.view:id/effect_toggle"
@@ -142,7 +142,7 @@
         instrumentation.waitForIdleSync()
 
         // Assert: verify that effect is active.
-        assertThat(fragment.mSurfaceEffect.isSurfaceRequestedAndProvided()).isTrue()
+        assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isTrue()
     }
 
     @Test
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
index d558b58..97a9bde 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
@@ -60,7 +60,7 @@
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.ImageProxy;
 import androidx.camera.core.Logger;
-import androidx.camera.core.SurfaceEffect;
+import androidx.camera.core.SurfaceProcessor;
 import androidx.camera.core.ZoomState;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
@@ -126,7 +126,7 @@
     private ImageAnalysis.Analyzer mWrappedAnalyzer;
 
     @VisibleForTesting
-    ToneMappingSurfaceEffect mSurfaceEffect;
+    ToneMappingSurfaceProcessor mSurfaceProcessor;
 
     private final ImageAnalysis.Analyzer mAnalyzer = image -> {
         byte[] bytes = new byte[image.getPlanes()[0].getBuffer().remaining()];
@@ -182,7 +182,7 @@
         });
 
         // Set up post-processing effects.
-        mSurfaceEffect = new ToneMappingSurfaceEffect();
+        mSurfaceProcessor = new ToneMappingSurfaceProcessor();
         mEffectToggle = view.findViewById(R.id.effect_toggle);
         mEffectToggle.setOnCheckedChangeListener((compoundButton, isChecked) -> onEffectsToggled());
         onEffectsToggled();
@@ -352,15 +352,15 @@
             mExecutorService.shutdown();
         }
         mRotationProvider.removeListener(mRotationListener);
-        mSurfaceEffect.release();
+        mSurfaceProcessor.release();
     }
 
     private void onEffectsToggled() {
         if (mEffectToggle.isChecked()) {
             mCameraController.setEffectBundle(new EffectBundle.Builder(mainThreadExecutor())
-                    .addEffect(SurfaceEffect.PREVIEW, mSurfaceEffect)
+                    .addEffect(SurfaceProcessor.PREVIEW, mSurfaceProcessor)
                     .build());
-        } else if (mSurfaceEffect != null) {
+        } else if (mSurfaceProcessor != null) {
             mCameraController.setEffectBundle(null);
         }
     }
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
similarity index 96%
rename from camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
rename to camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
index 2ed13ae..e70fb20 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
@@ -22,8 +22,8 @@
 import android.os.Looper
 import android.view.Surface
 import androidx.annotation.VisibleForTesting
-import androidx.camera.core.SurfaceEffect
 import androidx.camera.core.SurfaceOutput
+import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.utils.Threads.checkMainThread
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -31,11 +31,11 @@
 import androidx.camera.core.processing.ShaderProvider
 
 /**
- * A effect that applies tone mapping on camera output.
+ * A processor that applies tone mapping on camera output.
  *
  * <p>The thread safety is guaranteed by using the main thread.
  */
-class ToneMappingSurfaceEffect : SurfaceEffect, OnFrameAvailableListener {
+class ToneMappingSurfaceProcessor : SurfaceProcessor, OnFrameAvailableListener {
 
     companion object {
         // A fragment shader that applies a yellow hue.
diff --git a/car/app/app-automotive/lint-baseline.xml b/car/app/app-automotive/lint-baseline.xml
new file mode 100644
index 0000000..5840334
--- /dev/null
+++ b/car/app/app-automotive/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="This annotation does not apply for type com.google.common.collect.ImmutableMap&lt;java.util.Set&lt;androidx.car.app.hardware.common.CarZone>,java.util.Set&lt;java.lang.Integer>>; expected int"
+        errorLine1="    @HvacFanDirection"
+        errorLine2="    ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/car/app/hardware/common/CarPropertyProfile.java"/>
+    </issue>
+
+</issues>
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java b/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java
index da88bc2..f22d30f 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/media/AutomotiveCarAudioRecord.java
@@ -24,10 +24,12 @@
 import android.media.AudioRecord;
 import android.media.MediaRecorder;
 
+import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresPermission;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.CarContext;
+import androidx.car.app.annotations.CarProtocol;
 
 /**
  * A {@link CarAudioRecord} for automotive OS.
@@ -35,6 +37,8 @@
  * @hide
  */
 @RestrictTo(LIBRARY_GROUP)
+@Keep
+@CarProtocol
 public class AutomotiveCarAudioRecord extends CarAudioRecord {
     /**
      * Only used for Automotive, as the car microphone is the device microphone.
diff --git a/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java b/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java
index 38a98ba..b55a5c2 100644
--- a/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java
+++ b/car/app/app-projected/src/main/java/androidx/car/app/media/ProjectedCarAudioRecord.java
@@ -23,11 +23,13 @@
 
 import android.util.Log;
 
+import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresPermission;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.CarContext;
+import androidx.car.app.annotations.CarProtocol;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -38,6 +40,8 @@
  * @hide
  */
 @RestrictTo(LIBRARY_GROUP)
+@Keep
+@CarProtocol
 public class ProjectedCarAudioRecord extends CarAudioRecord {
     @Nullable
     private InputStream mInputStream;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java
index c8d4a62..a054acf 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseService.java
@@ -35,6 +35,7 @@
 public final class ShowcaseService extends CarAppService {
     public static final String SHARED_PREF_KEY = "ShowcasePrefs";
     public static final String PRE_SEED_KEY = "PreSeed";
+    public static final String LOADING_KEY = "LoadingKey";
 
     // Intent actions for notification actions in car and phone
     public static final String INTENT_ACTION_NAVIGATE =
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java
new file mode 100644
index 0000000..0d321708
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.model.Toggle;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.car.app.sample.showcase.common.ShowcaseSession;
+import androidx.car.app.sample.showcase.common.screens.settings.CarHardwareDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.settings.ContentLimitsDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.settings.LatestFeatures;
+import androidx.car.app.sample.showcase.common.screens.settings.ParkedVsDrivingDemoScreen;
+
+/** A screen demonstrating selectable lists. */
+public final class SettingsScreen extends Screen {
+
+    private boolean mLoadingToggleState;
+
+    @NonNull
+    private final ShowcaseSession mShowcaseSession;
+
+    public SettingsScreen(@NonNull CarContext carContext,
+            @NonNull ShowcaseSession showcaseSession) {
+        super(carContext);
+        mShowcaseSession = showcaseSession;
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        Toggle mLoadingToggle = new Toggle.Builder((checked) -> {
+            if (checked) {
+                makeCarToast(R.string.loading_toggle_enabled);
+                setLoadingKeyValue(true);
+            } else {
+                makeCarToast(R.string.loading_toggle_disabled);
+                setLoadingKeyValue(false);
+            }
+            mLoadingToggleState = !mLoadingToggleState;
+        }).setChecked(mLoadingToggleState).build();
+
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        listBuilder.addItem(buildRowForTemplate(new LatestFeatures(getCarContext()),
+                R.string.latest_feature_title));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.loading_demo_title, mLoadingToggle));
+
+        listBuilder.addItem(buildRowForTemplate(new ContentLimitsDemoScreen(getCarContext()),
+                R.string.content_limits_demo_title));
+
+        listBuilder.addItem(buildRowForTemplate(new ParkedVsDrivingDemoScreen(getCarContext()),
+                R.string.parking_vs_driving_demo_title));
+
+        listBuilder.addItem(buildRowForTemplate(new CarHardwareDemoScreen(getCarContext(),
+                mShowcaseSession), R.string.car_hardware_demo_title));
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.settings_action_title) + " ("
+                        + getCarContext().getString(R.string.cal_api_level_prefix,
+                        getCarContext().getCarAppApiLevel()) + ")")
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .setBrowsable(true)
+                .build();
+    }
+
+    private Row buildRowForTemplate(int title, Toggle toggle) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setToggle(toggle)
+                .build();
+    }
+
+    private void makeCarToast(int toastText) {
+        CarToast.makeText(getCarContext(), toastText,
+                LENGTH_LONG).show();
+    }
+
+    private void setLoadingKeyValue(boolean val) {
+        getCarContext()
+                .getSharedPreferences(
+                        ShowcaseService.SHARED_PREF_KEY,
+                        Context.MODE_PRIVATE)
+                .edit()
+                .putBoolean(
+                        ShowcaseService.LOADING_KEY, val)
+                .apply();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java
new file mode 100644
index 0000000..76ab41e
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.GridTemplateMenuDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.ListTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.MessageTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.PaneTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.SearchTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.SignInTemplateDemoScreen;
+
+/** A screen demonstrating different template layouts. */
+public final class TemplateLayoutsDemoScreen extends Screen {
+
+    public TemplateLayoutsDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        listBuilder.addItem(buildRowForTemplate(new ListTemplateDemoScreen(getCarContext()),
+                R.string.list_template_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new GridTemplateMenuDemoScreen(getCarContext()),
+                R.string.grid_template_menu_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new MessageTemplateDemoScreen(getCarContext()),
+                R.string.msg_template_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new PaneTemplateDemoScreen(getCarContext()),
+                R.string.pane_template_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new SearchTemplateDemoScreen(getCarContext()),
+                R.string.search_template_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new SignInTemplateDemoScreen(getCarContext()),
+                R.string.sign_in_template_demo_title));
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.template_layouts_demo_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .setBrowsable(true)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareDemoScreen.java
new file mode 100644
index 0000000..f7b6614
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareDemoScreen.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.settings;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Template;
+import androidx.car.app.navigation.model.NavigationTemplate;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseSession;
+import androidx.car.app.sample.showcase.common.renderer.CarHardwareRenderer;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+
+/** Simple demo of how access car hardware information. */
+public final class CarHardwareDemoScreen extends Screen {
+
+    // Package private for inner class reference
+    final CarHardwareRenderer mCarHardwareRenderer;
+
+    public CarHardwareDemoScreen(@NonNull CarContext carContext,
+            @NonNull ShowcaseSession showcaseSession) {
+        super(carContext);
+        mCarHardwareRenderer = new CarHardwareRenderer(carContext);
+        Lifecycle lifecycle = getLifecycle();
+        lifecycle.addObserver(new DefaultLifecycleObserver() {
+
+            @NonNull
+            final ShowcaseSession mShowcaseSession = showcaseSession;
+
+            @Override
+            public void onResume(@NonNull LifecycleOwner owner) {
+                // When this screen is visible set the SurfaceRenderer to show
+                // CarHardware information.
+                mShowcaseSession.overrideRenderer(mCarHardwareRenderer);
+            }
+
+            @Override
+            public void onPause(@NonNull LifecycleOwner owner) {
+                // When this screen is hidden set the SurfaceRenderer to show
+                // CarHardware information.
+                mShowcaseSession.overrideRenderer(null);
+            }
+        });
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ActionStrip actionStrip =
+                new ActionStrip.Builder()
+                        // Add a Button to show the CarHardware info screen
+                        .addAction(new Action.Builder()
+                                .setIcon(
+                                        new CarIcon.Builder(
+                                                IconCompat.createWithResource(
+                                                        getCarContext(),
+                                                        R.drawable.info_gm_grey_24dp))
+                                                .build())
+                                .setOnClickListener(() -> getScreenManager().push(
+                                        new CarHardwareInfoScreen(getCarContext())))
+                                .build())
+                        .addAction(
+                                new Action.Builder()
+                                        .setTitle(getCarContext()
+                                                .getString(R.string.back_caps_action_title))
+                                        .setOnClickListener(this::finish)
+                                        .build())
+                        .build();
+
+        return new NavigationTemplate.Builder().setActionStrip(actionStrip).build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareInfoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareInfoScreen.java
new file mode 100644
index 0000000..8a14d77
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/CarHardwareInfoScreen.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.settings;
+
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.hardware.CarHardwareManager;
+import androidx.car.app.hardware.common.CarValue;
+import androidx.car.app.hardware.common.OnCarDataAvailableListener;
+import androidx.car.app.hardware.info.CarInfo;
+import androidx.car.app.hardware.info.EnergyProfile;
+import androidx.car.app.hardware.info.Model;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.Pane;
+import androidx.car.app.model.PaneTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Creates a screen that show the static information (such as model and energy profile) available
+ * via CarHardware interfaces.
+ */
+public final class CarHardwareInfoScreen extends Screen {
+    private static final String TAG = "showcase";
+
+    // Package private for inner class reference
+    boolean mHasModelPermission;
+    boolean mHasEnergyProfilePermission;
+    final Executor mCarHardwareExecutor;
+
+    /**
+     * Value fetched from CarHardwareManager containing model information.
+     *
+     * <p>It is requested asynchronously and can be {@code null} until the response is
+     * received.
+     */
+    @Nullable
+    @GuardedBy("this")
+    Model mModel;
+
+    /**
+     * Value fetched from CarHardwareManager containing what type of fuel/ports the car has.
+     *
+     * <p>It is requested asynchronously and can be {@code null} until the response is
+     * received.
+     */
+    @Nullable
+    @GuardedBy("this")
+    EnergyProfile mEnergyProfile;
+
+    OnCarDataAvailableListener<Model> mModelListener = data -> {
+        synchronized (this) {
+            Log.i(TAG, "Received model information: " + data);
+            mModel = data;
+            invalidate();
+        }
+    };
+
+    OnCarDataAvailableListener<EnergyProfile> mEnergyProfileListener = data -> {
+        synchronized (this) {
+            Log.i(TAG, "Received energy profile information: " + data);
+            mEnergyProfile = data;
+            invalidate();
+        }
+    };
+
+    public CarHardwareInfoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        mCarHardwareExecutor = ContextCompat.getMainExecutor(getCarContext());
+        Lifecycle lifecycle = getLifecycle();
+        lifecycle.addObserver(new DefaultLifecycleObserver() {
+
+            @Override
+            public void onCreate(@NonNull LifecycleOwner owner) {
+                CarHardwareManager carHardwareManager =
+                        getCarContext().getCarService(CarHardwareManager.class);
+                CarInfo carInfo = carHardwareManager.getCarInfo();
+
+                // Request any single shot values.
+                synchronized (CarHardwareInfoScreen.this) {
+                    mModel = null;
+
+                    try {
+                        carInfo.fetchModel(mCarHardwareExecutor, mModelListener);
+                        mHasModelPermission = true;
+                    } catch (SecurityException e) {
+                        mHasModelPermission = false;
+                    }
+
+                    mEnergyProfile = null;
+                    try {
+                        carInfo.fetchEnergyProfile(mCarHardwareExecutor, mEnergyProfileListener);
+                        mHasEnergyProfilePermission = true;
+                    } catch (SecurityException e) {
+                        mHasEnergyProfilePermission = false;
+                    }
+                }
+            }
+
+        });
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        Pane.Builder paneBuilder = new Pane.Builder();
+        if (allInfoAvailable()) {
+            Row.Builder modelRowBuilder = new Row.Builder()
+                    .setTitle(getCarContext().getString(R.string.model_info));
+            if (!mHasModelPermission) {
+                modelRowBuilder.addText(getCarContext().getString(R.string.no_model_permission));
+            } else {
+                StringBuilder info = new StringBuilder();
+                synchronized (CarHardwareInfoScreen.this) {
+                    if (mModel.getManufacturer().getStatus() != CarValue.STATUS_SUCCESS) {
+                        info.append(getCarContext().getString(R.string.manufacturer_unavailable));
+                        info.append(", ");
+                    } else {
+                        info.append(mModel.getManufacturer().getValue());
+                        info.append(", ");
+                    }
+                    if (mModel.getName().getStatus() != CarValue.STATUS_SUCCESS) {
+                        info.append(getCarContext().getString(R.string.model_unavailable));
+                        info.append(", ");
+                    } else {
+                        info.append(mModel.getName().getValue());
+                        info.append(", ");
+                    }
+                    if (mModel.getYear().getStatus() != CarValue.STATUS_SUCCESS) {
+                        info.append(getCarContext().getString(R.string.year_unavailable));
+                    } else {
+                        info.append(mModel.getYear().getValue());
+                    }
+                }
+                modelRowBuilder.addText(info);
+            }
+            paneBuilder.addRow(modelRowBuilder.build());
+
+            Row.Builder energyProfileRowBuilder = new Row.Builder()
+                    .setTitle(getCarContext().getString(R.string.energy_profile));
+            if (!mHasEnergyProfilePermission) {
+                energyProfileRowBuilder.addText(getCarContext()
+                        .getString(R.string.no_energy_profile_permission));
+            } else {
+                StringBuilder fuelInfo = new StringBuilder();
+
+                synchronized (this) {
+                    if (mEnergyProfile.getFuelTypes().getStatus() != CarValue.STATUS_SUCCESS) {
+                        fuelInfo.append(getCarContext().getString(R.string.fuel_types));
+                        fuelInfo.append(": ");
+                        fuelInfo.append(getCarContext().getString(R.string.unavailable));
+                    } else {
+                        fuelInfo.append(getCarContext().getString(R.string.fuel_types));
+                        fuelInfo.append(": ");
+                        for (int fuelType : mEnergyProfile.getFuelTypes().getValue()) {
+                            fuelInfo.append(fuelTypeAsString(fuelType));
+                            fuelInfo.append(" ");
+                        }
+                    }
+                    energyProfileRowBuilder.addText(fuelInfo);
+                    StringBuilder evInfo = new StringBuilder();
+                    if (mEnergyProfile.getEvConnectorTypes().getStatus()
+                            != CarValue.STATUS_SUCCESS) {
+                        evInfo.append(" ");
+                        evInfo.append(getCarContext().getString(R.string.ev_connector_types));
+                        evInfo.append(": ");
+                        evInfo.append(getCarContext().getString(R.string.unavailable));
+                    } else {
+                        evInfo.append(getCarContext().getString(R.string.ev_connector_types));
+                        evInfo.append(": ");
+                        for (int connectorType : mEnergyProfile.getEvConnectorTypes().getValue()) {
+                            evInfo.append(evConnectorAsString(connectorType));
+                            evInfo.append(" ");
+                        }
+                    }
+                    energyProfileRowBuilder.addText(evInfo);
+                }
+            }
+            paneBuilder.addRow(energyProfileRowBuilder.build());
+        } else {
+            paneBuilder.setLoading(true);
+        }
+        return new PaneTemplate.Builder(paneBuilder.build())
+                .setHeaderAction(Action.BACK)
+                .setTitle(getCarContext().getString(R.string.car_hardware_info))
+                .build();
+    }
+
+    private boolean allInfoAvailable() {
+        synchronized (this) {
+            if (mHasModelPermission && mModel == null) {
+                return false;
+            }
+            if (mHasEnergyProfilePermission && mEnergyProfile == null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private String fuelTypeAsString(int fuelType) {
+        switch (fuelType) {
+            case EnergyProfile.FUEL_TYPE_UNLEADED:
+                return "UNLEADED";
+            case EnergyProfile.FUEL_TYPE_LEADED:
+                return "LEADED";
+            case EnergyProfile.FUEL_TYPE_DIESEL_1:
+                return "DIESEL_1";
+            case EnergyProfile.FUEL_TYPE_DIESEL_2:
+                return "DIESEL_2";
+            case EnergyProfile.FUEL_TYPE_BIODIESEL:
+                return "BIODIESEL";
+            case EnergyProfile.FUEL_TYPE_E85:
+                return "E85";
+            case EnergyProfile.FUEL_TYPE_LPG:
+                return "LPG";
+            case EnergyProfile.FUEL_TYPE_CNG:
+                return "CNG";
+            case EnergyProfile.FUEL_TYPE_LNG:
+                return "LNG";
+            case EnergyProfile.FUEL_TYPE_ELECTRIC:
+                return "ELECTRIC";
+            case EnergyProfile.FUEL_TYPE_HYDROGEN:
+                return "HYDROGEN";
+            case EnergyProfile.FUEL_TYPE_OTHER:
+                return "OTHER";
+            case EnergyProfile.FUEL_TYPE_UNKNOWN:
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    private String evConnectorAsString(int evConnectorType) {
+        switch (evConnectorType) {
+            case EnergyProfile.EVCONNECTOR_TYPE_J1772:
+                return "J1772";
+            case EnergyProfile.EVCONNECTOR_TYPE_MENNEKES:
+                return "MENNEKES";
+            case EnergyProfile.EVCONNECTOR_TYPE_CHADEMO:
+                return "CHADEMO";
+            case EnergyProfile.EVCONNECTOR_TYPE_COMBO_1:
+                return "COMBO_1";
+            case EnergyProfile.EVCONNECTOR_TYPE_COMBO_2:
+                return "COMBO_2";
+            case EnergyProfile.EVCONNECTOR_TYPE_TESLA_ROADSTER:
+                return "TESLA_ROADSTER";
+            case EnergyProfile.EVCONNECTOR_TYPE_TESLA_HPWC:
+                return "TESLA_HPWC";
+            case EnergyProfile.EVCONNECTOR_TYPE_TESLA_SUPERCHARGER:
+                return "TESLA_SUPERCHARGER";
+            case EnergyProfile.EVCONNECTOR_TYPE_GBT:
+                return "GBT";
+            case EnergyProfile.EVCONNECTOR_TYPE_GBT_DC:
+                return "GBT_DC";
+            case EnergyProfile.EVCONNECTOR_TYPE_SCAME:
+                return "SCAME";
+            case EnergyProfile.EVCONNECTOR_TYPE_OTHER:
+                return "OTHER";
+            case EnergyProfile.EVCONNECTOR_TYPE_UNKNOWN:
+            default:
+                return "UNKNOWN";
+        }
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java
new file mode 100644
index 0000000..8a728b4
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.settings;
+
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/**
+ * A {@link Screen} that shows examples on how to query for various content limits via the
+ * {@lnk ConstraintManager} API.
+ */
+public class ContentLimitsDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+    // Loading State parameters
+    private static final int LOADING_TIME_MILLIS = 1000;
+    private boolean mIsFinishedLoading;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private boolean mShouldLoadScreens;
+
+    public ContentLimitsDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+    }
+
+    @Override
+    @SuppressWarnings({"FutureReturnValueIgnored"})
+    public void onStart(@NonNull LifecycleOwner owner) {
+        mShouldLoadScreens =
+                getCarContext()
+                        .getSharedPreferences(ShowcaseService.SHARED_PREF_KEY, Context.MODE_PRIVATE)
+                        .getBoolean(ShowcaseService.LOADING_KEY, false);
+        if (mShouldLoadScreens) {
+            // Post a message that finishes loading the template after some time.
+            mHandler.postDelayed(
+                    () -> {
+                        mIsFinishedLoading = true;
+                        invalidate();
+                    },
+                    LOADING_TIME_MILLIS);
+        }
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+
+        if (!mIsFinishedLoading && mShouldLoadScreens) {
+            return loadingScreenTemplate(getCarContext());
+        }
+
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        listBuilder.addItem(buildRowForTemplate(R.string.list_limit,
+                ConstraintManager.CONTENT_LIMIT_TYPE_LIST));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.grid_limit,
+                ConstraintManager.CONTENT_LIMIT_TYPE_GRID));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.pane_limit,
+                ConstraintManager.CONTENT_LIMIT_TYPE_PANE));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.place_list_limit,
+                ConstraintManager.CONTENT_LIMIT_TYPE_PLACE_LIST));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.route_list_limit,
+                ConstraintManager.CONTENT_LIMIT_TYPE_ROUTE_LIST));
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.content_limits))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(int title, int contentLimitType) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .addText(Integer.toString(getCarContext()
+                        .getCarService(ConstraintManager.class)
+                        .getContentLimit(contentLimitType)))
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LatestFeatures.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LatestFeatures.java
new file mode 100644
index 0000000..26b7a6e
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LatestFeatures.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.settings;
+
+
+import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** A screen that demonstrates the message template. */
+public class LatestFeatures extends Screen implements DefaultLifecycleObserver {
+
+    private static final int LOADING_TIME_MILLIS = 1000;
+    private boolean mIsFinishedLoading;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private boolean mShouldLoadScreens;
+
+    public LatestFeatures(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+    }
+
+    @Override
+    @SuppressWarnings({"FutureReturnValueIgnored"})
+    public void onStart(@NonNull LifecycleOwner owner) {
+        mShouldLoadScreens =
+                getCarContext()
+                        .getSharedPreferences(ShowcaseService.SHARED_PREF_KEY, Context.MODE_PRIVATE)
+                        .getBoolean(ShowcaseService.LOADING_KEY, false);
+        if (mShouldLoadScreens) {
+            // Post a message that finishes loading the template after some time.
+            mHandler.postDelayed(
+                    () -> {
+                        mIsFinishedLoading = true;
+                        invalidate();
+                    },
+                    LOADING_TIME_MILLIS);
+        }
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        if (!mIsFinishedLoading && mShouldLoadScreens) {
+            return loadingScreenTemplate(getCarContext());
+        } else {
+            return new MessageTemplate.Builder(
+                    getCarContext().getString(R.string.latest_feature_details))
+                    .setTitle(getCarContext().getString(R.string.latest_feature_title))
+                    .setHeaderAction(Action.BACK)
+                    .build();
+        }
+
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LoadingScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LoadingScreen.java
new file mode 100644
index 0000000..f6fae20
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/LoadingScreen.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.settings;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+
+/** A class that provides a sample template for a loading screen */
+public abstract class LoadingScreen {
+
+    private LoadingScreen() {
+    }
+
+    /**
+    * Returns a sample template to be used for loading a screen
+    */
+    @NonNull
+    public static Template loadingScreenTemplate(@NonNull CarContext carContext) {
+        return new MessageTemplate.Builder(
+                carContext.getString(R.string.loading_screen))
+                .setLoading(true)
+                .setHeaderAction(BACK)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
new file mode 100644
index 0000000..ba198e9
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.settings;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.ParkedOnlyOnClickListener;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** A screen demonstrating selectable lists. */
+public final class ParkedVsDrivingDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+    // Adding loading state parameters
+    private static final int LOADING_TIME_MILLIS = 1000;
+    private boolean mIsFinishedLoading;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private boolean mShouldLoadScreens;
+
+    public ParkedVsDrivingDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+    }
+
+    @Override
+    @SuppressWarnings({"FutureReturnValueIgnored"})
+    public void onStart(@NonNull LifecycleOwner owner) {
+        mShouldLoadScreens =
+                getCarContext()
+                        .getSharedPreferences(ShowcaseService.SHARED_PREF_KEY, Context.MODE_PRIVATE)
+                        .getBoolean(ShowcaseService.LOADING_KEY, false);
+        if (mShouldLoadScreens) {
+            // Post a message that finishes loading the template after some time.
+            mHandler.postDelayed(
+                    () -> {
+                        mIsFinishedLoading = true;
+                        invalidate();
+                    },
+                    LOADING_TIME_MILLIS);
+        }
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+
+        if (!mIsFinishedLoading && mShouldLoadScreens) {
+            return loadingScreenTemplate(getCarContext());
+        }
+
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+
+        listBuilder.addItem(
+                new Row.Builder()
+                        .setOnClickListener(
+                                ParkedOnlyOnClickListener.create(() -> onClick(
+                                        getCarContext().getString(R.string.parked_toast_msg))))
+                        .setTitle(getCarContext().getString(R.string.parked_only_title))
+                        .addText(getCarContext().getString(R.string.parked_only_text))
+                        .build());
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.parking_vs_driving_demo_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private void onClick(String text) {
+        CarToast.makeText(getCarContext(), text, LENGTH_LONG).show();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java
new file mode 100644
index 0000000..930f1b3
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.misc.NotificationDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.gridtemplates.GridTemplateDemoScreen;
+import androidx.lifecycle.DefaultLifecycleObserver;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link ListTemplate} to display a
+ * full-screen list.
+ */
+public final class GridTemplateMenuDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+    public GridTemplateMenuDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        listBuilder.addItem(buildRowForTemplate(new GridTemplateDemoScreen(getCarContext()),
+                R.string.grid_template_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new NotificationDemoScreen(getCarContext()),
+                R.string.notification_template_demo_title));
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.grid_template_menu_demo_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .setBrowsable(true)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java
new file mode 100644
index 0000000..1888b87
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.ContentProviderIconsDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.RadioButtonListDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.TextAndIconsDemosScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates.ToggleButtonListDemoScreen;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link ListTemplate} to display a
+ * full-screen list.
+ */
+public final class ListTemplateDemoScreen extends Screen {
+
+    public ListTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder listBuilder = new ItemList.Builder();
+        listBuilder.addItem(buildRowForTemplate(new RadioButtonListDemoScreen(getCarContext()),
+                R.string.radio_button_list_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new ToggleButtonListDemoScreen(getCarContext()),
+                R.string.toggle_button_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new TextAndIconsDemosScreen(getCarContext()),
+                R.string.text_icons_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new ContentProviderIconsDemoScreen(getCarContext()),
+                R.string.content_provider_icons_demo_title));
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.list_template_demo_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .setBrowsable(true)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java
new file mode 100644
index 0000000..32aaa1a
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates.LongMessageTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates.ShortMessageTemplateDemoScreen;
+import androidx.lifecycle.DefaultLifecycleObserver;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link ListTemplate} to display a
+ * full-screen list.
+ */
+public final class MessageTemplateDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+    public MessageTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        listBuilder.addItem(buildRowForTemplate(new ShortMessageTemplateDemoScreen(getCarContext()),
+                R.string.short_msg_template_demo_title));
+        listBuilder.addItem(buildRowForTemplate(new LongMessageTemplateDemoScreen(getCarContext()),
+                R.string.long_msg_template_demo_title));
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.msg_template_demo_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .setBrowsable(true)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/PaneTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/PaneTemplateDemoScreen.java
new file mode 100644
index 0000000..baac535
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/PaneTemplateDemoScreen.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.CarToast.LENGTH_SHORT;
+import static androidx.car.app.model.Action.FLAG_PRIMARY;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Pane;
+import androidx.car.app.model.PaneTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/**
+ * Creates a screen that demonstrates usage of the full screen {@link PaneTemplate} to display a
+ * details screen.
+ */
+public final class PaneTemplateDemoScreen extends Screen implements DefaultLifecycleObserver {
+    @Nullable
+    private IconCompat mPaneImage;
+
+    @Nullable
+    private IconCompat mRowLargeIcon;
+
+    @Nullable
+    private IconCompat mCommuteIcon;
+
+    public PaneTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+    }
+
+    @Override
+    public void onCreate(@NonNull LifecycleOwner owner) {
+        Resources resources = getCarContext().getResources();
+        Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.patio);
+        mPaneImage = IconCompat.createWithBitmap(bitmap);
+        mRowLargeIcon = IconCompat.createWithResource(getCarContext(),
+                R.drawable.ic_fastfood_white_48dp);
+        mCommuteIcon = IconCompat.createWithResource(getCarContext(), R.drawable.ic_commute_24px);
+    }
+
+    private Row createRow(int index) {
+        switch (index) {
+            case 0:
+                // Row with a large image.
+                return new Row.Builder()
+                        .setTitle(getCarContext().getString(R.string.first_row_title))
+                        .addText(getCarContext().getString(R.string.first_row_text))
+                        .addText(getCarContext().getString(R.string.first_row_text))
+                        .setImage(new CarIcon.Builder(mRowLargeIcon).build())
+                        .build();
+            default:
+                return new Row.Builder()
+                        .setTitle(
+                                getCarContext().getString(R.string.other_row_title_prefix) + (index
+                                        + 1))
+                        .addText(getCarContext().getString(R.string.other_row_text))
+                        .addText(getCarContext().getString(R.string.other_row_text))
+                        .build();
+        }
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        int listLimit = getCarContext().getCarService(ConstraintManager.class).getContentLimit(
+                ConstraintManager.CONTENT_LIMIT_TYPE_PANE);
+
+        Pane.Builder paneBuilder = new Pane.Builder();
+        for (int i = 0; i < listLimit; i++) {
+            paneBuilder.addRow(createRow(i));
+        }
+
+        // Also set a large image outside of the rows.
+        paneBuilder.setImage(new CarIcon.Builder(mPaneImage).build());
+
+        Action.Builder primaryActionBuilder = new Action.Builder()
+                .setTitle(getCarContext().getString(R.string.search_action_title))
+                .setBackgroundColor(CarColor.BLUE)
+                .setOnClickListener(
+                        () -> CarToast.makeText(
+                                        getCarContext(),
+                                        getCarContext().getString(R.string.search_toast_msg),
+                                        LENGTH_SHORT)
+                                .show());
+        if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_4) {
+            primaryActionBuilder.setFlags(FLAG_PRIMARY);
+        }
+
+        paneBuilder
+                .addAction(primaryActionBuilder.build())
+                .addAction(
+                        new Action.Builder()
+                                .setTitle(getCarContext().getString(R.string.options_action_title))
+                                .setOnClickListener(
+                                        () -> CarToast.makeText(
+                                                        getCarContext(),
+                                                        getCarContext().getString(
+                                                                R.string.options_toast_msg),
+                                                        LENGTH_SHORT)
+                                                .show())
+                                .build());
+
+        return new PaneTemplate.Builder(paneBuilder.build())
+                .setHeaderAction(Action.BACK)
+                .setActionStrip(
+                        new ActionStrip.Builder()
+                                .addAction(new Action.Builder()
+                                        .setTitle(getCarContext().getString(
+                                                R.string.commute_action_title))
+                                        .setIcon(
+                                                new CarIcon.Builder(mCommuteIcon)
+                                                        .setTint(CarColor.BLUE)
+                                                        .build())
+                                        .setOnClickListener(
+                                                () -> CarToast.makeText(
+                                                                getCarContext(),
+                                                                getCarContext().getString(
+                                                                        R.string.commute_toast_msg),
+                                                                LENGTH_SHORT)
+                                                        .show())
+                                        .build())
+                                .build())
+                .setTitle(getCarContext().getString(R.string.pane_template_demo_title))
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SearchTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SearchTemplateDemoScreen.java
new file mode 100644
index 0000000..b0387e7
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SearchTemplateDemoScreen.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.SearchTemplate;
+import androidx.car.app.model.SearchTemplate.SearchCallback;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+
+/** A screen that demonstrates the search template. */
+public class SearchTemplateDemoScreen extends Screen {
+
+    public SearchTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder listBuilder = new ItemList.Builder();
+        for (int i = 1; i <= 6; ++i) {
+            listBuilder.addItem(
+                    new Row.Builder()
+                            .setTitle(getCarContext().getString(R.string.title_prefix) + " " + i)
+                            .addText(getCarContext().getString(R.string.first_line_text))
+                            .addText(getCarContext().getString(R.string.second_line_text))
+                            .build());
+        }
+
+        SearchCallback searchListener =
+                new SearchCallback() {
+                    @Override
+                    public void onSearchTextChanged(@NonNull String searchText) {
+                    }
+
+                    @Override
+                    public void onSearchSubmitted(@NonNull String searchText) {
+                        CarToast.makeText(
+                                        getCarContext(),
+                                        "Searched for " + searchText,
+                                        LENGTH_LONG)
+                                .show();
+                    }
+                };
+
+        ActionStrip actionStrip = new ActionStrip.Builder()
+                .addAction(
+                        new Action.Builder()
+                                .setTitle(getCarContext().getString(R.string.settings_action_title))
+                                .setOnClickListener(
+                                        () -> CarToast.makeText(
+                                                        getCarContext(),
+                                                        getCarContext().getString(
+                                                                R.string.settings_toast_msg),
+                                                        LENGTH_LONG)
+                                                .show())
+                                .build())
+                .build();
+
+        return new SearchTemplate.Builder(searchListener)
+                .setSearchHint(getCarContext().getString(R.string.search_hint))
+                .setHeaderAction(Action.BACK)
+                .setShowKeyboardByDefault(false)
+                .setItemList(listBuilder.build())
+                .setActionStrip(actionStrip)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInTemplateDemoScreen.java
new file mode 100644
index 0000000..e552e9c
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInTemplateDemoScreen.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+
+import android.graphics.Color;
+import android.net.Uri;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.InputCallback;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.ParkedOnlyOnClickListener;
+import androidx.car.app.model.Template;
+import androidx.car.app.model.signin.InputSignInMethod;
+import androidx.car.app.model.signin.PinSignInMethod;
+import androidx.car.app.model.signin.ProviderSignInMethod;
+import androidx.car.app.model.signin.QRCodeSignInMethod;
+import androidx.car.app.model.signin.SignInTemplate;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.common.Utils;
+import androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates.LongMessageTemplateDemoScreen;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen that demonstrates the sign-in template. */
+public class SignInTemplateDemoScreen extends Screen {
+    private static final String EMAIL_REGEXP = "^(.+)@(.+)$";
+    private static final String EXPECTED_PASSWORD = "password";
+    private static final int MIN_USERNAME_LENGTH = 5;
+    private final CharSequence mAdditionalText;
+    private final Action mProviderSignInAction;
+    private final Action mPinSignInAction;
+    private final Action mQRCodeSignInAction;
+    // package private to avoid synthetic accessor
+    State mState = State.USERNAME;
+    String mLastErrorMessage; // last displayed error message
+    String mErrorMessage;
+
+    @Nullable
+    String mUsername;
+
+    public SignInTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+
+        // Handle back pressed events manually, as we use them to navigate between templates within
+        // the same screen.
+        OnBackPressedCallback callback = new OnBackPressedCallback(true) {
+            @Override
+            public void handleOnBackPressed() {
+                mErrorMessage = "";
+                if (mState == State.USERNAME || mState == State.SIGNED_IN) {
+                    getScreenManager().pop();
+                } else {
+                    mState = State.USERNAME;
+                    invalidate();
+                }
+            }
+        };
+        carContext.getOnBackPressedDispatcher().addCallback(this, callback);
+
+        mAdditionalText = Utils.clickable(getCarContext().getString(R.string.additional_text), 18,
+                16,
+                () -> getScreenManager().push(new LongMessageTemplateDemoScreen(getCarContext())));
+
+        mProviderSignInAction = new Action.Builder()
+                .setTitle(getCarContext().getString(R.string.google_sign_in))
+                .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
+                    mState = State.PROVIDER;
+                    invalidate();
+                }))
+                .build();
+
+        mPinSignInAction = new Action.Builder()
+                .setTitle(getCarContext().getString(R.string.use_pin))
+                .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
+                    mState = State.PIN;
+                    invalidate();
+                }))
+                .build();
+
+        mQRCodeSignInAction = new Action.Builder()
+                .setTitle(getCarContext().getString(R.string.qr_code))
+                .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
+                    mState = State.QR_CODE;
+                    invalidate();
+                }))
+                .build();
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        if (getCarContext().getCarAppApiLevel() < CarAppApiLevels.LEVEL_2) {
+            return new MessageTemplate.Builder(
+                    getCarContext().getString(R.string.sign_in_template_not_supported_text))
+                    .setTitle(getCarContext().getString(
+                            R.string.sign_in_template_not_supported_title))
+                    .setHeaderAction(Action.BACK)
+                    .build();
+        }
+        switch (mState) {
+            case USERNAME:
+                return getUsernameSignInTemplate();
+            case PASSWORD:
+                return getPasswordSignInTemplate();
+            case PIN:
+                return getPinSignInTemplate();
+            case PROVIDER:
+                return getProviderSignInTemplate();
+            case QR_CODE:
+                return getQRCodeSignInTemplate();
+            case SIGNED_IN:
+                return getSignInCompletedMessageTemplate();
+        }
+        throw new IllegalStateException("Invalid state: " + mState);
+    }
+
+    private Template getUsernameSignInTemplate() {
+        InputCallback listener = new InputCallback() {
+            @Override
+            public void onInputSubmitted(@NonNull String text) {
+                if (mState == State.USERNAME) {
+                    mUsername = text;
+                    submitUsername();
+                }
+            }
+
+            @Override
+            public void onInputTextChanged(@NonNull String text) {
+                // This callback demonstrates how to use handle the text changed event.
+                // In this case, we check that the user name doesn't exceed a certain length.
+                if (mState == State.USERNAME) {
+                    mUsername = text;
+                    mErrorMessage = validateUsername();
+
+                    // Invalidate the template (and hence possibly update the error message) only
+                    // if clearing up the error string, or if the error is changing.
+                    if (!mLastErrorMessage.isEmpty()
+                            && (mErrorMessage.isEmpty()
+                            || !mLastErrorMessage.equals(mErrorMessage))) {
+                        invalidate();
+                    }
+                }
+            }
+        };
+
+        InputSignInMethod.Builder builder = new InputSignInMethod.Builder(listener)
+                .setHint(getCarContext().getString(R.string.email_hint))
+                .setKeyboardType(InputSignInMethod.KEYBOARD_EMAIL);
+        if (mErrorMessage != null) {
+            builder.setErrorMessage(mErrorMessage);
+            mLastErrorMessage = mErrorMessage;
+        }
+        if (mUsername != null) {
+            builder.setDefaultValue(mUsername);
+        }
+        InputSignInMethod signInMethod = builder.build();
+
+        return new SignInTemplate.Builder(signInMethod)
+                .addAction(mProviderSignInAction)
+                .addAction(getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_3
+                        ? mQRCodeSignInAction : mPinSignInAction)
+                .setTitle(getCarContext().getString(R.string.sign_in_title))
+                .setInstructions(getCarContext().getString(R.string.sign_in_instructions))
+                .setHeaderAction(Action.BACK)
+                .setAdditionalText(mAdditionalText)
+                .build();
+    }
+
+    /**
+     * Validates the currently entered user name and returns an error message string if invalid,
+     * or an empty string otherwise.
+     */
+    String validateUsername() {
+        if (mUsername == null || mUsername.length() < MIN_USERNAME_LENGTH) {
+            return getCarContext().getString(R.string.invalid_length_error_msg,
+                    Integer.toString(MIN_USERNAME_LENGTH));
+        } else if (!mUsername.matches(EMAIL_REGEXP)) {
+            return getCarContext().getString(R.string.invalid_email_error_msg);
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Moves to the password screen if the user name currently entered is valid, or displays
+     * an error message otherwise.
+     */
+    void submitUsername() {
+        mErrorMessage = validateUsername();
+
+        boolean isError = !mErrorMessage.isEmpty();
+        if (!isError) {
+            // If there's no error, go to the password screen.
+            mState = State.PASSWORD;
+        }
+
+        // Invalidate the template so that we either display an error, or go to the password screen.
+        invalidate();
+    }
+
+    private Template getPasswordSignInTemplate() {
+        InputCallback callback = new InputCallback() {
+            @Override
+            public void onInputSubmitted(@NonNull String text) {
+                // Mocked password validation
+                if (!EXPECTED_PASSWORD.equals(text)) {
+                    mErrorMessage = getCarContext().getString(R.string.invalid_password_error_msg);
+                } else {
+                    mErrorMessage = "";
+                    mState = State.SIGNED_IN;
+                }
+                invalidate();
+            }
+        };
+        InputSignInMethod.Builder builder = new InputSignInMethod.Builder(callback)
+                .setHint(getCarContext().getString(R.string.password_hint))
+                .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD);
+        if (mErrorMessage != null) {
+            builder.setErrorMessage(mErrorMessage);
+        }
+        InputSignInMethod signInMethod = builder.build();
+
+        return new SignInTemplate.Builder(signInMethod)
+                .addAction(mProviderSignInAction)
+                .addAction(getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_3
+                        ? mQRCodeSignInAction : mPinSignInAction)
+                .setTitle(getCarContext().getString(R.string.sign_in_title))
+                .setInstructions(
+                        getCarContext().getString(R.string.password_sign_in_instruction_prefix)
+                                + ": " + mUsername)
+                .setHeaderAction(Action.BACK)
+                .setAdditionalText(mAdditionalText)
+                .build();
+    }
+
+    private Template getPinSignInTemplate() {
+        PinSignInMethod pinSignInMethod = new PinSignInMethod("123456789ABC");
+        return new SignInTemplate.Builder(pinSignInMethod)
+                .setTitle(getCarContext().getString(R.string.sign_in_title))
+                .setInstructions(getCarContext().getString(R.string.pin_sign_in_instruction))
+                .setHeaderAction(Action.BACK)
+                .setAdditionalText(mAdditionalText)
+                .build();
+    }
+
+    private Template getQRCodeSignInTemplate() {
+        QRCodeSignInMethod qrCodeSignInMethod = new QRCodeSignInMethod(Uri.parse("https://www"
+                + ".youtube.com/watch?v=dQw4w9WgXcQ"));
+        return new SignInTemplate.Builder(qrCodeSignInMethod)
+                .setTitle(getCarContext().getString(R.string.qr_code_sign_in_title))
+                .setHeaderAction(Action.BACK)
+                .setAdditionalText(mAdditionalText)
+                .addAction(mPinSignInAction)
+                .addAction(mProviderSignInAction)
+                .build();
+    }
+
+    private Template getProviderSignInTemplate() {
+        IconCompat providerIcon = IconCompat.createWithResource(getCarContext(),
+                R.drawable.ic_googleg);
+        CarColor noTint = CarColor.createCustom(Color.TRANSPARENT, Color.TRANSPARENT);
+
+        ProviderSignInMethod providerSignInMethod = new ProviderSignInMethod(
+                new Action.Builder()
+                        .setTitle(Utils.colorize(
+                                getCarContext().getString(R.string.sign_in_with_google_title),
+                                CarColor.createCustom(Color.BLACK, Color.BLACK), 0, 19))
+                        .setBackgroundColor(CarColor.createCustom(Color.WHITE, Color.WHITE))
+                        .setIcon(new CarIcon.Builder(providerIcon)
+                                .setTint(noTint)
+                                .build())
+                        .setOnClickListener(ParkedOnlyOnClickListener.create(
+                                this::performSignInWithGoogleFlow)).build());
+
+        return new SignInTemplate.Builder(providerSignInMethod)
+                .setTitle(getCarContext().getString(R.string.sign_in_title))
+                .setInstructions(getCarContext().getString(R.string.provider_sign_in_instruction))
+                .setHeaderAction(Action.BACK)
+                .setAdditionalText(mAdditionalText)
+                .build();
+    }
+
+    private void performSignInWithGoogleFlow() {
+        // This is here for demonstration purposes, if the APK is not signed with a signature
+        // that has been registered for sign in with Google flow, the sign in will fail at runtime.
+
+//        Bundle extras = new Bundle(1);
+//        extras.putBinder(BINDER_KEY, new SignInWithGoogleActivity.OnSignInComplete() {
+//            @Override
+//            public void onSignInComplete(@Nullable GoogleSignInAccount account) {
+//                if (account == null) {
+//                    CarToast.makeText(getCarContext(), "Error signing in", LENGTH_LONG).show();
+//                    return;
+//                }
+//
+//                // Use the account
+//                CarToast.makeText(getCarContext(),
+//                        account.getGivenName() + " signed in", LENGTH_LONG).show();
+//            }
+//        });
+//        getCarContext().startActivity(
+//                new Intent()
+//                        .setClass(getCarContext(), SignInWithGoogleActivity.class)
+//                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+//                        .putExtras(extras));
+        CarToast.makeText(getCarContext(),
+                        getCarContext().getString(R.string.sign_in_with_google_toast_msg),
+                        LENGTH_LONG)
+                .show();
+    }
+
+    private MessageTemplate getSignInCompletedMessageTemplate() {
+        return new MessageTemplate.Builder(
+                getCarContext().getString(R.string.sign_in_complete_text))
+                .setTitle(getCarContext().getString(R.string.sign_in_complete_title))
+                .setHeaderAction(Action.BACK)
+                .addAction(new Action.Builder()
+                        .setTitle(getCarContext().getString(R.string.sign_out_action_title))
+                        .setOnClickListener(() -> {
+                            mState = State.USERNAME;
+                            invalidate();
+                        })
+                        .build())
+                .build();
+    }
+
+    private enum State {
+        USERNAME,
+        PASSWORD,
+        PIN,
+        PROVIDER,
+        QR_CODE,
+        SIGNED_IN,
+    }
+
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInWithGoogleActivity.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInWithGoogleActivity.java
new file mode 100644
index 0000000..96cab04
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/SignInWithGoogleActivity.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts;
+
+import android.os.Bundle;
+
+import androidx.activity.ComponentActivity;
+import androidx.annotation.Nullable;
+
+/**
+ * An activity for use by the car app library to perform actions such as requesting permissions.
+ */
+public class SignInWithGoogleActivity extends ComponentActivity {
+    static final String BINDER_KEY = "binder";
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+//        OnSignInComplete signInCompleteCallback =
+//                (OnSignInComplete) getIntent().getExtras().getBinder(BINDER_KEY);
+//
+//        ActivityResultLauncher<Intent> activityResultLauncher =
+//                registerForActivityResult(
+//                        new ActivityResultContracts.StartActivityForResult(),
+//                        result -> {
+//                            GoogleSignInAccount account =
+//                                    GoogleSignIn.getSignedInAccountFromIntent(
+//                                            result.getData()).getResult();
+//                            signInCompleteCallback.onSignInComplete(account);
+//                            finish();
+//                        });
+//
+//        GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
+//        if (account != null) {
+//            signInCompleteCallback.onSignInComplete(account);
+//            finish();
+//        }
+//
+//        GoogleSignInOptions gso =
+//                new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+//                        .requestEmail()
+//                        .requestProfile()
+//                        .build();
+//        GoogleSignInClient signInClient = GoogleSignIn.getClient(this, gso);
+//        activityResultLauncher.launch(signInClient.getSignInIntent());
+    }
+
+
+//    /**
+//     * Binder callback to provide to the sign in activity.
+//     */
+//    abstract static class OnSignInComplete extends Binder implements IBinder {
+//        /**
+//         * Notifies that sign in flow completed.
+//         *
+//         * @param account the account signed in or {@code null} if there were issues signing in.
+//         */
+//        public abstract void onSignInComplete(@Nullable GoogleSignInAccount account);
+//    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java
new file mode 100644
index 0000000..1dfacb0
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts.gridtemplates;
+
+import static androidx.car.app.CarToast.LENGTH_SHORT;
+import static androidx.car.app.model.Action.BACK;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.GridItem;
+import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.OnClickListener;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** Creates a screen that demonstrates usage of the full screen {@link GridTemplate}. */
+public final class GridTemplateDemoScreen extends Screen implements DefaultLifecycleObserver {
+    private static final int MAX_GRID_ITEMS = 100;
+    private static final int LOADING_TIME_MILLIS = 2000;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    @Nullable
+    private IconCompat mImage;
+    @Nullable
+    private IconCompat mIcon;
+    private boolean mIsFourthItemLoading;
+    private boolean mThirdItemToggleState;
+    private boolean mFourthItemToggleState;
+    private boolean mFifthItemToggleState;
+
+    public GridTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+        mIsFourthItemLoading = false;
+        mThirdItemToggleState = false;
+        mFourthItemToggleState = true;
+        mFifthItemToggleState = false;
+    }
+
+    @Override
+    public void onCreate(@NonNull LifecycleOwner owner) {
+        Resources resources = getCarContext().getResources();
+        Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.test_image_square);
+        mImage = IconCompat.createWithBitmap(bitmap);
+        mIcon = IconCompat.createWithResource(getCarContext(), R.drawable.ic_fastfood_white_48dp);
+    }
+
+    @Override
+    @SuppressWarnings({"FutureReturnValueIgnored"})
+    public void onStart(@NonNull LifecycleOwner owner) {
+        mIsFourthItemLoading = false;
+
+        // Post a message that starts loading the fourth item for some time.
+        triggerFourthItemLoading();
+    }
+
+    private GridItem createGridItem(int index) {
+        switch (index) {
+            case 0:
+                // Grid item with an icon and a title.
+                return createGridItem(createCarIconFromImage(mIcon), GridItem.IMAGE_TYPE_ICON,
+                        getTextStringFromId(R.string.non_actionable));
+            case 1:
+                // Grid item with an icon, a title, onClickListener and no text.
+                return createGridItem(createCarIconFromImage(mIcon), GridItem.IMAGE_TYPE_ICON,
+                        getTextStringFromId(R.string.second_item), createOnClickListener(
+                                (String) getTextStringFromId(R.string.second_item_toast_msg),
+                                LENGTH_SHORT));
+
+            case 2:
+                // Grid item with an icon marked as icon, a title, a text and a toggle in
+                // unchecked state.
+                return createGridItem(createCarIconFromImage(mIcon),
+                        GridItem.IMAGE_TYPE_LARGE, getTextStringFromId(R.string.third_item),
+                        mThirdItemToggleState
+                                ? getTextStringFromId(R.string.checked_action_title) :
+                                getTextStringFromId(R.string.unchecked_action_title),
+                        createOnClickListenerForThirdItem());
+
+            case 3:
+                // Grid item with an image, a title, a long text and a toggle that takes some
+                // time to
+                // update.
+                if (mIsFourthItemLoading) {
+                    return createGridItem(getTextStringFromId(R.string.fourth_item),
+                            mFourthItemToggleState
+                                    ? getTextStringFromId(R.string.on_action_title) :
+                                    getTextStringFromId(R.string.off_action_title),
+                            true);
+                } else {
+                    return createGridItem(createCarIconFromImage(mImage),
+                            getTextStringFromId(R.string.fourth_item),
+                            mFourthItemToggleState
+                                    ? getTextStringFromId(R.string.on_action_title) :
+                                    getTextStringFromId(R.string.off_action_title),
+                            this::triggerFourthItemLoading);
+                }
+            case 4:
+                // Grid item with a large image, a long title, no text and a toggle in unchecked
+                // state.
+                return createGridItem(createCarIconFromImage(mIcon),
+                        GridItem.IMAGE_TYPE_LARGE, getTextStringFromId(R.string.fifth_item),
+                        createOnClickListenerForFifthItem());
+            case 5:
+                // Grid item with an image marked as an icon, a long title, a long text and
+                // onClickListener.
+                return createGridItem(createCarIconFromImage(mIcon),
+                        GridItem.IMAGE_TYPE_ICON, getTextStringFromId(R.string.sixth_item),
+                        getTextStringFromId(R.string.sixth_item),
+                        createOnClickListener(
+                                (String) getTextStringFromId(R.string.sixth_item_toast_msg),
+                                LENGTH_SHORT));
+
+            default:
+                String titleText = (index + 1) + "th item";
+                String toastText = "Clicked " + (index + 1) + "th item";
+
+                return createGridItem(createCarIconFromImage(mIcon),
+                        GridItem.IMAGE_TYPE_ICON, titleText, createOnClickListener(toastText,
+                                LENGTH_SHORT));
+        }
+    }
+
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        int itemLimit = 6;
+        // Adjust the item limit according to the car constrains.
+        if (getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) {
+            itemLimit =
+                    Math.min(MAX_GRID_ITEMS,
+                            getCarContext().getCarService(ConstraintManager.class).getContentLimit(
+                                    ConstraintManager.CONTENT_LIMIT_TYPE_GRID));
+        }
+
+        ItemList.Builder gridItemListBuilder = new ItemList.Builder();
+        for (int i = 0; i <= itemLimit; i++) {
+            gridItemListBuilder.addItem(createGridItem(i));
+        }
+
+        Action settings = new Action.Builder()
+                .setTitle(getCarContext().getString(
+                        R.string.settings_action_title))
+                .setOnClickListener(
+                        () -> CarToast.makeText(
+                                        getCarContext(),
+                                        getCarContext().getString(R.string.settings_toast_msg),
+                                        LENGTH_SHORT)
+                                .show())
+                .build();
+        return new GridTemplate.Builder()
+                .setHeaderAction(Action.APP_ICON)
+                .setSingleList(gridItemListBuilder.build())
+                .setTitle(getCarContext().getString(R.string.grid_template_demo_title))
+                .setActionStrip(
+                        new ActionStrip.Builder()
+                                .addAction(settings)
+                                .build())
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    /**
+     * Changes the fourth item to a loading state for some time and changes it back to the loaded
+     * state.
+     */
+    private void triggerFourthItemLoading() {
+        mHandler.post(
+                () -> {
+                    mIsFourthItemLoading = true;
+                    invalidate();
+
+                    mHandler.postDelayed(
+                            () -> {
+                                mIsFourthItemLoading = false;
+                                mFourthItemToggleState = !mFourthItemToggleState;
+                                invalidate();
+                            },
+                            LOADING_TIME_MILLIS);
+                });
+    }
+
+    /**
+     * Custom listener for third grid item
+     */
+    private OnClickListener createOnClickListenerForThirdItem() {
+        return () -> {
+            mThirdItemToggleState = !mThirdItemToggleState;
+            CarToast.makeText(
+                            getCarContext(),
+                            getCarContext().getString(
+                                    R.string.third_item_checked_toast_msg)
+                                    + ": " + mThirdItemToggleState,
+                            LENGTH_SHORT)
+                    .show();
+            invalidate();
+        };
+    }
+
+    /**
+     * Custom listener for fifth grid item
+     */
+    private OnClickListener createOnClickListenerForFifthItem() {
+        return () -> {
+            mFifthItemToggleState = !mFifthItemToggleState;
+            CarToast.makeText(
+                            getCarContext(),
+                            getCarContext().getString(
+                                    R.string.fifth_item_checked_toast_msg)
+                                    + ": "
+                                    + mFifthItemToggleState,
+                            LENGTH_SHORT)
+                    .show();
+            invalidate();
+        };
+    }
+
+    /**
+     * Returns CharSequence text for given id
+     */
+    private CharSequence getTextStringFromId(int id) {
+        return getCarContext().getString(id);
+    }
+
+    /**
+     * Create an onClickListener using a Car Toast
+     */
+    private OnClickListener createOnClickListener(String toastText, int toastLength) {
+        return () ->
+                CarToast.makeText(
+                                getCarContext(),
+                                toastText,
+                                toastLength)
+                        .show();
+    }
+
+    /**
+     * Creates CarIcon from given IconCompat image
+     */
+    private CarIcon createCarIconFromImage(IconCompat image) {
+        return new CarIcon.Builder(image).build();
+    }
+
+    /**
+     * A number of overloaded constructors with different parameters to build grid items
+     */
+    private GridItem createGridItem(CharSequence title, CharSequence text,
+            boolean isLoading) {
+        return new GridItem.Builder()
+                .setTitle(title)
+                .setText(text)
+                .setLoading(isLoading)
+                .build();
+    }
+
+    private GridItem createGridItem(CarIcon carIcon,
+            int imageType, CharSequence title, CharSequence text,
+            OnClickListener onClickListener) {
+        return new GridItem.Builder()
+                .setImage(carIcon, imageType)
+                .setTitle(title)
+                .setText(text)
+                .setOnClickListener(onClickListener)
+                .build();
+    }
+
+    private GridItem createGridItem(CarIcon carIcon,
+            CharSequence title, CharSequence text,
+            OnClickListener onClickListener) {
+        return new GridItem.Builder()
+                .setImage(carIcon)
+                .setTitle(title)
+                .setText(text)
+                .setOnClickListener(onClickListener)
+                .build();
+    }
+
+    private GridItem createGridItem(CarIcon carIcon,
+            int imageType, CharSequence title,
+            OnClickListener onClickListener) {
+        return new GridItem.Builder()
+                .setImage(carIcon, imageType)
+                .setTitle(title)
+                .setOnClickListener(onClickListener)
+                .build();
+    }
+
+    private GridItem createGridItem(CarIcon carIcon,
+            int imageType, CharSequence title) {
+        return new GridItem.Builder()
+                .setImage(carIcon, imageType)
+                .setTitle(title)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java
new file mode 100644
index 0000000..3753600
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts.gridtemplates;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.annotation.SuppressLint;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.GridItem;
+import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.Template;
+import androidx.car.app.notification.CarAppExtender;
+import androidx.car.app.notification.CarNotificationManager;
+import androidx.car.app.notification.CarPendingIntent;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.ShowcaseService;
+import androidx.core.app.NotificationChannelCompat;
+import androidx.core.app.NotificationCompat;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+/** A simple screen that demonstrates how to use notifications in a car app. */
+public final class NotificationDemoScreen extends Screen implements DefaultLifecycleObserver {
+
+    static final long NOTIFICATION_DELAY_IN_MILLIS = SECONDS.toMillis(1);
+    private static final String NOTIFICATION_CHANNEL_ID = "channel_00";
+    private static final CharSequence NOTIFICATION_CHANNEL_NAME = "Default Channel";
+    private static final int NOTIFICATION_ID = 1001;
+    private static final String NOTIFICATION_CHANNEL_HIGH_ID = "channel_01";
+    private static final CharSequence NOTIFICATION_CHANNEL_HIGH_NAME = "High Channel";
+    private static final String NOTIFICATION_CHANNEL_LOW_ID = "channel_02";
+    private static final CharSequence NOTIFICATION_CHANNEL_LOW_NAME = "Low Channel";
+    private static final String INTENT_ACTION_PRIMARY_PHONE =
+            "androidx.car.app.sample.showcase.common.INTENT_ACTION_PRIMARY_PHONE";
+    private static final String INTENT_ACTION_SECONDARY_PHONE =
+            "androidx.car.app.sample.showcase.common.INTENT_ACTION_SECONDARY_PHONE";
+    private static final int MSG_SEND_NOTIFICATION = 1;
+    final Handler mHandler = new Handler(Looper.getMainLooper(), new HandlerCallback());
+
+    private final IconCompat mIcon = IconCompat.createWithResource(getCarContext(),
+            R.drawable.ic_face_24px);
+    /** A broadcast receiver that can show a toast message upon receiving a broadcast. */
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            CarToast.makeText(
+                            getCarContext(),
+                            getCarContext().getString(R.string.triggered_toast_msg) + ": "
+                                    + intent.getAction(),
+                            CarToast.LENGTH_SHORT)
+                    .show();
+        }
+    };
+    private int mImportance = NotificationManager.IMPORTANCE_DEFAULT;
+    private boolean mIsNavCategory;
+    private boolean mSetOngoing;
+    private int mNotificationCount;
+
+    public NotificationDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+        getLifecycle().addObserver(this);
+    }
+
+    @Override
+    public void onCreate(@NonNull LifecycleOwner owner) {
+        registerBroadcastReceiver();
+    }
+
+    @Override
+    public void onDestroy(@NonNull LifecycleOwner owner) {
+        mHandler.removeMessages(MSG_SEND_NOTIFICATION);
+        CarNotificationManager.from(getCarContext()).cancelAll();
+        unregisterBroadcastReceiver();
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        // Send a single notification with the settings configured by other buttons.
+        listBuilder.addItem(
+                new GridItem.Builder()
+                        .setTitle(getCarContext().getString(R.string.send_notification_title))
+                        .setImage(new CarIcon.Builder(mIcon).build())
+                        .setOnClickListener(this::sendNotification)
+                        .build());
+
+        // Start a repeating notification with the settings configured by other buttons.
+        listBuilder.addItem(
+                new GridItem.Builder()
+                        .setTitle(getCarContext().getString(R.string.start_notifications_title))
+                        .setImage(new CarIcon.Builder(mIcon).build())
+                        .setOnClickListener(() -> mHandler.sendMessage(
+                                mHandler.obtainMessage(MSG_SEND_NOTIFICATION)))
+                        .build());
+
+        // Stop the repeating notification and reset the count.
+        listBuilder.addItem(
+                new GridItem.Builder()
+                        .setTitle(getCarContext().getString(R.string.stop_notifications_title))
+                        .setImage(new CarIcon.Builder(mIcon).build())
+                        .setOnClickListener(() -> {
+                            mHandler.removeMessages(MSG_SEND_NOTIFICATION);
+                            CarNotificationManager.from(getCarContext()).cancelAll();
+                            mNotificationCount = 0;
+                        })
+                        .build());
+
+        // Configure the notification importance.
+        listBuilder.addItem(
+                new GridItem.Builder()
+                        .setImage(new CarIcon.Builder(mIcon).build())
+                        .setTitle(getCarContext().getString(R.string.importance_title))
+                        .setText(getImportanceString())
+                        .setOnClickListener(() -> {
+                            setImportance();
+                            invalidate();
+                        })
+                        .build());
+
+        // Configure whether the notification's category is navigation.
+        listBuilder.addItem(
+                new GridItem.Builder()
+                        .setImage(new CarIcon.Builder(mIcon).build())
+                        .setTitle(getCarContext().getString(R.string.category_title))
+                        .setText(getCategoryString())
+                        .setOnClickListener(() -> {
+                            mIsNavCategory = !mIsNavCategory;
+                            invalidate();
+                        })
+                        .build());
+
+        // Configure whether the notification is an ongoing notification.
+        listBuilder.addItem(
+                new GridItem.Builder()
+                        .setImage(new CarIcon.Builder(mIcon).build())
+                        .setTitle(getCarContext().getString(R.string.ongoing_title))
+                        .setText(String.valueOf(mSetOngoing))
+                        .setOnClickListener(() -> {
+                            mSetOngoing = !mSetOngoing;
+                            invalidate();
+                        })
+                        .build());
+
+        return new GridTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.notification_demo))
+                .setHeaderAction(Action.BACK)
+                .build();
+    }
+
+    void sendNotification() {
+        mNotificationCount++;
+        String title =
+                getCarContext().getString(R.string.notification_title) + ": "
+                        + getImportanceString() + ", " + mNotificationCount;
+        String text = getCarContext().getString(R.string.category_title) + ": "
+                + getCategoryString() + ", "
+                + getCarContext().getString(R.string.ongoing_title) + ": " + mSetOngoing;
+
+        switch (mImportance) {
+            case NotificationManager.IMPORTANCE_HIGH:
+                sendNotification(title, text, NOTIFICATION_CHANNEL_HIGH_ID,
+                        NOTIFICATION_CHANNEL_HIGH_NAME, NOTIFICATION_ID,
+                        NotificationManager.IMPORTANCE_HIGH);
+                break;
+            case NotificationManager.IMPORTANCE_DEFAULT:
+                sendNotification(title, text, NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME,
+                        NOTIFICATION_ID, NotificationManager.IMPORTANCE_DEFAULT);
+                break;
+            case NotificationManager.IMPORTANCE_LOW:
+                sendNotification(title, text, NOTIFICATION_CHANNEL_LOW_ID,
+                        NOTIFICATION_CHANNEL_LOW_NAME, NOTIFICATION_ID,
+                        NotificationManager.IMPORTANCE_LOW);
+                break;
+            default:
+                break;
+        }
+    }
+
+    // Suppressing 'ObsoleteSdkInt' as this code is shared between APKs with different min SDK
+    // levels
+    @SuppressLint("ObsoleteSdkInt")
+    private void sendNotification(CharSequence title, CharSequence text, String channelId,
+            CharSequence channelName, int notificationId, int importance) {
+        CarNotificationManager carNotificationManager =
+                CarNotificationManager.from(getCarContext());
+
+        NotificationChannelCompat channel = new NotificationChannelCompat.Builder(channelId,
+                importance).setName(channelName).build();
+        carNotificationManager.createNotificationChannel(channel);
+
+        NotificationCompat.Builder builder;
+        builder = new NotificationCompat.Builder(getCarContext(), channelId);
+        if (mIsNavCategory) {
+            builder.setCategory(NotificationCompat.CATEGORY_NAVIGATION);
+        }
+        builder.setOngoing(mSetOngoing);
+
+        builder.setSmallIcon(R.drawable.ic_bug_report_24px)
+                .setContentTitle(title + " (phone)")
+                .setContentText(text + " (phone)")
+                .setColor(getCarContext().getColor(androidx.car.app.R.color.carColorGreen))
+                .setColorized(true)
+                .setLargeIcon(
+                        BitmapFactory.decodeResource(
+                                getCarContext().getResources(), R.drawable.ic_hi))
+                .addAction(
+                        new NotificationCompat.Action.Builder(
+                                R.drawable.ic_face_24px,
+                                "Action1 (phone)",
+                                createPendingIntent(INTENT_ACTION_PRIMARY_PHONE))
+                                .build())
+                .addAction(
+                        R.drawable.ic_commute_24px,
+                        "Action2 (phone)",
+                        createPendingIntent(INTENT_ACTION_SECONDARY_PHONE))
+                .extend(
+                        new CarAppExtender.Builder()
+                                .setContentTitle(title)
+                                .setContentText(text)
+                                .setContentIntent(
+                                        CarPendingIntent.getCarApp(getCarContext(), 0,
+                                                new Intent(Intent.ACTION_VIEW).setComponent(
+                                                        new ComponentName(getCarContext(),
+                                                                ShowcaseService.class)), 0))
+                                .setColor(CarColor.PRIMARY)
+                                .setSmallIcon(R.drawable.ic_bug_report_24px)
+                                .setLargeIcon(
+                                        BitmapFactory.decodeResource(
+                                                getCarContext().getResources(),
+                                                R.drawable.ic_hi))
+                                .addAction(
+                                        R.drawable.ic_commute_24px,
+                                        getCarContext().getString(R.string.navigate),
+                                        getPendingIntentForNavigation())
+                                .addAction(
+                                        R.drawable.ic_face_24px,
+                                        getCarContext().getString(R.string.call_action_title),
+                                        createPendingIntentForCall())
+                                .build());
+
+        carNotificationManager.notify(notificationId, builder);
+    }
+
+    private PendingIntent createPendingIntentForCall() {
+        Intent intent = new Intent(Intent.ACTION_DIAL).setData(Uri.parse("tel:+14257232350"));
+        return CarPendingIntent.getCarApp(getCarContext(), intent.hashCode(), intent, 0);
+    }
+
+    private PendingIntent getPendingIntentForNavigation() {
+        Intent intent = new Intent(CarContext.ACTION_NAVIGATE).setData(Uri.parse("geo:0,0?q=Home"));
+        return CarPendingIntent.getCarApp(getCarContext(), intent.hashCode(), intent, 0);
+    }
+
+    private String getImportanceString() {
+        switch (mImportance) {
+            case NotificationManager.IMPORTANCE_HIGH:
+                return getCarContext().getString(R.string.high_importance);
+            case NotificationManager.IMPORTANCE_DEFAULT:
+                return getCarContext().getString(R.string.default_importance);
+            case NotificationManager.IMPORTANCE_LOW:
+                return getCarContext().getString(R.string.low_importance);
+            default:
+                return getCarContext().getString(R.string.unknown_importance);
+        }
+    }
+
+    private String getCategoryString() {
+        return mIsNavCategory ? "Navigation" : "None";
+    }
+
+    /**
+     * Change the notification importance in a rotating sequence:
+     * Low -> Default -> High -> Low...
+     */
+    private void setImportance() {
+        switch (mImportance) {
+            case NotificationManager.IMPORTANCE_HIGH:
+                mImportance = NotificationManager.IMPORTANCE_LOW;
+                break;
+            case NotificationManager.IMPORTANCE_DEFAULT:
+                mImportance = NotificationManager.IMPORTANCE_HIGH;
+                break;
+            case NotificationManager.IMPORTANCE_LOW:
+                mImportance = NotificationManager.IMPORTANCE_DEFAULT;
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void registerBroadcastReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(INTENT_ACTION_PRIMARY_PHONE);
+        filter.addAction(INTENT_ACTION_SECONDARY_PHONE);
+
+        getCarContext().registerReceiver(mBroadcastReceiver, filter);
+    }
+
+    private void unregisterBroadcastReceiver() {
+        getCarContext().unregisterReceiver(mBroadcastReceiver);
+    }
+
+    /** Returns a pending intent with the provided intent action. */
+    private PendingIntent createPendingIntent(String intentAction) {
+        Intent intent = new Intent(intentAction);
+        return PendingIntent.getBroadcast(getCarContext(), intentAction.hashCode(), intent,
+                PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    final class HandlerCallback implements Handler.Callback {
+        @Override
+        public boolean handleMessage(Message msg) {
+            if (msg.what == MSG_SEND_NOTIFICATION) {
+                sendNotification();
+                mHandler.sendMessageDelayed(
+                        mHandler.obtainMessage(MSG_SEND_NOTIFICATION),
+                        NOTIFICATION_DELAY_IN_MILLIS);
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java
new file mode 100644
index 0000000..e94d105e
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.model.Action.BACK;
+
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.CarContext;
+import androidx.car.app.HostInfo;
+import androidx.car.app.Screen;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** Creates a screen that demonstrate the image loading in the library using a content provider. */
+public final class ContentProviderIconsDemoScreen extends Screen {
+    private static final int[] ICON_DRAWABLES = {
+            R.drawable.arrow_right_turn, R.drawable.arrow_straight, R.drawable.ic_i5,
+            R.drawable.ic_520
+    };
+    @Nullable
+    private final String mHostPackageName;
+
+    public ContentProviderIconsDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+
+        HostInfo hostInfo = carContext.getHostInfo();
+        mHostPackageName = hostInfo == null ? null : hostInfo.getPackageName();
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        String hostPackageName = mHostPackageName;
+        if (hostPackageName == null) {
+            // Cannot get the host package name, show an error message.
+            listBuilder.setNoItemsMessage(
+                    getCarContext().getString(R.string.images_unknown_host_error));
+        } else {
+            for (int i = 0; i < ICON_DRAWABLES.length; i++) {
+                int resId = ICON_DRAWABLES[i];
+                Uri uri = DelayedFileProvider.getUriForResource(getCarContext(), hostPackageName,
+                        resId);
+                listBuilder.addItem(
+                        new Row.Builder()
+                                .setImage(
+                                        new CarIcon.Builder(
+                                                IconCompat.createWithContentUri(uri))
+                                                .build())
+                                .setTitle(
+                                        getCarContext().getString(R.string.icon_title_prefix) + " "
+                                                + i)
+                                .build());
+            }
+        }
+
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.content_provider_icons_demo_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/DelayedFileProvider.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/DelayedFileProvider.java
new file mode 100644
index 0000000..217bd16
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/DelayedFileProvider.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.FileProvider;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.ThreadLocalRandom;
+
+/** A simple file provider that returns files after a random delay. */
+public class DelayedFileProvider extends FileProvider {
+    private static final String FILE_PROVIDER_AUTHORITY = "com.showcase.fileprovider";
+    private static final String RESOURCE_DIR = "res";
+    private static final long MIN_DELAY_MILLIS = 1000;
+    private static final long MAX_DELAY_MILLIS = 3000;
+
+    /** Creates a file from the given resource id and returns the URI for it. */
+    @NonNull
+    public static Uri getUriForResource(@NonNull Context context,
+            @NonNull String hostPackageName, int resId) {
+        File resourceFile =
+                new File(context.getFilesDir().getAbsolutePath(), RESOURCE_DIR + "/" + resId);
+        if (!resourceFile.exists()) {
+            resourceFile.getParentFile().mkdir();
+
+            Bitmap bm = BitmapFactory.decodeResource(context.getResources(), resId);
+            try (FileOutputStream fos = new FileOutputStream(resourceFile)) {
+                bm.compress(CompressFormat.PNG, 10, fos);
+            } catch (IOException ex) {
+                throw new IllegalArgumentException("Invalid resource " + resId);
+            }
+        }
+        Uri uri = getUriForFile(context, FILE_PROVIDER_AUTHORITY, resourceFile);
+
+        // FileProvider requires the app to grant temporary access to the car hosts for the file.
+        // A URI from a content provider may not need to do this if its contents are public.
+        context.grantUriPermission(hostPackageName, uri,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        return uri;
+    }
+
+    @Override
+    @NonNull
+    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
+            throws FileNotFoundException {
+        try {
+            // Wait for a random period between the minimum and maximum delay.
+            Thread.sleep(ThreadLocalRandom.current().nextLong(MIN_DELAY_MILLIS, MAX_DELAY_MILLIS));
+        } catch (InterruptedException e) {
+            throw new FileNotFoundException(e.getMessage());
+        }
+
+        return super.openFile(uri, mode);
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java
new file mode 100644
index 0000000..df4d004
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.SectionedItemList;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen demonstrating selectable lists. */
+public final class RadioButtonListDemoScreen extends Screen {
+
+    public RadioButtonListDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    private boolean mIsEnabled = true;
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ListTemplate.Builder templateBuilder = new ListTemplate.Builder();
+        ItemList radioList =
+                new ItemList.Builder()
+
+                        .addItem(buildRowForTemplate(R.string.option_row_radio_title,
+                                R.string.additional_text))
+
+                        .addItem(buildRowForTemplate(R.string.option_row_radio_icon_title,
+                                R.string.additional_text,
+                                buildImageWithResource(R.drawable
+                                        .ic_fastfood_white_48dp), Row.IMAGE_TYPE_ICON))
+
+                        .addItem(buildRowForTemplate(
+                                R.string.option_row_radio_icon_colored_text_title,
+                                R.string.additional_text,
+                                buildImageWithResource(R.drawable
+                                        .test_image_square), Row.IMAGE_TYPE_LARGE))
+
+                        .setOnSelectedListener(this::onSelected)
+                        .build();
+        templateBuilder.addSectionedList(
+                SectionedItemList.create(radioList,
+                        getCarContext().getString(R.string.sample_additional_list)));
+
+        return templateBuilder
+                .setTitle(getCarContext().getString(R.string.radio_button_list_demo_title))
+                .setActionStrip(
+                        new ActionStrip.Builder()
+                                .addAction(
+                                        new Action.Builder()
+                                                .setTitle(mIsEnabled
+                                                        ? getCarContext().getString(
+                                                        R.string.disable_all_rows)
+                                                        : getCarContext().getString(
+                                                                R.string.enable_all_rows))
+                                                .setOnClickListener(
+                                                        () -> {
+                                                            mIsEnabled = !mIsEnabled;
+                                                            invalidate();
+                                                        })
+                                                .build())
+                                .build())
+                .setHeaderAction(
+                        BACK).build();
+    }
+
+    private CarIcon buildImageWithResource(int imageId) {
+        return new CarIcon.Builder(
+                IconCompat.createWithResource(
+                        getCarContext(),
+                        imageId))
+                .build();
+    }
+
+    private Row buildRowForTemplate(int title, int text, CarIcon icon, int imageType) {
+        return new Row.Builder()
+                .addText(getCarContext().getString(text))
+                .setTitle(getCarContext().getString(title))
+                .setImage(icon, imageType)
+                .setEnabled(mIsEnabled)
+                .build();
+
+    }
+
+    private Row buildRowForTemplate(int title, int text) {
+        return new Row.Builder()
+                .addText(getCarContext().getString(text))
+                .setTitle(getCarContext().getString(title))
+                .setEnabled(mIsEnabled)
+                .build();
+
+    }
+
+    private void onSelected(int index) {
+        CarToast.makeText(getCarContext(),
+                        getCarContext()
+                                .getString(R.string.changes_selection_to_index_toast_msg_prefix)
+                                + ":"
+                                + " " + index, LENGTH_LONG)
+                .show();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java
new file mode 100644
index 0000000..ceb70e06
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 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.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.CarColor.GREEN;
+import static androidx.car.app.model.CarColor.RED;
+import static androidx.car.app.model.CarColor.YELLOW;
+
+import android.graphics.BitmapFactory;
+import android.text.SpannableString;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.common.Utils;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** Creates a screen that shows different types of texts and icons. */
+public final class TextAndIconsDemosScreen extends Screen {
+
+    private static final String FULL_STAR = "\u2605";
+    private static final String HALF_STAR = "\u00BD";
+
+    public TextAndIconsDemosScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        listBuilder.addItem(buildRowForTemplate(R.string.title_with_app_icon_row_title,
+                CarIcon.APP_ICON));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.png_bitmap_title,
+                buildCarIconWithBitmap(R.drawable.banana)));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.png_res_title,
+                buildCarIconWithResource(R.drawable.banana)));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.title_with_res_id_image_row_title,
+                buildSecondaryText(R.string.example_1_text, RED, 16, 3),
+                buildCarIconWithResource(R.drawable.ic_fastfood_white_48dp, RED)));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.title_with_svg_image_row_title,
+                buildSecondaryText(R.string.example_2_text, GREEN, 16, 5),
+                buildCarIconWithResource(R.drawable.ic_emoji_food_beverage_white_48dp, GREEN)));
+
+        listBuilder.addItem(buildRowForTemplate(R.string.colored_secondary_row_title,
+                getRatingsString(3.5)));
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.text_icons_demo_title))
+                .setHeaderAction(BACK)
+                .build();
+
+    }
+
+    private CarIcon buildCarIconWithResource(int imageId) {
+        return new CarIcon.Builder(
+                IconCompat.createWithResource(
+                        getCarContext(),
+                        imageId))
+                .build();
+    }
+
+    private CarIcon buildCarIconWithResource(int imageId, CarColor color) {
+        return new CarIcon.Builder(
+                IconCompat.createWithResource(
+                        getCarContext(),
+                        imageId))
+                .setTint(color)
+                .build();
+    }
+
+    private CarIcon buildCarIconWithBitmap(int imageId) {
+        return new CarIcon.Builder(
+                IconCompat.createWithBitmap(
+                        BitmapFactory.decodeResource(
+                                getCarContext().getResources(),
+                                imageId)))
+                .build();
+    }
+
+    /**
+    * build a colored line of secondary text using a specific CarColor and some custom text
+    */
+    private CharSequence buildSecondaryText(int textId, CarColor color, int index, int length) {
+        return Utils.colorize(getCarContext().getString(textId),
+                color, index, length);
+    }
+
+    private Row buildRowForTemplate(int title, CharSequence text) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .addText(text)
+                .build();
+    }
+
+    private Row buildRowForTemplate(int title, CarIcon image) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setImage(image)
+                .build();
+    }
+
+    private Row buildRowForTemplate(int title, CharSequence text, CarIcon image) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .addText(text)
+                .setImage(image)
+                .build();
+    }
+
+    private static CharSequence getRatingsString(Double ratings) {
+        String s;
+        double r;
+        for (s = "", r = ratings; r > 0; --r) {
+            s += r < 1 ? HALF_STAR : FULL_STAR;
+        }
+        SpannableString ss = new SpannableString(s + " ratings: " + ratings);
+        if (!s.isEmpty()) {
+            Utils.colorize(ss, YELLOW, 0, s.length());
+        }
+        return ss;
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java
new file mode 100644
index 0000000..092b530
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.CarColor.GREEN;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.OnClickListener;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.model.Toggle;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen demonstrating selectable lists. */
+public final class ToggleButtonListDemoScreen extends Screen {
+
+    public ToggleButtonListDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    private boolean mFirstToggleState;
+    private boolean mSecondToggleState;
+    private boolean mSecondToggleEnabled = true;
+    private int mImageType = Row.IMAGE_TYPE_ICON;
+    private boolean mSetTintToVector;
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        Toggle mToggleForVector = new Toggle.Builder((checked) -> {
+            mSetTintToVector = !mSetTintToVector;
+            invalidate();
+        }).setChecked(mSetTintToVector).build();
+
+        Toggle mFirstToggle = new Toggle.Builder((checked) -> {
+            mSecondToggleEnabled = checked;
+            if (checked) {
+                CarToast.makeText(getCarContext(), R.string.toggle_test_enabled,
+                        LENGTH_LONG).show();
+            } else {
+                CarToast.makeText(getCarContext(), R.string.toggle_test_disabled,
+                        LENGTH_LONG).show();
+            }
+            mFirstToggleState = !mFirstToggleState;
+            invalidate();
+        }).setChecked(mFirstToggleState).build();
+
+        Toggle mSecondToggle = new Toggle.Builder((checked) -> {
+            mSecondToggleState = !mSecondToggleState;
+            invalidate();
+        }).setChecked(mSecondToggleState).setEnabled(mSecondToggleEnabled).build();
+
+        ItemList.Builder builder = new ItemList.Builder();
+        builder.addItem(buildRowForTemplate(R.string.toggle_test_first_toggle_title,
+                R.string.toggle_test_first_toggle_text, mFirstToggle));
+
+        builder.addItem(buildRowForTemplate(R.string.toggle_test_second_toggle_title,
+                R.string.toggle_test_second_toggle_text, mSecondToggle));
+
+        builder.addItem(buildRowForTemplate(titleForVectorDrawable(),
+                R.string.vector_toggle_details, mToggleForVector, buildCarIconForVectorDrawable(),
+                null, Row.IMAGE_TYPE_ICON));
+
+        builder.addItem(buildRowForTemplate(R.string.image_test_title,
+                R.string.image_test_text, null, buildCarIconForImageTest(),
+                buildOnClickListenerForImageTest(), mImageType));
+
+        return new ListTemplate.Builder()
+                .setSingleList(builder.build())
+                .setTitle(getCarContext().getString(R.string.toggle_button_demo_title))
+                .setHeaderAction(BACK)
+                .setActionStrip(
+                        new ActionStrip.Builder()
+                                .addAction(
+                                        new Action.Builder()
+                                                .setTitle(getCarContext().getString(
+                                                        R.string.home_caps_action_title))
+                                                .setOnClickListener(
+                                                        () -> getScreenManager().popToRoot())
+                                                .build())
+                                .build())
+                .build();
+    }
+
+    private CarIcon buildCarIconForVectorDrawable() {
+        CarIcon.Builder carIconBuilder = new CarIcon.Builder(
+                IconCompat.createWithResource(
+                        getCarContext(),
+                        R.drawable.ic_fastfood_white_48dp));
+
+        if (mSetTintToVector) {
+            carIconBuilder.setTint(GREEN);
+        }
+        return carIconBuilder.build();
+    }
+
+    private CarIcon buildCarIconForImageTest() {
+        return new CarIcon.Builder(
+                IconCompat.createWithResource(
+                        getCarContext(),
+                        R.drawable.ic_fastfood_yellow_48dp))
+                .build();
+    }
+
+    private int titleForVectorDrawable() {
+        if (mSetTintToVector) {
+            return R.string.vector_with_tint_title;
+        } else {
+            return R.string.vector_no_tint_title;
+        }
+    }
+
+    private OnClickListener buildOnClickListenerForImageTest() {
+        return () -> {
+            mImageType =
+                    mImageType == Row.IMAGE_TYPE_ICON
+                            ? Row.IMAGE_TYPE_LARGE
+                            : Row.IMAGE_TYPE_ICON;
+            invalidate();
+        };
+    }
+
+    private Row buildRowForTemplate(int title, int text, Toggle toggle) {
+        return new Row.Builder()
+                .addText(getCarContext().getString(text))
+                .setTitle(getCarContext().getString(title))
+                .setToggle(toggle)
+                .build();
+
+    }
+
+    private Row buildRowForTemplate(int title, int text, Toggle toggle, CarIcon icon,
+            OnClickListener onClickListener, int imageType) {
+
+        Row.Builder rowBuilder = new Row.Builder();
+        rowBuilder
+                .addText(getCarContext().getString(text))
+                .setTitle(getCarContext().getString(title))
+                .setImage(icon, imageType);
+
+        if (toggle != null) rowBuilder.setToggle(toggle);
+        if (onClickListener != null) rowBuilder.setOnClickListener(onClickListener);
+
+        return rowBuilder.build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/LongMessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/LongMessageTemplateDemoScreen.java
new file mode 100644
index 0000000..03e5598
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/LongMessageTemplateDemoScreen.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.Action.FLAG_PRIMARY;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.LongMessageTemplate;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.ParkedOnlyOnClickListener;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+
+/** A screen that demonstrates the long message template. */
+public class LongMessageTemplateDemoScreen extends Screen {
+    public LongMessageTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        if (getCarContext().getCarAppApiLevel() < CarAppApiLevels.LEVEL_2) {
+            return new MessageTemplate.Builder(
+                    getCarContext().getString(R.string.long_msg_template_not_supported_text))
+                    .setTitle(getCarContext().getString(
+                            R.string.long_msg_template_not_supported_title))
+                    .setHeaderAction(Action.BACK)
+                    .build();
+        }
+
+        Action.Builder primaryActionBuilder = new Action.Builder()
+                .setOnClickListener(
+                        ParkedOnlyOnClickListener.create(() -> {
+                            getScreenManager().pop();
+                            CarToast.makeText(
+                                    getCarContext(),
+                                    getCarContext().getString(R.string.primary_action_title),
+                                    LENGTH_LONG
+                            ).show();
+                        }))
+                .setTitle(getCarContext().getString(R.string.accept_action_title));
+        if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_4) {
+            primaryActionBuilder.setFlags(FLAG_PRIMARY);
+        }
+
+        return new LongMessageTemplate.Builder(
+                getCarContext().getString(R.string.long_msg_template_text))
+                .setTitle(getCarContext().getString(R.string.long_msg_template_demo_title))
+                .setHeaderAction(BACK)
+                .addAction(primaryActionBuilder.build())
+                .addAction(new Action.Builder()
+                        .setBackgroundColor(CarColor.RED)
+                        .setOnClickListener(
+                                ParkedOnlyOnClickListener.create(() -> getScreenManager().pop()))
+                        .setTitle(getCarContext().getString(R.string.reject_action_title))
+                        .build())
+                .setActionStrip(new ActionStrip.Builder()
+                        .addAction(new Action.Builder()
+                                .setTitle(getCarContext().getString(R.string.more_action_title))
+                                .setOnClickListener(
+                                        () ->
+                                                CarToast.makeText(
+                                                                getCarContext(),
+                                                                getCarContext().getString(
+                                                                        R.string.more_toast_msg),
+                                                                LENGTH_LONG)
+                                                        .show())
+                                .build())
+                        .build())
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/ShortMessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/ShortMessageTemplateDemoScreen.java
new file mode 100644
index 0000000..f24d545
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/messagetemplates/ShortMessageTemplateDemoScreen.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.templatelayouts.messagetemplates;
+
+import static androidx.car.app.CarToast.LENGTH_LONG;
+import static androidx.car.app.model.Action.BACK;
+import static androidx.car.app.model.Action.FLAG_PRIMARY;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.versioning.CarAppApiLevels;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** A screen that demonstrates the message template. */
+public class ShortMessageTemplateDemoScreen extends Screen {
+
+    public ShortMessageTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        Action.Builder primaryActionBuilder = new Action.Builder()
+                .setOnClickListener(() -> {
+                    CarToast.makeText(
+                            getCarContext(),
+                            getCarContext().getString(R.string.primary_action_title),
+                            LENGTH_LONG
+                    ).show();
+                })
+                .setTitle(getCarContext().getString(R.string.ok_action_title));
+        if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_4) {
+            primaryActionBuilder.setFlags(FLAG_PRIMARY);
+        }
+
+        Action settings = new Action.Builder()
+                .setTitle(getCarContext().getString(
+                        R.string.settings_action_title))
+                .setOnClickListener(
+                        () -> CarToast.makeText(
+                                        getCarContext(),
+                                        getCarContext().getString(
+                                                R.string.settings_toast_msg),
+                                        LENGTH_LONG)
+                                .show())
+                .build();
+
+        return new MessageTemplate.Builder(
+                getCarContext().getString(R.string.msg_template_demo_text))
+                .setTitle(getCarContext().getString(R.string.msg_template_demo_title))
+                .setIcon(
+                        new CarIcon.Builder(
+                                IconCompat.createWithResource(
+                                        getCarContext(),
+                                        R.drawable.ic_emoji_food_beverage_white_48dp))
+                                .setTint(CarColor.GREEN)
+                                .build())
+                .setHeaderAction(BACK)
+                .addAction(primaryActionBuilder.build())
+                .addAction(
+                        new Action.Builder()
+                                .setBackgroundColor(CarColor.RED)
+                                .setTitle(getCarContext().getString(R.string.throw_action_title))
+                                .setOnClickListener(
+                                        () -> {
+                                            throw new RuntimeException("Error");
+                                        })
+                                .build())
+
+                .setActionStrip(
+                        new ActionStrip.Builder()
+                                .addAction(settings)
+                                .build())
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/res/values/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
index 45f07ef..9a75cf192 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
@@ -113,7 +113,7 @@
   <string name="example_2_text">This text has a green color</string>
   <string name="example_3_text">This text has a blue color</string>
   <string name="example_4_text">This text has a yellow color</string>
-  <string name="example_5_text">This text uses the primary colo</string>
+  <string name="example_5_text">This text uses the primary color</string>
   <string name="example_6_text">This text uses the secondary color</string>
   <string name="color_demo">Color Demo</string>
 
@@ -291,6 +291,7 @@
   <!-- MessageTemplateDemoScreen -->
   <string name="msg_template_demo_title">Message Template Demo</string>
   <string name="msg_template_demo_text">Message goes here.\nMore text on second line.</string>
+  <string name="short_msg_template_demo_title">Short Message Template Demo</string>
 
   <!-- PaneTemplateDemoScreen -->
   <string name="pane_template_demo_title">Pane Template Demo</string>
@@ -338,7 +339,7 @@
   <string name="vector_with_tint_title">A vector drawable, with a tint</string>
   <string name="vector_with_app_theme_attr_title">A vector drawable, with an app\'s theme attribute for its color</string>
   <string name="png_res_title">A PNG, sent as a resource</string>
-  <string name="png_bitmap_title">A PNG, sent as a resource</string>
+  <string name="png_bitmap_title">A PNG, sent as a bitmap</string>
 
   <!-- RowDemoScreen -->
   <string name="just_row_title">Just a title</string>
@@ -356,6 +357,7 @@
   <string name="row_text_icons_demo_title">Rows with Text and Icons Demo</string>
 
   <!-- SelectableListsDemoScreen -->
+  <string name="radio_button_list_demo_title">Radio Button Lists Demo</string>
   <string name="selectable_lists_demo_title">Selectable Lists Demo</string>
   <string name="option_1_title">Option 1</string>
   <string name="option_2_title">Option 2</string>
@@ -371,6 +373,9 @@
   <string name="task_limit_reached_msg">Task limit reached\nGoing forward will force stop the app</string>
   <string name="task_step_of_title">Task step %1$d of %2$d</string>
   <string name="task_step_of_text">Click to go forward</string>
+
+  <!-- ToggleButtonDemoScreen -->
+  <string name="toggle_button_demo_title">Toggle Button Demo</string>
   <string name="toggle_test_title">Toggle test</string>
   <string name="toggle_test_text">Stateful changes are allowed</string>
   <string name="toggle_test_first_toggle_title">Enable Toggle Test</string>
@@ -388,6 +393,8 @@
   <string name="misc_templates_demos_title">Misc Templates Demos</string>
   <string name="cal_api_level_prefix" translatable="false">CAL API Level: %d</string>
   <string name="showcase_demos_title">Showcase Demos</string>
+  <string name="template_layouts_demo_title">Template Layout Demos</string>
+  <string name="grid_template_menu_demo_title">Grid Template Demos</string>
 
   <!-- User Interactions Screen -->
   <string name="voice_access_demo_title">Voice Access Demo Screen</string>
@@ -433,4 +440,12 @@
   <string name="location_9_address" translatable="false">111 Waverly Way, Kirkland, WA 98033</string>
   <string name="location_description_text_label">Text label</string>
   <string name="location_phone_not_available" translatable="false">n/a</string>
+
+  <string name="parking_vs_driving_demo_title">Parking Vs Driving Demo</string>
+  <string name="latest_feature_details">Cluster Displays in cars!</string>
+  <string name="latest_feature_title">Latest Features</string>
+  <string name="loading_toggle_enabled">Loading enabled</string>
+  <string name="loading_toggle_disabled">Loading disabled</string>
+  <string name="loading_screen">Loading screen</string>
+  <string name="vector_toggle_details">Toggle to add/remove color</string>
 </resources>
diff --git a/car/app/app/lint-baseline.xml b/car/app/app/lint-baseline.xml
index 6f805be..1c92b5a 100644
--- a/car/app/app/lint-baseline.xml
+++ b/car/app/app/lint-baseline.xml
@@ -327,7 +327,7 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: CarAppApiLevels.UNKNOWN, CarAppApiLevels.LEVEL_1, CarAppApiLevels.LEVEL_2, CarAppApiLevels.LEVEL_3, CarAppApiLevels.LEVEL_4, CarAppApiLevels.LEVEL_5"
+        message="Must be one of: CarAppApiLevels.UNKNOWN, CarAppApiLevels.LEVEL_1, CarAppApiLevels.LEVEL_2, CarAppApiLevels.LEVEL_3, CarAppApiLevels.LEVEL_4, CarAppApiLevels.LEVEL_5, CarAppApiLevels.LEVEL_6"
         errorLine1="        mCarAppApiLevel = handshakeInfo.getHostCarAppApiLevel();"
         errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -370,4 +370,40 @@
             file="src/main/java/androidx/car/app/navigation/model/PanModeDelegateImpl.java"/>
     </issue>
 
+    <issue
+        id="SupportAnnotationUsage"
+        message="This annotation does not apply for type java.util.List&lt;androidx.car.app.hardware.climate.CarClimateFeature>; expected int"
+        errorLine1="    @ClimateProfileRequest.ClimateProfileFeature"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/car/app/hardware/climate/RegisterClimateStateRequest.java"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="This annotation does not apply for type java.util.List&lt;androidx.car.app.hardware.climate.CarClimateFeature>; expected int"
+        errorLine1="    @ClimateProfileRequest.ClimateProfileFeature"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/car/app/hardware/climate/RegisterClimateStateRequest.java"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="This annotation does not apply for type java.util.List&lt;androidx.car.app.hardware.climate.CarClimateFeature>; expected int"
+        errorLine1="    @ClimateProfileRequest.ClimateProfileFeature"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/car/app/hardware/climate/RegisterClimateStateRequest.java"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="This annotation does not apply for type androidx.car.app.model.Row.Builder; expected int or long"
+        errorLine1="        @IntRange(from = 0)"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/car/app/model/Row.java"/>
+    </issue>
+
 </issues>
diff --git a/paging/paging-common/lint-baseline.xml b/paging/paging-common/lint-baseline.xml
new file mode 100644
index 0000000..2bfe322
--- /dev/null
+++ b/paging/paging-common/lint-baseline.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+        errorLine1="        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/paging/PageFetcher.kt"/>
+    </issue>
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Did you mean `@get:RestrictTo`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+        errorLine1="    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/paging/PagedList.kt"/>
+    </issue>
+
+</issues>
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/AnchorTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/AnchorTest.kt
new file mode 100644
index 0000000..4f3f8e7
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/AnchorTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.recyclerview.widget
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.resume
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class AnchorTest : BaseRecyclerViewInstrumentationTest() {
+
+    @Test
+    fun noAnchoringWhenViewportNotFilled(): Unit = runBlocking(Dispatchers.Main) {
+        val mainAdapter = AnchorTestAdapter(0)
+        // This simulates a loading spinning added to the end via ConcatAdapter, such as Paging does
+        val suffixAdapter = AnchorTestAdapter(1)
+        val concatAdapter = ConcatAdapter(mainAdapter, suffixAdapter)
+
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val layoutManager = LinearLayoutManager(
+            context,
+            LinearLayoutManager.VERTICAL,
+            false
+        )
+        mRecyclerView = RecyclerView(context)
+        mRecyclerView.layoutParams = TestedFrameLayout.FullControlLayoutParams(100, 100)
+        mRecyclerView.adapter = concatAdapter
+        mRecyclerView.layoutManager = layoutManager
+        activity.container.addView(mRecyclerView)
+        mRecyclerView.awaitLayout()
+
+        assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+        assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(0)
+
+        mainAdapter.numItems = 20
+        mRecyclerView.awaitLayout()
+        // Before this was fixed, this would anchor to the "spinner" and end up at the
+        // bottom of the list (first visible item would be 11)
+        assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+        assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(9)
+    }
+
+    @Test
+    fun noAnchoringWhenFillingWrapContentRecyclerView(): Unit = runBlocking(Dispatchers.Main) {
+        val mainAdapter = AnchorTestAdapter(0)
+        // This simulates a loading spinning added to the end via ConcatAdapter, such as Paging does
+        val suffixAdapter = AnchorTestAdapter(1)
+        val concatAdapter = ConcatAdapter(mainAdapter, suffixAdapter)
+
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val layoutManager = LinearLayoutManager(
+            context,
+            LinearLayoutManager.VERTICAL,
+            false
+        )
+
+        val frame = FrameLayout(context)
+        frame.layoutParams = TestedFrameLayout.FullControlLayoutParams(100, 100)
+        mRecyclerView = RecyclerView(context)
+        mRecyclerView.layoutParams =
+            FrameLayout.LayoutParams(100, ViewGroup.LayoutParams.WRAP_CONTENT)
+        mRecyclerView.adapter = concatAdapter
+        mRecyclerView.layoutManager = layoutManager
+        frame.addView(mRecyclerView)
+        activity.container.addView(frame)
+        mRecyclerView.awaitLayout()
+
+        assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+        assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(0)
+
+        mainAdapter.numItems = 20
+        mRecyclerView.awaitLayout()
+
+        // Before this was fixed, this would anchor to the "spinner" and end up at the
+        // bottom of the list (first visible item would be 11)
+        assertThat(layoutManager.findFirstVisibleItemPosition()).isEqualTo(0)
+        assertThat(layoutManager.findLastVisibleItemPosition()).isEqualTo(9)
+    }
+
+    private class AnchorTestAdapter(length: Int) :
+        RecyclerView.Adapter<AnchorTestAdapter.ViewHolder>() {
+        class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+
+        var numItems: Int = length
+            set(value) {
+                val old = field
+                val diff = value - old
+                if (diff < 0) {
+                    TODO("Length reduction not supported")
+                }
+                field = value
+                notifyItemRangeInserted(old, diff)
+            }
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+            val lp = RecyclerView.LayoutParams(10, 10)
+            val v = View(parent.context)
+            v.layoutParams = lp
+            return ViewHolder(v)
+        }
+
+        override fun getItemCount(): Int = numItems
+
+        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        }
+    }
+}
+
+private suspend fun View.awaitLayout() {
+    suspendCancellableCoroutine { continuation ->
+        val runnable = Runnable {
+            continuation.resume(Unit)
+        }
+        continuation.invokeOnCancellation {
+            this.removeCallbacks(runnable)
+        }
+        this.post(runnable)
+    }
+}
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerMeasureAnchorTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerMeasureAnchorTest.kt
new file mode 100644
index 0000000..0fca345
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerMeasureAnchorTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.recyclerview.widget
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.IllegalArgumentException
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+public class LinearLayoutManagerMeasureAnchorTest {
+
+    @Test
+    public fun testStackFromEndVertical() {
+        test(true, false, true)
+    }
+
+    @Test
+    public fun testReverseLayoutVertical() {
+        test(false, true, true)
+    }
+
+    @Test
+    public fun testStackFromEndHorizontal() {
+        test(true, false, false)
+    }
+
+    @Test
+    public fun testReverseLayoutHorizontal() {
+        test(false, true, false)
+    }
+
+    public fun test(reverseLayout: Boolean, stackFromEnd: Boolean, vertical: Boolean) {
+
+        if (!(reverseLayout xor stackFromEnd)) {
+            throw IllegalArgumentException("Must be reverseLayout xor stackFromEnd")
+        }
+
+        // Arrange
+
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val recyclerView = RecyclerView(context)
+        val orientation = if (vertical) RecyclerView.VERTICAL else RecyclerView.HORIZONTAL
+        recyclerView.layoutManager =
+            LinearLayoutManager(context, orientation, reverseLayout).apply {
+                this.stackFromEnd = stackFromEnd
+            }
+        recyclerView.adapter = TestAdapter(context, vertical)
+
+        // Act
+
+        recyclerView.measure(
+            View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.AT_MOST),
+            View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.AT_MOST)
+        )
+        recyclerView.measure(
+            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST),
+            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST)
+        )
+        recyclerView.layout(0, 0, 100, 100)
+
+        // Assert
+
+        val view: View? =
+            (0 until recyclerView.childCount)
+                .map { recyclerView.getChildAt(it) }
+                .find { it.tag == if (reverseLayout) 0 else 9 }
+
+        assertThat(view).isNotNull()
+        if (vertical) {
+            assertThat(view!!.top).isEqualTo(0)
+        } else {
+            assertThat(view!!.left).isEqualTo(0)
+        }
+    }
+
+    internal class TestAdapter(val context: Context, val vertical: Boolean) :
+        RecyclerView.Adapter<TestViewHolder>() {
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
+            val view = View(context)
+            view.layoutParams = if (vertical) {
+                ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100)
+            } else {
+                ViewGroup.LayoutParams(100, ViewGroup.LayoutParams.MATCH_PARENT)
+            }
+            return TestViewHolder(view)
+        }
+
+        override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
+            holder.itemView.tag = position
+        }
+
+        override fun getItemCount(): Int {
+            return 10
+        }
+    }
+
+    internal class TestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPreviousMeasuredDimensionsTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPreviousMeasuredDimensionsTest.kt
new file mode 100644
index 0000000..3b42020
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewPreviousMeasuredDimensionsTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.recyclerview.widget
+
+import android.content.Context
+import android.view.View
+import android.view.View.MeasureSpec.UNSPECIFIED
+import android.view.View.MeasureSpec.AT_MOST
+import android.view.View.MeasureSpec.EXACTLY
+import android.view.ViewGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@SmallTest
+public class RecyclerViewPreviousMeasuredDimensionsTest(
+    private val firstWidthMode: Int,
+    private val firstHeightMode: Int,
+    private val secondWidthMode: Int,
+    private val secondHeightMode: Int
+) {
+
+    public companion object {
+        @JvmStatic
+        @Parameterized.Parameters(
+            name = "firstWidthMode={0}, firstHeightMode={1}, " +
+                "secondWidthMode={2}, secondHeightMode={3}"
+        )
+        public fun data(): Collection<Array<Int>> {
+            val widthModes = listOf(
+                UNSPECIFIED,
+                EXACTLY,
+                AT_MOST
+            )
+
+            val data = ArrayList<Array<Int>>()
+            for (firstWidthMode in widthModes) {
+                for (firstHeightMode in widthModes) {
+                    for (secondWidthMode in widthModes) {
+                        for (secondHeightMode in widthModes) {
+                            data.add(
+                                listOf(
+                                    firstWidthMode,
+                                    firstHeightMode,
+                                    secondWidthMode,
+                                    secondHeightMode
+                                ).toTypedArray()
+                            )
+                        }
+                    }
+                }
+            }
+            return data
+        }
+    }
+
+    @Test
+    public fun previousMeasuredDimensionTest() {
+
+        // Arrange
+
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val recyclerView = RecyclerView(context)
+        recyclerView.layoutManager =
+            LinearLayoutManager(context).apply {
+                this.stackFromEnd = stackFromEnd
+            }
+        recyclerView.adapter = TestAdapter(context)
+
+        // Assert 0
+
+        assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(0)
+        assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(0)
+
+        // Act 1
+
+        recyclerView.measure(
+            View.MeasureSpec.makeMeasureSpec(100, firstWidthMode),
+            View.MeasureSpec.makeMeasureSpec(100, firstHeightMode)
+        )
+
+        // Assert 1
+
+        val expectedPreviousWidth1 = if (firstWidthMode == UNSPECIFIED) 300 else 100
+        val expectedPreviousHeight1 = if (firstHeightMode == UNSPECIFIED) 300 else 100
+        assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(expectedPreviousWidth1)
+        assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(expectedPreviousHeight1)
+
+        // Act 2
+
+        recyclerView.measure(
+            View.MeasureSpec.makeMeasureSpec(200, secondWidthMode),
+            View.MeasureSpec.makeMeasureSpec(200, secondHeightMode)
+        )
+
+        // Assert 2
+
+        val expectedPreviousWidth2 = if (secondWidthMode == UNSPECIFIED) 300 else 200
+        val expectedPreviousHeight2 = if (secondHeightMode == UNSPECIFIED) 300 else 200
+        assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(expectedPreviousWidth2)
+        assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(expectedPreviousHeight2)
+
+        // Act 3, layout should not have any affect on previous measured values.
+
+        recyclerView.layout(0, 0, 500, 500)
+
+        // Assert 3
+
+        assertThat(recyclerView.mState.previousMeasuredWidth).isEqualTo(expectedPreviousWidth2)
+        assertThat(recyclerView.mState.previousMeasuredHeight).isEqualTo(expectedPreviousHeight2)
+    }
+
+    internal class TestAdapter(val context: Context) :
+        RecyclerView.Adapter<TestViewHolder>() {
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
+            val view = View(context)
+            view.layoutParams = ViewGroup.LayoutParams(300, 300)
+            return TestViewHolder(view)
+        }
+
+        override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
+            holder.itemView.tag = position
+        }
+
+        override fun getItemCount(): Int {
+            return 1
+        }
+    }
+
+    internal class TestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
index 22fd533..c7b7852 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
@@ -85,6 +85,30 @@
      */
     private boolean mLastStackFromEnd;
 
+    /**
+     * Whether the last layout filled the entire viewport
+     *
+     * If the last layout did not fill the viewport, we should not attempt to calculate an
+     * anchoring based on the current children (other than if one is focused), because there
+     * isn't any scrolling that could have occurred that would indicate a position in the list
+     * that needs to be preserved - and in fact, trying to do so could produce the wrong result,
+     * such as the case of anchoring to a loading spinner at the end of the list.
+     */
+    private boolean mLastLayoutFilledViewport = false;
+
+    /**
+     * Whether the *current* layout filled the entire viewport
+     *
+     * This is used to populate mLastLayoutFilledViewport. It exists as a separate variable
+     * because we need to populate it at the correct moment, which is tricky due to the
+     * LayoutManager layout being called multiple times.  We want to not set it in prelayout
+     * (because that's not the real layout), but we want to set it the *first* time that the
+     * actual layout is run, because for certain non-exact layout cases, there are two passes,
+     * with the second pass being provided an EXACTLY spec (when the actual spec was non-exact).
+     * This would otherwise incorrectly believe the viewport was filled, because it was provided
+     * just enough space to contain the content, and thus it would always fill the viewport.
+     */
+    private Boolean mThisLayoutFilledViewport = null;
 
     /**
      * Defines if layout should be calculated from end to start.
@@ -571,11 +595,26 @@
         // resolve layout direction
         resolveShouldLayoutReverse();
 
+        boolean layoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+
+        // The 2 booleans below are necessary because if we are laying out from the end, and the
+        // previous measured dimension is different from the new measured value, then any
+        // previously calculated anchor will be incorrect.
+        boolean reCalcAnchorDueToVertical = layoutFromEnd
+                && getOrientation() == RecyclerView.VERTICAL
+                && state.getPreviousMeasuredHeight() != getHeight();
+        boolean reCalcAnchorDueToHorizontal = layoutFromEnd
+                && getOrientation() == RecyclerView.HORIZONTAL
+                && state.getPreviousMeasuredWidth() != getWidth();
+
+        boolean reCalcAnchor = reCalcAnchorDueToVertical || reCalcAnchorDueToHorizontal
+                || mPendingScrollPosition != RecyclerView.NO_POSITION || mPendingSavedState != null;
+
         final View focused = getFocusedChild();
-        if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
-                || mPendingSavedState != null) {
+
+        if (!mAnchorInfo.mValid || reCalcAnchor) {
             mAnchorInfo.reset();
-            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+            mAnchorInfo.mLayoutFromEnd = layoutFromEnd;
             // calculate anchor position and coordinate
             updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
             mAnchorInfo.mValid = true;
@@ -714,13 +753,17 @@
             // because layout from end may be changed by scroll to position
             // we re-calculate it.
             // find which side we should check for gaps.
-            if (mShouldReverseLayout ^ mStackFromEnd) {
+            if (layoutFromEnd) {
                 int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
                 startOffset += fixOffset;
                 endOffset += fixOffset;
                 fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
                 startOffset += fixOffset;
                 endOffset += fixOffset;
+                if (!state.isPreLayout() && mThisLayoutFilledViewport == null) {
+                    mThisLayoutFilledViewport =
+                            (startOffset <= mOrientationHelper.getStartAfterPadding());
+                }
             } else {
                 int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
                 startOffset += fixOffset;
@@ -728,6 +771,10 @@
                 fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
                 startOffset += fixOffset;
                 endOffset += fixOffset;
+                if (!state.isPreLayout() && mThisLayoutFilledViewport == null) {
+                    mThisLayoutFilledViewport =
+                            (endOffset >= mOrientationHelper.getEndAfterPadding());
+                }
             }
         }
         layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
@@ -749,6 +796,8 @@
         mPendingSavedState = null; // we don't need this anymore
         mPendingScrollPosition = RecyclerView.NO_POSITION;
         mPendingScrollPositionOffset = INVALID_OFFSET;
+        mLastLayoutFilledViewport = mThisLayoutFilledViewport != null && mThisLayoutFilledViewport;
+        mThisLayoutFilledViewport = null;
         mAnchorInfo.reset();
     }
 
@@ -858,11 +907,19 @@
         if (getChildCount() == 0) {
             return false;
         }
+
         final View focused = getFocusedChild();
         if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
             anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
             return true;
         }
+
+        // If we did not fill the layout, don't anchor. This prevents, for example,
+        // anchoring to the bottom of the list when there is a loading indicator.
+        if (!mLastLayoutFilledViewport) {
+            return false;
+        }
+
         if (mLastStackFromEnd != mStackFromEnd) {
             return false;
         }
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index df4de2d..866b4c4 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -2226,7 +2226,7 @@
 
     /**
      * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
-     * range. This value is used to compute the length of the thumb within the scrollbar's track.
+     * range. This value is used to compute the position of the thumb within the scrollbar's track.
      * </p>
      *
      * <p>The range is expressed in arbitrary units that must be the same as the units used by
@@ -2287,7 +2287,7 @@
      * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
      * LayoutManager.</p>
      *
-     * @return The total horizontal range represented by the vertical scrollbar
+     * @return The total horizontal range represented by the horizontal scrollbar
      * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
      */
     @Override
@@ -2300,7 +2300,7 @@
 
     /**
      * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
-     * This value is used to compute the length of the thumb within the scrollbar's track. </p>
+     * This value is used to compute the position of the thumb within the scrollbar's track. </p>
      *
      * <p>The range is expressed in arbitrary units that must be the same as the units used by
      * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
@@ -3946,6 +3946,12 @@
 
     @Override
     protected void onMeasure(int widthSpec, int heightSpec) {
+        internalOnMeasure(widthSpec, heightSpec);
+        mState.mPreviousMeasuredWidth = this.getMeasuredWidth();
+        mState.mPreviousMeasuredHeight = this.getMeasuredHeight();
+    }
+
+    private void internalOnMeasure(int widthSpec, int heightSpec) {
         if (mLayout == null) {
             defaultOnMeasure(widthSpec, heightSpec);
             return;
@@ -10893,7 +10899,7 @@
          * <p>Default implementation returns 0.</p>
          *
          * @param state Current State of RecyclerView where you can find total item count
-         * @return The total horizontal range represented by the vertical scrollbar
+         * @return The total horizontal range represented by the horizontal scrollbar
          * @see RecyclerView#computeHorizontalScrollRange()
          */
         public int computeHorizontalScrollRange(@NonNull State state) {
@@ -13296,6 +13302,42 @@
         boolean mRunPredictiveAnimations = false;
 
         /**
+         * The values in these fields reflect the values passed to
+         * {@link RecyclerView#setMeasuredDimension(int, int)} and are set just before
+         * {@link RecyclerView#onMeasure(int, int)} is completed. They are intended to be used to
+         * during onMeasure(int, int) to know how the RecyclerView was measured during previous
+         * calls to onMeasure(int, int).
+         */
+        int mPreviousMeasuredWidth = 0;
+        int mPreviousMeasuredHeight = 0;
+
+        // TODO(b/181991552): Make this public after 1.2.0 stable.
+        /**
+         * Returns the previously measured width of the {@link RecyclerView} that was most
+         * recently set via {@link RecyclerView#setMeasuredDimension(int, int)} during the most
+         * recent call to {@link RecyclerView#onMeasure(int, int)}. This is intended to be used
+         * during {@link LayoutManager#onLayoutChildren(Recycler, State)} when
+         * {@link State#isMeasuring()} is {@code true} in order to understand how the current
+         * measure specs compare to the result of any previous measurement.
+         */
+        int getPreviousMeasuredWidth() {
+            return mPreviousMeasuredWidth;
+        }
+
+        // TODO(b/181991552): Make this public after 1.2.0 stable.
+        /**
+         * Returns the previously measured height of the {@link RecyclerView} that was most
+         * recently set via {@link RecyclerView#setMeasuredDimension(int, int)} during the most
+         * recent call to {@link RecyclerView#onMeasure(int, int)}. This is intended to be used
+         * during {@link LayoutManager#onLayoutChildren(Recycler, State)} when
+         * {@link State#isMeasuring()} is {@code true} in order to understand how the current
+         * measure specs compare to the result of any previous measurement.
+         */
+        int getPreviousMeasuredHeight() {
+            return mPreviousMeasuredHeight;
+        }
+
+        /**
          * This data is saved before a layout calculation happens. After the layout is finished,
          * if the previously focused view has been replaced with another view for the same item, we
          * move the focus to the new item automatically.
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
index 63c2de1..ae1c0fb 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -83,10 +83,15 @@
                 }
                 appendLine()
             }
-            appendLine("Generated files:")
-            generatedSources.forEach {
-                appendLine(it.relativePath)
+            if (generatedSources.isEmpty()) {
+                appendLine("Generated files: NONE")
+            } else {
+                appendLine("Generated files:")
+                generatedSources.forEach {
+                    appendLine(it.relativePath)
+                }
             }
+            appendLine()
             appendLine("RAW OUTPUT:")
             appendLine(rawOutput())
         }
@@ -414,7 +419,7 @@
         if (processingException != null) {
             // processor has an error which we want to throw but we also want the subject, hence
             // we wrap it
-            throw createProcessorAssertionError(
+            throw CompilationAssertionError(
                 compilationResult = compilationResult,
                 realError = processingException
             )
@@ -466,13 +471,15 @@
     }
 
     /**
-     * Helper method to create an exception that does not include the stack trace from the test
-     * infra, instead, it just reports the stack trace of the actual error with added log.
+     * Helper error that does not include the stack trace from the test infra, instead, it just
+     * reports the stack trace of the actual error with added log.
      */
-    private fun createProcessorAssertionError(
-        compilationResult: CompilationResult,
-        realError: Throwable
-    ) = object : AssertionError("processor did throw an error\n$compilationResult", realError) {
+    private class CompilationAssertionError(
+        val compilationResult: CompilationResult,
+        val realError: Throwable
+    ) : AssertionError(
+        "Processor did throw an error.\n$compilationResult", realError
+    ) {
         override fun fillInStackTrace(): Throwable {
             return realError
         }
@@ -509,7 +516,7 @@
     diagnostics = diagnostics
 ) {
     override fun rawOutput(): String {
-        return delegate.diagnostics().joinToString {
+        return delegate.diagnostics().joinToString(separator = System.lineSeparator()) {
             it.toString()
         }
     }
@@ -532,7 +539,7 @@
     override fun rawOutput(): String {
         return delegate.diagnostics.flatMap {
             it.value
-        }.joinToString {
+        }.joinToString(separator = System.lineSeparator()) {
             it.toString()
         }
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
index 77be512..a425abc 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
@@ -17,9 +17,18 @@
 package androidx.room.compiler.codegen
 
 import androidx.room.compiler.processing.XNullability
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.KTypeName
-import com.squareup.kotlinpoet.javapoet.toKTypeName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.toKClassName
+
+typealias JCodeBlock = com.squareup.javapoet.CodeBlock
+typealias JCodeBlockBuilder = com.squareup.javapoet.CodeBlock.Builder
+typealias JAnnotationSpecBuilder = com.squareup.javapoet.AnnotationSpec.Builder
+typealias JTypeSpecBuilder = com.squareup.javapoet.TypeSpec.Builder
+typealias KCodeBlock = com.squareup.kotlinpoet.CodeBlock
+typealias KCodeBlockBuilder = com.squareup.kotlinpoet.CodeBlock.Builder
+typealias KAnnotationSpecBuilder = com.squareup.kotlinpoet.AnnotationSpec.Builder
+typealias KTypeSpecBuilder = com.squareup.kotlinpoet.TypeSpec.Builder
+typealias JArrayTypeName = com.squareup.javapoet.ArrayTypeName
 
 // TODO(b/127483380): Recycle to room-compiler?
 val L = "\$L"
@@ -28,9 +37,5 @@
 val S = "\$S"
 val W = "\$W"
 
-internal fun JTypeName.toKTypeName(nullability: XNullability): KTypeName = this.toKTypeName().let {
-    when (nullability) {
-        XNullability.NULLABLE, XNullability.UNKNOWN -> it.copy(nullable = true)
-        XNullability.NONNULL -> it.copy(nullable = false)
-    }
-}
\ No newline at end of file
+// TODO(b/247247366): Temporary migration API, delete me plz!
+fun JClassName.toXClassName() = XClassName(this, this.toKClassName(), XNullability.NONNULL)
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt
index e6617fb..cafc0f0 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XAnnotationSpec.kt
@@ -18,8 +18,6 @@
 
 import androidx.room.compiler.codegen.java.JavaAnnotationSpec
 import androidx.room.compiler.codegen.kotlin.KotlinAnnotationSpec
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.toKClassName
 
 interface XAnnotationSpec : TargetLanguage {
 
@@ -41,13 +39,13 @@
     }
 
     companion object {
-        fun builder(language: CodeLanguage, className: JClassName): Builder {
+        fun builder(language: CodeLanguage, className: XClassName): Builder {
             return when (language) {
                 CodeLanguage.JAVA -> JavaAnnotationSpec.Builder(
-                    com.squareup.javapoet.AnnotationSpec.builder(className)
+                    com.squareup.javapoet.AnnotationSpec.builder(className.java)
                 )
                 CodeLanguage.KOTLIN -> KotlinAnnotationSpec.Builder(
-                    com.squareup.kotlinpoet.AnnotationSpec.builder(className.toKClassName())
+                    com.squareup.kotlinpoet.AnnotationSpec.builder(className.kotlin)
                 )
             }
         }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt
index 1e7da00..4b16c2b6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XCodeBlock.kt
@@ -18,7 +18,6 @@
 
 import androidx.room.compiler.codegen.java.JavaCodeBlock
 import androidx.room.compiler.codegen.kotlin.KotlinCodeBlock
-import com.squareup.kotlinpoet.javapoet.JTypeName
 
 interface XCodeBlock : TargetLanguage {
 
@@ -32,7 +31,7 @@
 
         fun addLocalVariable(
             name: String,
-            type: JTypeName,
+            type: XTypeName,
             isMutable: Boolean = false,
             assignExpr: XCodeBlock
         ): Builder
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt
index ff6be2c..898c8d1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XFunSpec.kt
@@ -20,20 +20,17 @@
 import androidx.room.compiler.codegen.java.toJavaVisibilityModifier
 import androidx.room.compiler.codegen.kotlin.KotlinFunSpec
 import androidx.room.compiler.codegen.kotlin.toKotlinVisibilityModifier
-import androidx.room.compiler.processing.XNullability
 import com.squareup.javapoet.MethodSpec
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
-import com.squareup.kotlinpoet.javapoet.JTypeName
 
 interface XFunSpec : TargetLanguage {
 
     interface Builder : TargetLanguage {
         // TODO(b/247247442): Maybe make a XParameterSpec ?
         fun addParameter(
-            typeName: JTypeName,
+            typeName: XTypeName,
             name: String,
-            nullability: XNullability,
             annotations: List<XAnnotationSpec> = emptyList()
         ): Builder
 
@@ -43,10 +40,7 @@
 
         fun callSuperConstructor(vararg args: XCodeBlock): Builder
 
-        fun returns(
-            typeName: JTypeName,
-            nullability: XNullability,
-        ): Builder
+        fun returns(typeName: XTypeName): Builder
 
         fun build(): XFunSpec
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
new file mode 100644
index 0000000..e792548
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.codegen
+
+import androidx.room.compiler.processing.XNullability
+import com.squareup.kotlinpoet.DelicateKotlinPoetApi
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KClassName
+import com.squareup.kotlinpoet.javapoet.KTypeName
+import kotlin.reflect.KClass
+
+/**
+ * Represents a type name in Java and Kotlin's type system.
+ *
+ * It simply contains a [com.squareup.javapoet.TypeName] and a [com.squareup.kotlinpoet.TypeName].
+ * If the name comes from xprocessing APIs then the KotlinPoet name will default to 'Unavailable'
+ * if the processing backend is not KSP.
+ *
+ * @see [androidx.room.compiler.processing.XType.asTypeName]
+ */
+open class XTypeName protected constructor(
+    internal open val java: JTypeName,
+    internal open val kotlin: KTypeName,
+    internal val nullability: XNullability
+) {
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is XTypeName) return false
+        if (java != other.java) return false
+        if (kotlin != other.kotlin) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = java.hashCode()
+        result = 31 * result + kotlin.hashCode()
+        return result
+    }
+
+    override fun toString() = buildString {
+        append("XTypeName[")
+        append(java)
+        append(" / ")
+        if (kotlin != UNAVAILABLE_KTYPE_NAME) {
+            append(kotlin)
+        } else {
+            append("UNAVAILABLE")
+        }
+        append("]")
+    }
+
+    companion object {
+        /**
+         * The default [KTypeName] returned by xprocessing APIs when the backend is not KSP.
+         */
+        internal val UNAVAILABLE_KTYPE_NAME =
+            KClassName("androidx.room.compiler.codegen", "Unavailable")
+
+        operator fun invoke(
+            java: JTypeName,
+            kotlin: KTypeName,
+            nullability: XNullability
+        ): XTypeName {
+            return XTypeName(java, kotlin, nullability)
+        }
+    }
+}
+
+/**
+ * Represents a fully-qualified class name.
+ *
+ * It simply contains a [com.squareup.javapoet.ClassName] and a [com.squareup.kotlinpoet.ClassName].
+ *
+ * @see [androidx.room.compiler.processing.XTypeElement.asClassName]
+ */
+class XClassName internal constructor(
+    override val java: JClassName,
+    override val kotlin: KClassName,
+    nullability: XNullability
+) : XTypeName(java, kotlin, nullability) {
+
+    // TODO(b/248000692): Using the JClassName as source of truth. This is wrong since we need to
+    //  handle Kotlin interop types for KotlinPoet, i.e. java.lang.String to kotlin.String.
+    //  But a decision has to be made...
+    val packageName: String = java.packageName()
+    val simpleNames: List<String> = java.simpleNames()
+
+    fun copy(nullable: Boolean): XClassName {
+        return XClassName(
+            java = java,
+            kotlin = kotlin.copy(nullable = nullable) as KClassName,
+            nullability = if (nullable) XNullability.NULLABLE else XNullability.NONNULL
+        )
+    }
+
+    companion object {
+        fun get(
+            packageName: String,
+            vararg names: String
+        ): XClassName {
+            return XClassName(
+                java = JClassName.get(packageName, names.first(), *names.drop(1).toTypedArray()),
+                kotlin = KClassName(packageName, *names),
+                nullability = XNullability.NONNULL
+            )
+        }
+    }
+}
+
+@OptIn(DelicateKotlinPoetApi::class)
+fun Class<*>.asClassName(): XClassName {
+    return XClassName(
+        java = JClassName.get(this),
+        kotlin = this.asClassName(),
+        nullability = XNullability.NONNULL
+    )
+}
+
+fun KClass<*>.asClassName(): XClassName {
+    return XClassName(
+        java = JClassName.get(this.java),
+        kotlin = this.asClassName(),
+        nullability = XNullability.NONNULL
+    )
+}
+
+fun XTypeName.toJavaPoet() = this.java
+fun XClassName.toJavaPoet() = this.java
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt
index e1799c5..506d1c6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeSpec.kt
@@ -19,25 +19,20 @@
 import androidx.room.compiler.codegen.java.JavaTypeSpec
 import androidx.room.compiler.codegen.kotlin.KotlinTypeSpec
 import androidx.room.compiler.processing.XElement
-import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.addOriginatingElement
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.toKClassName
 
 interface XTypeSpec : TargetLanguage {
 
-    val className: JClassName
+    val className: XClassName
 
     interface Builder : TargetLanguage {
-        fun superclass(typeName: JTypeName): Builder
+        fun superclass(typeName: XTypeName): Builder
         fun addAnnotation(annotation: XAnnotationSpec)
 
         // TODO(b/247241418): Maybe make a XPropertySpec ?
         fun addProperty(
-            typeName: JTypeName,
+            typeName: XTypeName,
             name: String,
-            nullability: XNullability,
             visibility: VisibilityModifier,
             isMutable: Boolean = false,
             initExpr: XCodeBlock? = null,
@@ -67,15 +62,15 @@
     }
 
     companion object {
-        fun classBuilder(language: CodeLanguage, className: JClassName): Builder {
+        fun classBuilder(language: CodeLanguage, className: XClassName): Builder {
             return when (language) {
                 CodeLanguage.JAVA -> JavaTypeSpec.Builder(
                     className = className,
-                    actual = com.squareup.javapoet.TypeSpec.classBuilder(className)
+                    actual = com.squareup.javapoet.TypeSpec.classBuilder(className.java)
                 )
                 CodeLanguage.KOTLIN -> KotlinTypeSpec.Builder(
                     className = className,
-                    actual = com.squareup.kotlinpoet.TypeSpec.classBuilder(className.toKClassName())
+                    actual = com.squareup.kotlinpoet.TypeSpec.classBuilder(className.kotlin)
                 )
             }
         }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/ClassNames.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/ClassNames.kt
similarity index 94%
rename from room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/ClassNames.kt
rename to room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/ClassNames.kt
index 465ea86..b4637ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/ClassNames.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/ClassNames.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.compiler.codegen
+package androidx.room.compiler.codegen.java
 
 import com.squareup.javapoet.ClassName
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt
index 06e915a..2a3e25f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaAnnotationSpec.kt
@@ -16,15 +16,17 @@
 
 package androidx.room.compiler.codegen.java
 
+import androidx.room.compiler.codegen.JAnnotationSpecBuilder
 import androidx.room.compiler.codegen.XAnnotationSpec
 import androidx.room.compiler.codegen.XCodeBlock
+import com.squareup.kotlinpoet.javapoet.JAnnotationSpec
 
 internal class JavaAnnotationSpec(
-    internal val actual: com.squareup.javapoet.AnnotationSpec
+    internal val actual: JAnnotationSpec
 ) : JavaLang(), XAnnotationSpec {
 
     internal class Builder(
-        internal val actual: com.squareup.javapoet.AnnotationSpec.Builder
+        internal val actual: JAnnotationSpecBuilder
     ) : JavaLang(), XAnnotationSpec.Builder {
         override fun addMember(name: String, code: XCodeBlock) = apply {
             require(code is JavaCodeBlock)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt
index 6bc983b..a5e9cff 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaCodeBlock.kt
@@ -17,18 +17,19 @@
 package androidx.room.compiler.codegen.java
 
 import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.JCodeBlock
 import androidx.room.compiler.codegen.TargetLanguage
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
-import com.squareup.kotlinpoet.javapoet.JTypeName
 
 internal class JavaCodeBlock(
-    internal val actual: com.squareup.javapoet.CodeBlock
+    internal val actual: JCodeBlock
 ) : JavaLang(), XCodeBlock {
 
     internal class Builder : JavaLang(), XCodeBlock.Builder {
-        internal val actual = com.squareup.javapoet.CodeBlock.builder()
+        internal val actual = JCodeBlock.builder()
 
         override fun add(code: XCodeBlock) = apply {
             require(code is JavaCodeBlock)
@@ -49,7 +50,7 @@
 
         override fun addLocalVariable(
             name: String,
-            type: JTypeName,
+            type: XTypeName,
             isMutable: Boolean,
             assignExpr: XCodeBlock
         ) = apply {
@@ -78,6 +79,7 @@
                     check(arg.language == CodeLanguage.JAVA) { "$arg is not JavaCode" }
                 }
                 when (arg) {
+                    is XTypeName -> arg.java
                     is XTypeSpec -> (arg as JavaTypeSpec).actual
                     is XFunSpec -> (arg as JavaFunSpec).actual
                     is XCodeBlock -> (arg as JavaCodeBlock).actual
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt
index ae0bc2d..a8113df 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaFunSpec.kt
@@ -17,12 +17,11 @@
 package androidx.room.compiler.codegen.java
 
 import androidx.room.compiler.codegen.L
-import androidx.room.compiler.codegen.NONNULL_ANNOTATION
-import androidx.room.compiler.codegen.NULLABLE_ANNOTATION
 import androidx.room.compiler.codegen.VisibilityModifier
 import androidx.room.compiler.codegen.XAnnotationSpec
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.KnownTypeNames.KOTLIN_UNIT
 import androidx.room.compiler.processing.XNullability
 import com.squareup.javapoet.CodeBlock
@@ -36,7 +35,7 @@
 ) : JavaLang(), XFunSpec {
 
     internal class Builder(
-        internal val actual: com.squareup.javapoet.MethodSpec.Builder
+        internal val actual: MethodSpec.Builder
     ) : JavaLang(), XFunSpec.Builder {
 
         override fun addCode(code: XCodeBlock) = apply {
@@ -45,17 +44,16 @@
         }
 
         override fun addParameter(
-            typeName: JTypeName,
+            typeName: XTypeName,
             name: String,
-            nullability: XNullability,
             annotations: List<XAnnotationSpec>
         ) = apply {
             actual.addParameter(
-                ParameterSpec.builder(typeName, name, Modifier.FINAL)
+                ParameterSpec.builder(typeName.java, name, Modifier.FINAL)
                     .apply {
-                        if (nullability == XNullability.NULLABLE) {
+                        if (typeName.nullability == XNullability.NULLABLE) {
                             addAnnotation(NULLABLE_ANNOTATION)
-                        } else if (nullability == XNullability.NONNULL) {
+                        } else if (typeName.nullability == XNullability.NONNULL) {
                             addAnnotation(NONNULL_ANNOTATION)
                         }
                     }.build()
@@ -76,19 +74,19 @@
             )
         }
 
-        override fun returns(typeName: JTypeName, nullability: XNullability) = apply {
-            if (typeName == com.squareup.javapoet.TypeName.VOID || typeName == KOTLIN_UNIT) {
+        override fun returns(typeName: XTypeName) = apply {
+            if (typeName.java == JTypeName.VOID || typeName.java == KOTLIN_UNIT) {
                 return@apply
             }
             // TODO(b/247242374) Add nullability annotations for non-private methods
             if (!actual.modifiers.contains(Modifier.PRIVATE)) {
-                if (nullability == XNullability.NULLABLE) {
+                if (typeName.nullability == XNullability.NULLABLE) {
                     actual.addAnnotation(NULLABLE_ANNOTATION)
-                } else if (nullability == XNullability.NONNULL) {
+                } else if (typeName.nullability == XNullability.NONNULL) {
                     actual.addAnnotation(NONNULL_ANNOTATION)
                 }
             }
-            actual.returns(typeName)
+            actual.returns(typeName.java)
         }
 
         override fun build() = JavaFunSpec(actual.build())
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt
index 0dd5016..952ef06 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/java/JavaTypeSpec.kt
@@ -16,30 +16,30 @@
 
 package androidx.room.compiler.codegen.java
 
-import androidx.room.compiler.codegen.NONNULL_ANNOTATION
-import androidx.room.compiler.codegen.NULLABLE_ANNOTATION
+import androidx.room.compiler.codegen.JTypeSpecBuilder
 import androidx.room.compiler.codegen.VisibilityModifier
 import androidx.room.compiler.codegen.XAnnotationSpec
+import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.processing.XNullability
 import com.squareup.javapoet.FieldSpec
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeSpec
 import javax.lang.model.element.Modifier
 
 internal class JavaTypeSpec(
-    override val className: JClassName,
-    internal val actual: com.squareup.javapoet.TypeSpec
+    override val className: XClassName,
+    internal val actual: JTypeSpec
 ) : JavaLang(), XTypeSpec {
 
     internal class Builder(
-        private val className: JClassName,
-        internal val actual: com.squareup.javapoet.TypeSpec.Builder
+        private val className: XClassName,
+        internal val actual: JTypeSpecBuilder
     ) : JavaLang(), XTypeSpec.Builder {
-        override fun superclass(typeName: JTypeName) = apply {
-            actual.superclass(typeName)
+        override fun superclass(typeName: XTypeName) = apply {
+            actual.superclass(typeName.java)
         }
 
         override fun addAnnotation(annotation: XAnnotationSpec) {
@@ -48,22 +48,21 @@
         }
 
         override fun addProperty(
-            typeName: JTypeName,
+            typeName: XTypeName,
             name: String,
-            nullability: XNullability,
             visibility: VisibilityModifier,
             isMutable: Boolean,
             initExpr: XCodeBlock?,
             annotations: List<XAnnotationSpec>
         ) = apply {
             actual.addField(
-                FieldSpec.builder(typeName, name).apply {
+                FieldSpec.builder(typeName.java, name).apply {
                     val visibilityModifier = visibility.toJavaVisibilityModifier()
                     // TODO(b/247242374) Add nullability annotations for non-private fields
                     if (visibilityModifier != Modifier.PRIVATE) {
-                        if (nullability == XNullability.NULLABLE) {
+                        if (typeName.nullability == XNullability.NULLABLE) {
                             addAnnotation(NULLABLE_ANNOTATION)
-                        } else if (nullability == XNullability.NONNULL) {
+                        } else if (typeName.nullability == XNullability.NONNULL) {
                             addAnnotation(NONNULL_ANNOTATION)
                         }
                     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt
index cdec2ad2..50222ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinAnnotationSpec.kt
@@ -16,16 +16,18 @@
 
 package androidx.room.compiler.codegen.kotlin
 
+import androidx.room.compiler.codegen.KAnnotationSpecBuilder
 import androidx.room.compiler.codegen.XAnnotationSpec
 import androidx.room.compiler.codegen.XCodeBlock
 import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.javapoet.KAnnotationSpec
 
 internal class KotlinAnnotationSpec(
-    internal val actual: com.squareup.kotlinpoet.AnnotationSpec
+    internal val actual: KAnnotationSpec
 ) : KotlinLang(), XAnnotationSpec {
 
     internal class Builder(
-        internal val actual: com.squareup.kotlinpoet.AnnotationSpec.Builder
+        internal val actual: KAnnotationSpecBuilder
     ) : KotlinLang(), XAnnotationSpec.Builder {
         override fun addMember(name: String, code: XCodeBlock) = apply {
             require(code is KotlinCodeBlock)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt
index 1ccc851..ede67d2 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinCodeBlock.kt
@@ -17,20 +17,21 @@
 package androidx.room.compiler.codegen.kotlin
 
 import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.KCodeBlock
+import androidx.room.compiler.codegen.KCodeBlockBuilder
 import androidx.room.compiler.codegen.TargetLanguage
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.toKTypeName
 
 internal class KotlinCodeBlock(
-    internal val actual: com.squareup.kotlinpoet.CodeBlock
+    internal val actual: KCodeBlock
 ) : KotlinLang(), XCodeBlock {
 
     internal class Builder : KotlinLang(), XCodeBlock.Builder {
 
-        internal val actual = com.squareup.kotlinpoet.CodeBlock.Builder()
+        internal val actual = KCodeBlockBuilder()
 
         override fun add(code: XCodeBlock) = apply {
             require(code is KotlinCodeBlock)
@@ -51,7 +52,7 @@
 
         override fun addLocalVariable(
             name: String,
-            type: JTypeName,
+            type: XTypeName,
             isMutable: Boolean,
             assignExpr: XCodeBlock
         ) = apply {
@@ -59,7 +60,7 @@
             val varOrVal = if (isMutable) "var" else "val"
             actual.addStatement(
                 "$varOrVal %L: %T = %L",
-                type.toKTypeName(),
+                type.kotlin,
                 name,
                 assignExpr.actual
             )
@@ -74,13 +75,11 @@
         private fun processArgs(args: Array<out Any?>): Array<Any?> {
             return Array(args.size) { index ->
                 val arg = args[index]
-                if (arg is JTypeName) {
-                    return@Array arg.toKTypeName()
-                }
                 if (arg is TargetLanguage) {
                     check(arg.language == CodeLanguage.KOTLIN) { "$arg is not KotlinCode" }
                 }
                 when (arg) {
+                    is XTypeName -> arg.kotlin
                     is XTypeSpec -> (arg as KotlinTypeSpec).actual
                     is XFunSpec -> (arg as KotlinFunSpec).actual
                     is XCodeBlock -> (arg as KotlinCodeBlock).actual
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt
index 0faf0ab..fdcb73f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinFunSpec.kt
@@ -20,20 +20,18 @@
 import androidx.room.compiler.codegen.XAnnotationSpec
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
-import androidx.room.compiler.codegen.toKTypeName
-import androidx.room.compiler.processing.KnownTypeNames.KOTLIN_UNIT
-import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.codegen.XTypeName
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.ParameterSpec
-import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.UNIT
 
 internal class KotlinFunSpec(
     internal val actual: FunSpec
 ) : KotlinLang(), XFunSpec {
 
     internal class Builder(
-        internal val actual: com.squareup.kotlinpoet.FunSpec.Builder
+        internal val actual: FunSpec.Builder
     ) : KotlinLang(), XFunSpec.Builder {
 
         override fun addCode(code: XCodeBlock) = apply {
@@ -42,13 +40,12 @@
         }
 
         override fun addParameter(
-            typeName: JTypeName,
+            typeName: XTypeName,
             name: String,
-            nullability: XNullability,
             annotations: List<XAnnotationSpec>
         ) = apply {
             actual.addParameter(
-                ParameterSpec.builder(name, typeName.toKTypeName(nullability)).apply {
+                ParameterSpec.builder(name, typeName.kotlin).apply {
                     // TODO(b/247247439): Add other annotations
                 }.build()
             )
@@ -63,11 +60,11 @@
             )
         }
 
-        override fun returns(typeName: JTypeName, nullability: XNullability) = apply {
-            if (typeName == com.squareup.javapoet.TypeName.VOID || typeName == KOTLIN_UNIT) {
+        override fun returns(typeName: XTypeName) = apply {
+            if (typeName.kotlin == UNIT) {
                 return@apply
             }
-            actual.returns(typeName.toKTypeName(nullability))
+            actual.returns(typeName.kotlin)
         }
 
         override fun build() = KotlinFunSpec(actual.build())
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt
index 4223657..da898fc 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/kotlin/KotlinTypeSpec.kt
@@ -16,29 +16,28 @@
 
 package androidx.room.compiler.codegen.kotlin
 
+import androidx.room.compiler.codegen.KTypeSpecBuilder
 import androidx.room.compiler.codegen.VisibilityModifier
 import androidx.room.compiler.codegen.XAnnotationSpec
+import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.codegen.toKTypeName
-import androidx.room.compiler.processing.XNullability
 import com.squareup.kotlinpoet.PropertySpec
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.toKTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeSpec
 
 internal class KotlinTypeSpec(
-    override val className: JClassName,
-    internal val actual: com.squareup.kotlinpoet.TypeSpec
+    override val className: XClassName,
+    internal val actual: KTypeSpec
 ) : KotlinLang(), XTypeSpec {
 
     internal class Builder(
-        private val className: JClassName,
-        internal val actual: com.squareup.kotlinpoet.TypeSpec.Builder
+        private val className: XClassName,
+        internal val actual: KTypeSpecBuilder
     ) : KotlinLang(), XTypeSpec.Builder {
-        override fun superclass(typeName: JTypeName) = apply {
-            actual.superclass(typeName.toKTypeName())
+        override fun superclass(typeName: XTypeName) = apply {
+            actual.superclass(typeName.kotlin)
         }
 
         override fun addAnnotation(annotation: XAnnotationSpec) {
@@ -47,16 +46,15 @@
         }
 
         override fun addProperty(
-            typeName: JTypeName,
+            typeName: XTypeName,
             name: String,
-            nullability: XNullability,
             visibility: VisibilityModifier,
             isMutable: Boolean,
             initExpr: XCodeBlock?,
             annotations: List<XAnnotationSpec>
         ) = apply {
             actual.addProperty(
-                PropertySpec.builder(name, typeName.toKTypeName(nullability)).apply {
+                PropertySpec.builder(name, typeName.kotlin).apply {
                     mutable(isMutable)
                     addModifiers(visibility.toKotlinVisibilityModifier())
                     initExpr?.let {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
index 95b43d7..ac25598 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
@@ -15,14 +15,14 @@
  */
 package androidx.room.compiler.processing
 
-import java.lang.Character.isISOControl
 import com.squareup.javapoet.AnnotationSpec
-import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.MethodSpec
 import com.squareup.javapoet.ParameterSpec
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
 import com.squareup.javapoet.TypeSpec
+import com.squareup.kotlinpoet.javapoet.JClassName
+import java.lang.Character.isISOControl
 import javax.lang.model.SourceVersion
 import javax.lang.model.element.Modifier
 import javax.lang.model.type.TypeKind
@@ -37,7 +37,8 @@
  *
  * We should still strive to avoid these cases, maybe turn it to an error in tests.
  */
-private val NONE_TYPE_NAME = ClassName.get("androidx.room.compiler.processing.error", "NotAType")
+internal val JAVA_NONE_TYPE_NAME: JClassName =
+    JClassName.get("androidx.room.compiler.processing.error", "NotAType")
 
 fun XAnnotation.toAnnotationSpec(): AnnotationSpec {
   val builder = AnnotationSpec.builder(className)
@@ -82,7 +83,7 @@
 }
 
 internal fun TypeMirror.safeTypeName(): TypeName = if (kind == TypeKind.NONE) {
-    NONE_TYPE_NAME
+    JAVA_NONE_TYPE_NAME
 } else {
     TypeName.get(this)
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
index 02affec..a02f25d 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/KotlinPoetExt.kt
@@ -17,6 +17,10 @@
 package androidx.room.compiler.processing
 
 import com.squareup.kotlinpoet.OriginatingElementsHolder
+import com.squareup.kotlinpoet.javapoet.KClassName
+
+internal val KOTLIN_NONE_TYPE_NAME: KClassName =
+    KClassName("androidx.room.compiler.processing.error", "NotAType")
 
 /**
  * Adds the given element as an originating element for compilation.
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
index d5cc7f0..227ba42 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
@@ -53,17 +53,17 @@
 }
 
 fun XTypeSpec.writeTo(generator: XFiler, mode: XFiler.Mode = XFiler.Mode.Isolating) {
-    require(this.className.simpleNames().size == 1) { "XTypeSpec must be a top-level class." }
+    require(this.className.simpleNames.size == 1) { "XTypeSpec must be a top-level class." }
     when (this.language) {
         CodeLanguage.JAVA -> {
             check(this is JavaTypeSpec)
-            JavaFile.builder(this.className.packageName(), this.actual)
+            JavaFile.builder(this.className.packageName, this.actual)
                 .build()
                 .writeTo(generator, mode)
         }
         CodeLanguage.KOTLIN -> {
             check(this is KotlinTypeSpec)
-            FileSpec.builder(this.className.packageName(), this.className.simpleName())
+            FileSpec.builder(this.className.packageName, this.className.simpleNames.single())
                 .addType(this.actual)
                 .build()
                 .writeTo(generator, mode)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index 31f9522..5636707 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.codegen.XTypeName
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.TypeName
 import kotlin.contracts.contract
@@ -31,9 +32,22 @@
     /**
      * The Javapoet [TypeName] representation of the type
      */
+    // TODO(b/247248619): Deprecate when more progress is made, otherwise -werror fails the build.
+    // @Deprecated(
+    //     message = "Use asTypeName().toJavaPoet() to be clear the name is for JavaPoet.",
+    //     replaceWith = ReplaceWith(
+    //         expression = "asTypeName().toJavaPoet()",
+    //         imports = ["androidx.room.compiler.codegen.toJavaPoet"]
+    //     )
+    // )
     val typeName: TypeName
 
     /**
+     * Gets the [XTypeName] representing the type.
+     */
+    fun asTypeName(): XTypeName
+
+    /**
      * Returns the rawType of this type. (e.g. `List<String>` to `List`.
      */
     val rawType: XRawType
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index aa9d882..3b49f682 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.codegen.XClassName
 import com.squareup.javapoet.ClassName
 
 interface XTypeElement : XHasModifiers, XParameterizable, XElement, XMemberContainer {
@@ -64,9 +65,22 @@
     /**
      * Javapoet [ClassName] of the type.
      */
+    // TODO(b/247248619): Deprecate when more progress is made, otherwise -werror fails the build.
+    // @Deprecated(
+    //     message = "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+    //     replaceWith = ReplaceWith(
+    //         expression = "asClassName().toJavaPoet()",
+    //         imports = ["androidx.room.compiler.codegen.toJavaPoet"]
+    //     )
+    // )
     override val className: ClassName
 
     /**
+     * Gets the [XClassName] of the type element.
+     */
+    fun asClassName(): XClassName
+
+    /**
      * The [XTypeElement] that contains this [XTypeElement] if it is an inner class/interface.
      */
     val enclosingTypeElement: XTypeElement?
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
index 66c25ae..b9c3ce7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
@@ -16,6 +16,8 @@
 
 package androidx.room.compiler.processing.javac
 
+import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XArrayType
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
@@ -72,6 +74,16 @@
         arrayOf(typeMirror)
     }
 
+    private val xTypeName: XTypeName by lazy {
+        XTypeName(
+            java = JArrayTypeName.get(typeMirror),
+            kotlin = XTypeName.UNAVAILABLE_KTYPE_NAME,
+            nullability = knownComponentNullability ?: XNullability.UNKNOWN,
+        )
+    }
+
+    override fun asTypeName() = xTypeName
+
     override val typeArguments: List<XType>
         get() = emptyList()
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 3526dc96..1735ad4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -16,16 +16,16 @@
 
 package androidx.room.compiler.processing.javac
 
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XEquality
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.javac.kotlin.KmType
 import androidx.room.compiler.processing.javac.kotlin.KotlinMetadataElement
-import androidx.room.compiler.processing.ksp.ERROR_TYPE_NAME
+import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
 import androidx.room.compiler.processing.safeTypeName
 import com.google.auto.common.MoreTypes
-import java.lang.IllegalStateException
 import javax.lang.model.type.TypeKind
 import javax.lang.model.type.TypeMirror
 import kotlin.reflect.KClass
@@ -68,13 +68,23 @@
     override fun isError(): Boolean {
         return typeMirror.kind == TypeKind.ERROR ||
             // https://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction
-            (kotlinType != null && typeName == ERROR_TYPE_NAME)
+            (kotlinType != null && typeName == ERROR_JTYPE_NAME)
     }
 
     override val typeName by lazy {
-        typeMirror.safeTypeName()
+        xTypeName.java
     }
 
+    private val xTypeName: XTypeName by lazy {
+        XTypeName(
+            typeMirror.safeTypeName(),
+            XTypeName.UNAVAILABLE_KTYPE_NAME,
+            nullability
+        )
+    }
+
+    override fun asTypeName() = xTypeName
+
     override fun equals(other: Any?): Boolean {
         return XEquality.equals(this, other)
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index 0eb5474..403d6fb 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -16,12 +16,15 @@
 
 package androidx.room.compiler.processing.javac
 
+import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XEnumEntry
 import androidx.room.compiler.processing.XEnumTypeElement
 import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.XHasModifiers
-import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XMemberContainer
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.XTypeParameterElement
@@ -34,6 +37,7 @@
 import com.google.auto.common.MoreTypes
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JClassName
 import javax.lang.model.element.ElementKind
 import javax.lang.model.element.TypeElement
 import javax.lang.model.type.TypeKind
@@ -60,9 +64,18 @@
     }
 
     override val className: ClassName by lazy {
-        ClassName.get(element)
+        xClassName.java
     }
 
+    private val xClassName: XClassName by lazy {
+        XClassName(
+            JClassName.get(element),
+            XTypeName.UNAVAILABLE_KTYPE_NAME,
+            XNullability.NONNULL
+        )
+    }
+    override fun asClassName() = xClassName
+
     override val enclosingElement: XMemberContainer? by lazy {
         enclosingTypeElement
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt
index ac380bf..c6f3ec1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/DefaultKspType.kt
@@ -19,17 +19,23 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.tryBox
 import com.google.devtools.ksp.symbol.KSType
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
 
 internal class DefaultKspType(
     env: KspProcessingEnv,
     ksType: KSType,
     jvmTypeResolver: KspJvmTypeResolver?
 ) : KspType(env, ksType, jvmTypeResolver) {
-    override fun resolveTypeName(): TypeName {
+
+    override fun resolveJTypeName(): JTypeName {
         // always box these. For primitives, typeName might return the primitive type but if we
         // wanted it to be a primitive, we would've resolved it to [KspPrimitiveType].
-        return ksType.typeName(env.resolver).tryBox()
+        return ksType.asJTypeName(env.resolver).tryBox()
+    }
+
+    override fun resolveKTypeName(): KTypeName {
+        return ksType.asKTypeName(env.resolver)
     }
 
     override fun boxed(): DefaultKspType {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index b773e09..aa157d0 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -17,222 +17,14 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.room.compiler.processing.XNullability
-import androidx.room.compiler.processing.javac.kotlin.typeNameFromJvmSignature
-import androidx.room.compiler.processing.tryBox
-import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
-import com.google.devtools.ksp.KspExperimental
-import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.symbol.KSAnnotated
 import com.google.devtools.ksp.symbol.KSDeclaration
-import com.google.devtools.ksp.symbol.KSName
 import com.google.devtools.ksp.symbol.KSNode
 import com.google.devtools.ksp.symbol.KSType
-import com.google.devtools.ksp.symbol.KSTypeAlias
 import com.google.devtools.ksp.symbol.KSTypeArgument
 import com.google.devtools.ksp.symbol.KSTypeParameter
 import com.google.devtools.ksp.symbol.KSTypeReference
 import com.google.devtools.ksp.symbol.Modifier
-import com.google.devtools.ksp.symbol.Variance
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeVariableName
-import com.squareup.javapoet.WildcardTypeName
-import kotlin.coroutines.Continuation
-
-// Catch-all type name when we cannot resolve to anything. This is what KAPT uses as error type
-// and we use the same type in KSP for consistency.
-// https://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction
-internal val ERROR_TYPE_NAME = ClassName.get("error", "NonExistentClass")
-
-/**
- * To handle self referencing types and avoid infinite recursion, we keep a lookup map for
- * TypeVariables.
- */
-private typealias TypeArgumentTypeLookup = LinkedHashMap<KSName, TypeName>
-
-/**
- * Turns a KSTypeReference into a TypeName in java's type system.
- */
-internal fun KSTypeReference?.typeName(resolver: Resolver): TypeName =
-    typeName(
-        resolver = resolver,
-        typeArgumentTypeLookup = TypeArgumentTypeLookup()
-    )
-
-private fun KSTypeReference?.typeName(
-    resolver: Resolver,
-    typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
-    return if (this == null) {
-        ERROR_TYPE_NAME
-    } else {
-        resolve().typeName(resolver, typeArgumentTypeLookup)
-    }
-}
-
-/**
- * Turns a KSDeclaration into a TypeName in java's type system.
- */
-internal fun KSDeclaration.typeName(resolver: Resolver): TypeName =
-    typeName(
-        resolver = resolver,
-        typeArgumentTypeLookup = TypeArgumentTypeLookup()
-    )
-
-@OptIn(KspExperimental::class)
-private fun KSDeclaration.typeName(
-    resolver: Resolver,
-    typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
-    if (this is KSTypeAlias) {
-        return this.type.typeName(resolver, typeArgumentTypeLookup)
-    }
-    if (this is KSTypeParameter) {
-        return this.typeName(resolver, typeArgumentTypeLookup)
-    }
-    // if there is no qualified name, it is a resolution error so just return shared instance
-    // KSP may improve that later and if not, we can improve it in Room
-    // TODO: https://issuetracker.google.com/issues/168639183
-    val qualified = qualifiedName?.asString() ?: return ERROR_TYPE_NAME
-    val jvmSignature = resolver.mapToJvmSignature(this)
-    if (jvmSignature != null && jvmSignature.isNotBlank()) {
-        return jvmSignature.typeNameFromJvmSignature()
-    }
-
-    // fallback to custom generation, it is very likely that this is an unresolved type
-    // get the package name first, it might throw for invalid types, hence we use
-    // safeGetPackageName
-    val pkg = getNormalizedPackageName()
-    // using qualified name and pkg, figure out the short names.
-    val shortNames = if (pkg == "") {
-        qualified
-    } else {
-        qualified.substring(pkg.length + 1)
-    }.split('.')
-    return ClassName.get(pkg, shortNames.first(), *(shortNames.drop(1).toTypedArray()))
-}
-
-/**
- * Turns a KSTypeArgument into a TypeName in java's type system.
- */
-internal fun KSTypeArgument.typeName(
-    resolver: Resolver
-): TypeName = typeName(
-    resolver = resolver,
-    typeArgumentTypeLookup = TypeArgumentTypeLookup()
-)
-
-private fun KSTypeParameter.typeName(
-    resolver: Resolver,
-    typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
-    // see https://github.com/square/javapoet/issues/842
-    typeArgumentTypeLookup[name]?.let {
-        return it
-    }
-    val mutableBounds = mutableListOf<TypeName>()
-    val typeName = createModifiableTypeVariableName(name = name.asString(), bounds = mutableBounds)
-    typeArgumentTypeLookup[name] = typeName
-    val resolvedBounds = bounds.map {
-        it.typeName(resolver, typeArgumentTypeLookup).tryBox()
-    }.toList()
-    if (resolvedBounds.isNotEmpty()) {
-        mutableBounds.addAll(resolvedBounds)
-        mutableBounds.remove(TypeName.OBJECT)
-    }
-    typeArgumentTypeLookup.remove(name)
-    return typeName
-}
-
-private fun KSTypeArgument.typeName(
-    resolver: Resolver,
-    typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
-    fun resolveTypeName() = type.typeName(resolver, typeArgumentTypeLookup).tryBox()
-    return when (variance) {
-        Variance.CONTRAVARIANT -> WildcardTypeName.supertypeOf(resolveTypeName())
-        Variance.COVARIANT -> WildcardTypeName.subtypeOf(resolveTypeName())
-        Variance.STAR -> {
-            WildcardTypeName.subtypeOf(TypeName.OBJECT)
-        }
-        else -> {
-            if (hasJvmWildcardAnnotation()) {
-                WildcardTypeName.subtypeOf(resolveTypeName())
-            } else {
-                resolveTypeName()
-            }
-        }
-    }
-}
-
-/**
- * Turns a KSType into a TypeName in java's type system.
- */
-internal fun KSType.typeName(resolver: Resolver): TypeName =
-    typeName(
-        resolver = resolver,
-        typeArgumentTypeLookup = TypeArgumentTypeLookup()
-    )
-
-private fun KSType.typeName(
-    resolver: Resolver,
-    typeArgumentTypeLookup: TypeArgumentTypeLookup
-): TypeName {
-    return if (this.arguments.isNotEmpty() && !isRaw()) {
-        val args: Array<TypeName> = this.arguments
-            .map { typeArg ->
-                typeArg.typeName(
-                    resolver = resolver,
-                    typeArgumentTypeLookup = typeArgumentTypeLookup
-                )
-            }
-            .map { it.tryBox() }
-            .let { args ->
-                if (this.isSuspendFunctionType) args.convertToSuspendSignature()
-                else args
-            }
-            .toTypedArray()
-
-        when (
-            val typeName = declaration
-                .typeName(resolver, typeArgumentTypeLookup).tryBox()
-        ) {
-            is ArrayTypeName -> ArrayTypeName.of(args.single())
-            is ClassName -> ParameterizedTypeName.get(
-                typeName,
-                *args
-            )
-            else -> error("Unexpected type name for KSType: $typeName")
-        }
-    } else {
-        this.declaration.typeName(resolver, typeArgumentTypeLookup)
-    }
-}
-
-/**
- * Transforms [this] list of arguments to a suspend signature. For a [suspend] functional type, we
- * need to transform it to be a FunctionX with a [Continuation] with the correct return type. A
- * transformed SuspendFunction looks like this:
- *
- * FunctionX<[? super $params], ? super Continuation<? super $ReturnType>, ?>
- */
-private fun List<TypeName>.convertToSuspendSignature(): List<TypeName> {
-    val args = this
-
-    // The last arg is the return type, so take everything except the last arg
-    val actualArgs = args.subList(0, args.size - 1)
-    val continuationReturnType = WildcardTypeName.supertypeOf(args.last())
-    val continuationType = ParameterizedTypeName.get(
-        ClassName.get(Continuation::class.java),
-        continuationReturnType
-    )
-    return actualArgs + listOf(
-        WildcardTypeName.supertypeOf(continuationType),
-        WildcardTypeName.subtypeOf(TypeName.OBJECT)
-    )
-}
 
 /**
  * Root package comes as <root> instead of "" so we work around it here.
@@ -265,70 +57,26 @@
     else -> throw IllegalArgumentException("Cannot set KSType nullability to platform")
 }
 
-/**
- * The private constructor of [TypeVariableName] which receives a list.
- * We use this in [createModifiableTypeVariableName] to create a [TypeVariableName] whose bounds
- * can be modified afterwards.
- */
-private val typeVarNameConstructor by lazy {
-    try {
-        TypeVariableName::class.java.getDeclaredConstructor(
-            String::class.java,
-            List::class.java
-        ).also {
-            it.trySetAccessible()
-        }
-    } catch (ex: NoSuchMethodException) {
-        throw IllegalStateException(
-            """
-            Room couldn't find the constructor it is looking for in JavaPoet.
-            Please file a bug at $ISSUE_TRACKER_LINK.
-            """.trimIndent(),
-            ex
-        )
-    }
-}
-
-/**
- * Creates a TypeVariableName where we can change the bounds after constructor.
- * This is used to workaround a case for self referencing type declarations.
- * see b/187572913 for more details
- */
-private fun createModifiableTypeVariableName(
-    name: String,
-    bounds: List<TypeName>
-): TypeVariableName = typeVarNameConstructor.newInstance(
-    name,
-    bounds
-) as TypeVariableName
-
 private fun KSAnnotated.hasAnnotation(
-    qName: String
-) = annotations.any {
-    it.annotationType.resolve().declaration.qualifiedName?.asString() == qName
-}
+     qName: String
+ ) = annotations.any {
+     it.annotationType.resolve().declaration.qualifiedName?.asString() == qName
+ }
 
-internal fun KSAnnotated.hasJvmWildcardAnnotation() = hasAnnotation(
-    JvmWildcard::class.java.canonicalName
-)
+ internal fun KSAnnotated.hasJvmWildcardAnnotation() = hasAnnotation(
+     JvmWildcard::class.java.canonicalName!!
+ )
 
-internal fun KSAnnotated.hasSuppressJvmWildcardAnnotation() = hasAnnotation(
-    JvmSuppressWildcards::class.java.canonicalName
-)
+ internal fun KSAnnotated.hasSuppressJvmWildcardAnnotation() = hasAnnotation(
+     JvmSuppressWildcards::class.java.canonicalName!!
+ )
 
-internal fun KSNode.hasSuppressWildcardsAnnotationInHierarchy(): Boolean {
-    (this as? KSAnnotated)?.let {
-        if (hasSuppressJvmWildcardAnnotation()) {
-            return true
-        }
-    }
-    val parent = parent ?: return false
-    return parent.hasSuppressWildcardsAnnotationInHierarchy()
-}
-
-internal fun KSType.isRaw(): Boolean {
-    // yes this is gross but KSP itself seems to be doing it as well
-    // https://github.com/google/ksp/blob/main/compiler-plugin/
-    // src/main/kotlin/com/google/devtools/ksp/symbol/impl/kotlin/KSTypeImpl.kt#L85
-    return toString().startsWith("raw ")
-}
\ No newline at end of file
+ internal fun KSNode.hasSuppressWildcardsAnnotationInHierarchy(): Boolean {
+     (this as? KSAnnotated)?.let {
+         if (hasSuppressJvmWildcardAnnotation()) {
+             return true
+         }
+     }
+     val parent = parent ?: return false
+     return parent.hasSuppressWildcardsAnnotationInHierarchy()
+ }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt
new file mode 100644
index 0000000..6150713
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.ksp
+
+import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.processing.javac.kotlin.typeNameFromJvmSignature
+import androidx.room.compiler.processing.tryBox
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
+import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSName
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Variance
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeVariableName
+import com.squareup.kotlinpoet.javapoet.JWildcardTypeName
+import kotlin.coroutines.Continuation
+
+// Catch-all type name when we cannot resolve to anything. This is what KAPT uses as error type
+// and we use the same type in KSP for consistency.
+// https://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction
+internal val ERROR_JTYPE_NAME = JClassName.get("error", "NonExistentClass")
+
+/**
+ * To handle self referencing types and avoid infinite recursion, we keep a lookup map for
+ * TypeVariables.
+ */
+private typealias JTypeArgumentTypeLookup = LinkedHashMap<KSName, JTypeName>
+
+/**
+ * Turns a KSTypeReference into a TypeName in java's type system.
+ */
+internal fun KSTypeReference?.asJTypeName(resolver: Resolver): JTypeName =
+    asJTypeName(
+        resolver = resolver,
+        typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+    )
+
+private fun KSTypeReference?.asJTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+    return if (this == null) {
+        ERROR_JTYPE_NAME
+    } else {
+        resolve().asJTypeName(resolver, typeArgumentTypeLookup)
+    }
+}
+
+/**
+ * Turns a KSDeclaration into a TypeName in java's type system.
+ */
+internal fun KSDeclaration.asJTypeName(resolver: Resolver): JTypeName =
+    asJTypeName(
+        resolver = resolver,
+        typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+    )
+
+@OptIn(KspExperimental::class)
+private fun KSDeclaration.asJTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+    if (this is KSTypeAlias) {
+        return this.type.asJTypeName(resolver, typeArgumentTypeLookup)
+    }
+    if (this is KSTypeParameter) {
+        return this.asJTypeName(resolver, typeArgumentTypeLookup)
+    }
+    // if there is no qualified name, it is a resolution error so just return shared instance
+    // KSP may improve that later and if not, we can improve it in Room
+    // TODO: https://issuetracker.google.com/issues/168639183
+    val qualified = qualifiedName?.asString() ?: return ERROR_JTYPE_NAME
+    val jvmSignature = resolver.mapToJvmSignature(this)
+    if (jvmSignature != null && jvmSignature.isNotBlank()) {
+        return jvmSignature.typeNameFromJvmSignature()
+    }
+
+    // fallback to custom generation, it is very likely that this is an unresolved type
+    // get the package name first, it might throw for invalid types, hence we use
+    // safeGetPackageName
+    val pkg = getNormalizedPackageName()
+    // using qualified name and pkg, figure out the short names.
+    val shortNames = if (pkg == "") {
+        qualified
+    } else {
+        qualified.substring(pkg.length + 1)
+    }.split('.')
+    return JClassName.get(pkg, shortNames.first(), *(shortNames.drop(1).toTypedArray()))
+}
+
+/**
+ * Turns a KSTypeArgument into a TypeName in java's type system.
+ */
+internal fun KSTypeArgument.asJTypeName(
+    resolver: Resolver
+): JTypeName = asJTypeName(
+    resolver = resolver,
+    typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+)
+
+private fun KSTypeParameter.asJTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+    // see https://github.com/square/javapoet/issues/842
+    typeArgumentTypeLookup[name]?.let {
+        return it
+    }
+    val mutableBounds = mutableListOf<JTypeName>()
+    val typeName = createModifiableTypeVariableName(name = name.asString(), bounds = mutableBounds)
+    typeArgumentTypeLookup[name] = typeName
+    val resolvedBounds = bounds.map {
+        it.asJTypeName(resolver, typeArgumentTypeLookup).tryBox()
+    }.toList()
+    if (resolvedBounds.isNotEmpty()) {
+        mutableBounds.addAll(resolvedBounds)
+        mutableBounds.remove(JTypeName.OBJECT)
+    }
+    typeArgumentTypeLookup.remove(name)
+    return typeName
+}
+
+private fun KSTypeArgument.asJTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+    fun resolveTypeName() = type.asJTypeName(resolver, typeArgumentTypeLookup).tryBox()
+    return when (variance) {
+        Variance.CONTRAVARIANT -> JWildcardTypeName.supertypeOf(resolveTypeName())
+        Variance.COVARIANT -> JWildcardTypeName.subtypeOf(resolveTypeName())
+        Variance.STAR -> {
+            JWildcardTypeName.subtypeOf(JTypeName.OBJECT)
+        }
+        else -> {
+            if (hasJvmWildcardAnnotation()) {
+                JWildcardTypeName.subtypeOf(resolveTypeName())
+            } else {
+                resolveTypeName()
+            }
+        }
+    }
+}
+
+/**
+ * Turns a KSType into a TypeName in java's type system.
+ */
+internal fun KSType.asJTypeName(resolver: Resolver): JTypeName =
+    asJTypeName(
+        resolver = resolver,
+        typeArgumentTypeLookup = JTypeArgumentTypeLookup()
+    )
+
+@OptIn(KspExperimental::class)
+private fun KSType.asJTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: JTypeArgumentTypeLookup
+): JTypeName {
+    return if (this.arguments.isNotEmpty() && !resolver.isJavaRawType(this)) {
+        val args: Array<JTypeName> = this.arguments
+            .map { typeArg ->
+                typeArg.asJTypeName(
+                    resolver = resolver,
+                    typeArgumentTypeLookup = typeArgumentTypeLookup
+                )
+            }
+            .map { it.tryBox() }
+            .let { args ->
+                if (this.isSuspendFunctionType) args.convertToSuspendSignature()
+                else args
+            }
+            .toTypedArray()
+
+        when (
+            val typeName = declaration
+                .asJTypeName(resolver, typeArgumentTypeLookup).tryBox()
+        ) {
+            is JArrayTypeName -> JArrayTypeName.of(args.single())
+            is JClassName -> JParameterizedTypeName.get(
+                typeName,
+                *args
+            )
+            else -> error("Unexpected type name for KSType: $typeName")
+        }
+    } else {
+        this.declaration.asJTypeName(resolver, typeArgumentTypeLookup)
+    }
+}
+
+/**
+ * Transforms [this] list of arguments to a suspend signature. For a [suspend] functional type, we
+ * need to transform it to be a FunctionX with a [Continuation] with the correct return type. A
+ * transformed SuspendFunction looks like this:
+ *
+ * FunctionX<[? super $params], ? super Continuation<? super $ReturnType>, ?>
+ */
+private fun List<JTypeName>.convertToSuspendSignature(): List<JTypeName> {
+    val args = this
+
+    // The last arg is the return type, so take everything except the last arg
+    val actualArgs = args.subList(0, args.size - 1)
+    val continuationReturnType = JWildcardTypeName.supertypeOf(args.last())
+    val continuationType = JParameterizedTypeName.get(
+        JClassName.get(Continuation::class.java),
+        continuationReturnType
+    )
+    return actualArgs + listOf(
+        JWildcardTypeName.supertypeOf(continuationType),
+        JWildcardTypeName.subtypeOf(JTypeName.OBJECT)
+    )
+}
+
+/**
+ * The private constructor of [JTypeVariableName] which receives a list.
+ * We use this in [createModifiableTypeVariableName] to create a [JTypeVariableName] whose bounds
+ * can be modified afterwards.
+ */
+private val typeVarNameConstructor by lazy {
+    try {
+        JTypeVariableName::class.java.getDeclaredConstructor(
+            String::class.java,
+            List::class.java
+        ).also {
+            it.trySetAccessible()
+        }
+    } catch (ex: NoSuchMethodException) {
+        throw IllegalStateException(
+            """
+            Room couldn't find the constructor it is looking for in JavaPoet.
+            Please file a bug at $ISSUE_TRACKER_LINK.
+            """.trimIndent(),
+            ex
+        )
+    }
+}
+
+/**
+ * Creates a TypeVariableName where we can change the bounds after constructor.
+ * This is used to workaround a case for self referencing type declarations.
+ * see b/187572913 for more details
+ */
+private fun createModifiableTypeVariableName(
+    name: String,
+    bounds: List<JTypeName>
+): JTypeVariableName = typeVarNameConstructor.newInstance(
+    name,
+    bounds
+) as JTypeVariableName
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
new file mode 100644
index 0000000..a862b1b
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.ksp
+
+import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSName
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Variance
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.javapoet.KClassName
+import com.squareup.kotlinpoet.javapoet.KTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeVariableName
+import com.squareup.kotlinpoet.javapoet.KWildcardTypeName
+
+internal val ERROR_KTYPE_NAME = KClassName("error", "NonExistentClass")
+
+private typealias KTypeArgumentTypeLookup = LinkedHashMap<KSName, KTypeName>
+
+internal fun KSTypeReference?.asKTypeName(resolver: Resolver): KTypeName =
+    asKTypeName(
+        resolver = resolver,
+        typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+    )
+
+private fun KSTypeReference?.asKTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+    return if (this == null) {
+        ERROR_KTYPE_NAME
+    } else {
+        resolve().asKTypeName(resolver, typeArgumentTypeLookup)
+    }
+}
+
+internal fun KSDeclaration.asKTypeName(resolver: Resolver): KTypeName =
+    asKTypeName(
+        resolver = resolver,
+        typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+    )
+
+private fun KSDeclaration.asKTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+    if (this is KSTypeAlias) {
+        return this.type.asKTypeName(resolver, typeArgumentTypeLookup)
+    }
+    if (this is KSTypeParameter) {
+        return this.asKTypeName(resolver, typeArgumentTypeLookup)
+    }
+    val qualified = qualifiedName?.asString() ?: return ERROR_KTYPE_NAME
+    val pkg = getNormalizedPackageName()
+    val shortNames = if (pkg == "") {
+        qualified
+    } else {
+        qualified.substring(pkg.length + 1)
+    }.split('.')
+    return KClassName(pkg, shortNames.first(), *(shortNames.drop(1).toTypedArray()))
+}
+
+private fun KSTypeParameter.asKTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+    typeArgumentTypeLookup[name]?.let {
+        return it
+    }
+    val mutableBounds = mutableListOf(ANY.copy(nullable = true))
+    val typeName = createModifiableTypeVariableName(name = name.asString(), bounds = mutableBounds)
+    typeArgumentTypeLookup[name] = typeName
+    val resolvedBounds = bounds.map {
+        it.asKTypeName(resolver, typeArgumentTypeLookup)
+    }.toList()
+    if (resolvedBounds.isNotEmpty()) {
+        mutableBounds.addAll(resolvedBounds)
+        mutableBounds.remove(ANY.copy(nullable = true))
+    }
+    typeArgumentTypeLookup.remove(name)
+    return typeName
+}
+
+internal fun KSTypeArgument.asKTypeName(
+    resolver: Resolver
+): KTypeName = asKTypeName(
+    resolver = resolver,
+    typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+)
+
+private fun KSTypeArgument.asKTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+    fun resolveTypeName() = type.asKTypeName(resolver, typeArgumentTypeLookup)
+    return when (variance) {
+        Variance.CONTRAVARIANT -> KWildcardTypeName.consumerOf(resolveTypeName())
+        Variance.COVARIANT -> KWildcardTypeName.producerOf(resolveTypeName())
+        Variance.STAR -> com.squareup.kotlinpoet.STAR
+        else -> {
+            if (hasJvmWildcardAnnotation()) {
+                KWildcardTypeName.consumerOf(resolveTypeName())
+            } else {
+                resolveTypeName()
+            }
+        }
+    }
+}
+
+internal fun KSType.asKTypeName(resolver: Resolver): KTypeName =
+    asKTypeName(
+        resolver = resolver,
+        typeArgumentTypeLookup = KTypeArgumentTypeLookup()
+    )
+
+@OptIn(KspExperimental::class)
+private fun KSType.asKTypeName(
+    resolver: Resolver,
+    typeArgumentTypeLookup: KTypeArgumentTypeLookup
+): KTypeName {
+    return if (this.arguments.isNotEmpty() && !resolver.isJavaRawType(this)) {
+        val args: List<KTypeName> = this.arguments
+            .map { typeArg ->
+                typeArg.asKTypeName(
+                    resolver = resolver,
+                    typeArgumentTypeLookup = typeArgumentTypeLookup
+                )
+            }
+        val typeName = declaration.asKTypeName(resolver, typeArgumentTypeLookup)
+        check(typeName is KClassName) { "Unexpected type name for KSType: $typeName" }
+        typeName.parameterizedBy(args)
+    } else {
+        this.declaration.asKTypeName(resolver, typeArgumentTypeLookup)
+    }.copy(nullable = isMarkedNullable)
+}
+
+/**
+ * Creates a TypeVariableName where we can change the bounds after constructor.
+ * This is used to workaround a case for self referencing type declarations.
+ */
+private fun createModifiableTypeVariableName(
+    name: String,
+    bounds: List<KTypeName>
+): KTypeVariableName = KTypeVariableNameFactory.newInstance(name, bounds)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KTypeVariableNameFactory.java b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KTypeVariableNameFactory.java
new file mode 100644
index 0000000..79489d3
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KTypeVariableNameFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.ksp;
+
+import com.squareup.kotlinpoet.TypeName;
+import com.squareup.kotlinpoet.TypeVariableName;
+
+import java.util.List;
+
+final class KTypeVariableNameFactory {
+    private KTypeVariableNameFactory() {
+    }
+
+    /**
+     * Calls the internal companion object of KTypeVariableName which receives a list.
+     * We use this in {@link KSTypeJavaPoetExtKt#createModifiableTypeVariableName(String, List)}
+     * to create a {@link com.squareup.kotlinpoet.TypeVariableName} whose bounds can be modified
+     * afterwards.
+     */
+    @SuppressWarnings("KotlinInternalInJava")
+    static TypeVariableName newInstance(String name, List<TypeName> bounds) {
+        return TypeVariableName.Companion.of$kotlinpoet(name, bounds, null);
+    }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt
index 2da4cad..7dc6993 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspArrayType.kt
@@ -16,13 +16,17 @@
 
 package androidx.room.compiler.processing.ksp
 
+import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XArrayType
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import com.google.devtools.ksp.symbol.KSType
 import com.google.devtools.ksp.symbol.Variance
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
 
 internal sealed class KspArrayType(
     env: KspProcessingEnv,
@@ -35,8 +39,12 @@
 
     abstract override val componentType: KspType
 
-    override fun resolveTypeName(): TypeName {
-        return ArrayTypeName.of(componentType.typeName)
+    override fun resolveJTypeName(): JTypeName {
+        return this.asTypeName().java
+    }
+
+    override fun resolveKTypeName(): KTypeName {
+        return this.asTypeName().kotlin
     }
 
     override fun boxed() = this
@@ -54,6 +62,17 @@
     ) : KspArrayType(
         env, ksType, jvmTypeResolver
     ) {
+        private val xTypeName: XTypeName by lazy {
+            val componentTypeName = componentType.asTypeName()
+            XTypeName(
+                java = JArrayTypeName.of(componentTypeName.java.box()),
+                kotlin = ARRAY.parameterizedBy(componentTypeName.kotlin),
+                nullability = nullability,
+            )
+        }
+
+        override fun asTypeName() = xTypeName
+
         override val componentType: KspType by lazy {
             val arg = ksType.arguments.single()
             // https://kotlinlang.org/docs/reference/basic-types.html#primitive-type-arrays
@@ -92,6 +111,17 @@
     ) : KspArrayType(
         env, ksType, jvmTypeResolver
     ) {
+        private val xTypeName: XTypeName by lazy {
+            val componentTypeName = componentType.asTypeName()
+            XTypeName(
+                java = JArrayTypeName.of(componentTypeName.java.unbox()),
+                kotlin = ksType.asKTypeName(env.resolver),
+                nullability = nullability,
+            )
+        }
+
+        override fun asTypeName() = xTypeName
+
         override fun copyWithNullability(nullability: XNullability): PrimitiveArray {
             return PrimitiveArray(
                 env = env,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
index bd3bab4..ec72704 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
@@ -29,7 +29,7 @@
     override val typeVariableNames: List<TypeVariableName> by lazy {
         origin.declaration.typeParameters.map {
             val typeParameterBounds = it.bounds.map {
-                it.typeName(env.resolver)
+                it.asJTypeName(env.resolver)
             }.toList().toTypedArray()
             TypeVariableName.get(
                 it.name.asString(),
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
index ac8c34e..771dba1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
@@ -19,7 +19,8 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.tryUnbox
 import com.google.devtools.ksp.symbol.KSType
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
 
 /**
  * This tries to mimic primitive types in Kotlin.
@@ -33,8 +34,12 @@
     ksType: KSType,
     jvmTypeResolver: KspJvmTypeResolver?
 ) : KspType(env, ksType, jvmTypeResolver) {
-    override fun resolveTypeName(): TypeName {
-        return ksType.typeName(env.resolver).tryUnbox()
+    override fun resolveJTypeName(): JTypeName {
+        return ksType.asJTypeName(env.resolver).tryUnbox()
+    }
+
+    override fun resolveKTypeName(): KTypeName {
+        return ksType.asKTypeName(env.resolver)
     }
 
     override fun boxed(): KspType {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index 8cacecb..876c02a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing.ksp
 
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XEquality
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
@@ -28,6 +29,8 @@
 import com.google.devtools.ksp.symbol.KSTypeReference
 import com.google.devtools.ksp.symbol.Nullability
 import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
 import kotlin.reflect.KClass
 
 /**
@@ -51,11 +54,21 @@
     }
 
     final override val typeName: TypeName by lazy {
-        jvmWildcardType?.typeName ?: resolveTypeName()
+        xTypeName.java
     }
 
+    private val xTypeName: XTypeName by lazy {
+        XTypeName(
+            jvmWildcardType?.typeName ?: resolveJTypeName(),
+            resolveKTypeName(),
+            nullability
+        )
+    }
+
+    override fun asTypeName() = xTypeName
+
     /**
-     * A Kotlin type might have a sligtly different type in JVM due to wildcards.
+     * A Kotlin type might have a slightly different type in JVM due to wildcards.
      * This fields holds onto that value which will be used when creating JVM types.
      */
     private val jvmWildcardType by lazy {
@@ -65,7 +78,9 @@
     val jvmWildcardTypeOrSelf
         get() = jvmWildcardType ?: this
 
-    protected abstract fun resolveTypeName(): TypeName
+    protected abstract fun resolveJTypeName(): JTypeName
+
+    protected abstract fun resolveKTypeName(): KTypeName
 
     override val nullability by lazy {
         when (ksType.nullability) {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
index a247fb6..f7471ae 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
@@ -21,7 +21,8 @@
 import com.google.devtools.ksp.symbol.KSTypeArgument
 import com.google.devtools.ksp.symbol.KSTypeParameter
 import com.google.devtools.ksp.symbol.KSTypeReference
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
 
 /**
  * The typeName for type arguments requires the type parameter, hence we have a special type
@@ -48,8 +49,12 @@
         )
     }
 
-    override fun resolveTypeName(): TypeName {
-        return typeArg.typeName(env.resolver)
+    override fun resolveJTypeName(): JTypeName {
+        return typeArg.asJTypeName(env.resolver)
+    }
+
+    override fun resolveKTypeName(): KTypeName {
+        return typeArg.asKTypeName(env.resolver)
     }
 
     override fun boxed(): KspTypeArgumentType {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index bf0c668..75517f7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing.ksp
 
+import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.processing.XAnnotated
 import androidx.room.compiler.processing.XConstructorElement
 import androidx.room.compiler.processing.XEnumEntry
@@ -24,6 +25,7 @@
 import androidx.room.compiler.processing.XHasModifiers
 import androidx.room.compiler.processing.XMemberContainer
 import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.XTypeParameterElement
@@ -44,6 +46,8 @@
 import com.google.devtools.ksp.symbol.Modifier
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.KClassName
 
 internal sealed class KspTypeElement(
     env: KspProcessingEnv,
@@ -130,14 +134,22 @@
     }
 
     override val className: ClassName by lazy {
-        declaration.typeName(env.resolver).tryBox().also { typeName ->
-            check(typeName is ClassName) {
+        xClassName.java
+    }
+
+    private val xClassName: XClassName by lazy {
+        val java = declaration.asJTypeName(env.resolver).tryBox().also { typeName ->
+            check(typeName is JClassName) {
                 "Internal error. The type name for $declaration should be a class name but " +
                     "received ${typeName::class}"
             }
-        } as ClassName
+        } as JClassName
+        val kotlin = declaration.asKTypeName(env.resolver) as KClassName
+        XClassName(java, kotlin, XNullability.NONNULL)
     }
 
+    override fun asClassName() = xClassName
+
     private val allMethods = MemoizedSequence {
         collectAllMethods(this)
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt
index 497280f6..87d50ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspVoidType.kt
@@ -18,7 +18,8 @@
 
 import androidx.room.compiler.processing.XNullability
 import com.google.devtools.ksp.symbol.KSType
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
 
 /**
  * Representation of `void` in KSP.
@@ -33,14 +34,18 @@
     val boxed: Boolean,
     jvmTypeResolver: KspJvmTypeResolver?
 ) : KspType(env, ksType, jvmTypeResolver) {
-    override fun resolveTypeName(): TypeName {
+    override fun resolveJTypeName(): JTypeName {
         return if (boxed || nullability == XNullability.NULLABLE) {
-            TypeName.VOID.box()
+            JTypeName.VOID.box()
         } else {
-            TypeName.VOID
+            JTypeName.VOID
         }
     }
 
+    override fun resolveKTypeName(): KTypeName {
+        return com.squareup.kotlinpoet.UNIT
+    }
+
     override fun boxed(): KspType {
         return if (boxed) {
             this
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
index 1bbe125..96a2bc9 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
@@ -16,18 +16,21 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.codegen.JArrayTypeName
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.ksp.createTypeReference
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.asJTypeName
+import androidx.room.compiler.processing.util.asKTypeName
 import androidx.room.compiler.processing.util.getField
 import androidx.room.compiler.processing.util.kspResolver
 import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
-import androidx.room.compiler.processing.util.typeName
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
 import org.junit.Test
 
 class XArrayTypeTest {
@@ -50,12 +53,20 @@
                 .getField("param")
                 .type
             assertThat(type.isArray()).isTrue()
-            assertThat(type.typeName).isEqualTo(
-                ArrayTypeName.of(String::class.java)
+            assertThat(type.asTypeName().java).isEqualTo(
+                JArrayTypeName.of(String::class.java)
             )
+            if (invocation.isKsp) {
+                assertThat(type.asTypeName().kotlin).isEqualTo(
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(String::class.asKTypeName())
+                )
+            }
             check(type.isArray())
             type.componentType.let { component ->
-                assertThat(component.typeName).isEqualTo(String::class.typeName())
+                assertThat(component.asTypeName().java).isEqualTo(String::class.asJTypeName())
+                if (invocation.isKsp) {
+                    assertThat(component.asTypeName().kotlin).isEqualTo(String::class.asKTypeName())
+                }
                 assertThat(component.nullability).isEqualTo(XNullability.UNKNOWN)
             }
         }
@@ -64,16 +75,30 @@
     @Test
     fun synthetic() {
         runProcessorTest {
-            val objArray = it.processingEnv.getArrayType(
-                TypeName.OBJECT
+            fun checkObjectArray(objArray: XArrayType) {
+                check(objArray.isArray())
+                assertThat(objArray.componentType.asTypeName().java)
+                    .isEqualTo(JTypeName.OBJECT)
+                assertThat(objArray.asTypeName().java).isEqualTo(
+                    JArrayTypeName.of(JTypeName.OBJECT)
+                )
+                if (it.isKsp) {
+                    assertThat(objArray.componentType.asTypeName().kotlin)
+                        .isEqualTo(com.squareup.kotlinpoet.ANY)
+                    assertThat(objArray.asTypeName().kotlin).isEqualTo(
+                        com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.ANY)
+                    )
+                }
+            }
+            checkObjectArray(
+                it.processingEnv.getArrayType(it.processingEnv.requireType("java.lang.Object"))
             )
-            check(objArray.isArray())
-            assertThat(objArray.componentType.typeName).isEqualTo(
-                TypeName.OBJECT
-            )
-            assertThat(objArray.typeName).isEqualTo(
-                ArrayTypeName.of(TypeName.OBJECT)
-            )
+            if (it.isKsp) {
+                // javac can't resolve Any
+                checkObjectArray(
+                    it.processingEnv.getArrayType(it.processingEnv.requireType("kotlin.Any"))
+                )
+            }
         }
     }
 
@@ -93,25 +118,51 @@
             sources = listOf(source)
         ) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
-            val nonNull = element.getField("nonNull").type
-            val nullable = element.getField("nullable").type
-            listOf(nonNull, nullable).forEach {
-                assertThat(it.isArray()).isTrue()
-                assertThat(it.typeName).isEqualTo(
-                    ArrayTypeName.of(String::class.java)
+            element.getField("nonNull").type.let { nonNull ->
+                check(nonNull.isArray())
+                assertThat(nonNull.asTypeName().java).isEqualTo(
+                    JArrayTypeName.of(String::class.java)
                 )
+                if (invocation.isKsp) {
+                    assertThat(nonNull.asTypeName().kotlin).isEqualTo(
+                        com.squareup.kotlinpoet.ARRAY.parameterizedBy(String::class.asKTypeName())
+                    )
+                }
+                nonNull.componentType.let { component ->
+                    assertThat(component.asTypeName().java).isEqualTo(
+                        String::class.asJTypeName()
+                    )
+                    if (invocation.isKsp) {
+                        assertThat(component.asTypeName().kotlin).isEqualTo(
+                            String::class.asKTypeName()
+                        )
+                    }
+                    assertThat(component.nullability).isEqualTo(XNullability.NONNULL)
+                }
             }
-            check(nonNull.isArray())
-            nonNull.componentType.let { component ->
-                assertThat(component.typeName).isEqualTo(
-                    String::class.typeName()
+            element.getField("nullable").type.let { nullable ->
+                check(nullable.isArray())
+                assertThat(nullable.asTypeName().java).isEqualTo(
+                    JArrayTypeName.of(String::class.java)
                 )
-                assertThat(component.nullability).isEqualTo(XNullability.NONNULL)
-            }
-            check(nullable.isArray())
-            nullable.componentType.let { component ->
-                assertThat(component.typeName).isEqualTo(String::class.typeName())
-                assertThat(component.nullability).isEqualTo(XNullability.NULLABLE)
+                if (invocation.isKsp) {
+                    assertThat(nullable.asTypeName().kotlin).isEqualTo(
+                        com.squareup.kotlinpoet.ARRAY.parameterizedBy(
+                            String::class.asKTypeName().copy(nullable = true)
+                        )
+                    )
+                }
+                nullable.componentType.let { component ->
+                    assertThat(component.asTypeName().java).isEqualTo(
+                        String::class.asJTypeName()
+                    )
+                    if (invocation.isKsp) {
+                        assertThat(component.asTypeName().kotlin).isEqualTo(
+                            String::class.asKTypeName().copy(nullable = true)
+                        )
+                    }
+                    assertThat(component.nullability).isEqualTo(XNullability.NULLABLE)
+                }
             }
         }
     }
@@ -141,29 +192,119 @@
             }
             """.trimIndent()
         )
-        runProcessorTest(listOf(src)) {
-            val subject = it.processingEnv.requireTypeElement("Subject")
+        runProcessorTest(listOf(src)) { invocation ->
+            class Container(
+                val method: String,
+                val jTypeName: JTypeName,
+                val kTypeName: KTypeName
+            ) {
+                override fun equals(other: Any?): Boolean {
+                    if (this === other) return true
+                    if (javaClass != other?.javaClass) return false
+                    other as Container
+                    if (method != other.method) return false
+                    if (jTypeName != other.jTypeName) return false
+                    if (invocation.isKsp) {
+                        if (kTypeName != other.kTypeName) return false
+                    }
+                    return true
+                }
+                override fun hashCode(): Int {
+                    var result = method.hashCode()
+                    result = 31 * result + jTypeName.hashCode()
+                    if (invocation.isKsp) {
+                        result = 31 * result + kTypeName.hashCode()
+                    }
+                    return result
+                }
+            }
+
+            val subject = invocation.processingEnv.requireTypeElement("Subject")
             val types = subject.getAllFieldsIncludingPrivateSupers().map {
                 assertWithMessage(it.name).that(it.type.isArray()).isTrue()
-                it.name to it.type.typeName
+                Container(it.name, it.type.asTypeName().java, it.type.asTypeName().kotlin)
             }.toList()
             assertThat(types).containsExactly(
-                "primitiveBooleanArray" to ArrayTypeName.of(TypeName.BOOLEAN),
-                "primitiveByteArray" to ArrayTypeName.of(TypeName.BYTE),
-                "primitiveShortArray" to ArrayTypeName.of(TypeName.SHORT),
-                "primitiveIntArray" to ArrayTypeName.of(TypeName.INT),
-                "primitiveLongArray" to ArrayTypeName.of(TypeName.LONG),
-                "primitiveCharArray" to ArrayTypeName.of(TypeName.CHAR),
-                "primitiveFloatArray" to ArrayTypeName.of(TypeName.FLOAT),
-                "primitiveDoubleArray" to ArrayTypeName.of(TypeName.DOUBLE),
-                "boxedBooleanArray" to ArrayTypeName.of(TypeName.BOOLEAN.box()),
-                "boxedByteArray" to ArrayTypeName.of(TypeName.BYTE.box()),
-                "boxedShortArray" to ArrayTypeName.of(TypeName.SHORT.box()),
-                "boxedIntArray" to ArrayTypeName.of(TypeName.INT.box()),
-                "boxedLongArray" to ArrayTypeName.of(TypeName.LONG.box()),
-                "boxedCharArray" to ArrayTypeName.of(TypeName.CHAR.box()),
-                "boxedFloatArray" to ArrayTypeName.of(TypeName.FLOAT.box()),
-                "boxedDoubleArray" to ArrayTypeName.of(TypeName.DOUBLE.box())
+                Container(
+                    "primitiveBooleanArray",
+                    JArrayTypeName.of(JTypeName.BOOLEAN),
+                    com.squareup.kotlinpoet.BOOLEAN_ARRAY
+                ),
+                Container(
+                    "primitiveByteArray",
+                    JArrayTypeName.of(JTypeName.BYTE),
+                    com.squareup.kotlinpoet.BYTE_ARRAY
+                ),
+                Container(
+                        "primitiveShortArray",
+                    JArrayTypeName.of(JTypeName.SHORT),
+                    com.squareup.kotlinpoet.SHORT_ARRAY
+                ),
+                Container(
+                    "primitiveIntArray",
+                    JArrayTypeName.of(JTypeName.INT),
+                    com.squareup.kotlinpoet.INT_ARRAY
+                ),
+                Container(
+                    "primitiveLongArray",
+                    JArrayTypeName.of(JTypeName.LONG),
+                    com.squareup.kotlinpoet.LONG_ARRAY
+                ),
+                Container(
+                    "primitiveCharArray",
+                    JArrayTypeName.of(JTypeName.CHAR),
+                    com.squareup.kotlinpoet.CHAR_ARRAY
+                ),
+                Container(
+                    "primitiveFloatArray",
+                    JArrayTypeName.of(JTypeName.FLOAT),
+                    com.squareup.kotlinpoet.FLOAT_ARRAY
+                ),
+                Container(
+                    "primitiveDoubleArray",
+                    JArrayTypeName.of(JTypeName.DOUBLE),
+                    com.squareup.kotlinpoet.DOUBLE_ARRAY
+                ),
+                Container(
+                    "boxedBooleanArray",
+                    JArrayTypeName.of(JTypeName.BOOLEAN.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.BOOLEAN)
+                ),
+                Container(
+                    "boxedByteArray",
+                    JArrayTypeName.of(JTypeName.BYTE.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.BYTE)
+                ),
+                Container(
+                    "boxedShortArray",
+                    JArrayTypeName.of(JTypeName.SHORT.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.SHORT)
+                ),
+                Container(
+                    "boxedIntArray",
+                    JArrayTypeName.of(JTypeName.INT.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.INT)
+                ),
+                Container(
+                    "boxedLongArray",
+                    JArrayTypeName.of(JTypeName.LONG.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.LONG)
+                ),
+                Container(
+                    "boxedCharArray",
+                    JArrayTypeName.of(JTypeName.CHAR.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.CHAR)
+                ),
+                Container(
+                    "boxedFloatArray",
+                    JArrayTypeName.of(JTypeName.FLOAT.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.FLOAT)
+                ),
+                Container(
+                    "boxedDoubleArray",
+                    JArrayTypeName.of(JTypeName.DOUBLE.box()),
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(com.squareup.kotlinpoet.DOUBLE)
+                )
             )
         }
     }
@@ -177,8 +318,11 @@
             invocation.processingEnv.getArrayType(intType).let {
                 assertThat(it.isArray()).isTrue()
                 assertThat(it.componentType).isEqualTo(intType)
-                assertThat(it.typeName).isEqualTo(
-                    ArrayTypeName.of(TypeName.INT)
+                assertThat(it.asTypeName().java).isEqualTo(
+                    JArrayTypeName.of(JTypeName.INT)
+                )
+                assertThat(it.asTypeName().kotlin).isEqualTo(
+                    com.squareup.kotlinpoet.INT_ARRAY
                 )
             }
             val nullableInt = (invocation.processingEnv as KspProcessingEnv).wrap(
@@ -188,8 +332,13 @@
             invocation.processingEnv.getArrayType(nullableInt).let {
                 assertThat(it.isArray()).isTrue()
                 assertThat(it.componentType).isEqualTo(nullableInt)
-                assertThat(it.typeName).isEqualTo(
-                    ArrayTypeName.of(TypeName.INT.box())
+                assertThat(it.asTypeName().java).isEqualTo(
+                    JArrayTypeName.of(JTypeName.INT.box())
+                )
+                assertThat(it.asTypeName().kotlin).isEqualTo(
+                    com.squareup.kotlinpoet.ARRAY.parameterizedBy(
+                        com.squareup.kotlinpoet.INT.copy(nullable = true)
+                    )
                 )
             }
         }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index c1134d4..248cb25 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -29,6 +30,9 @@
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
 import com.squareup.javapoet.TypeVariableName
+import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KClassName
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -67,29 +71,52 @@
                 assertThat(it.packageName).isEqualTo("")
                 assertThat(it.name).isEqualTo("TopLevel")
                 assertThat(it.qualifiedName).isEqualTo("TopLevel")
-                assertThat(it.className).isEqualTo(ClassName.get("", "TopLevel"))
+                assertThat(it.asClassName().java)
+                    .isEqualTo(JClassName.get("", "TopLevel"))
+                if (invocation.isKsp) {
+                    assertThat(it.asClassName().kotlin)
+                        .isEqualTo(KClassName("", "TopLevel"))
+                } else {
+                    assertThat(it.asClassName().kotlin)
+                        .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+                }
             }
             invocation.processingEnv.requireTypeElement("foo.bar.InFooBar").let {
                 assertThat(it.packageName).isEqualTo("foo.bar")
                 assertThat(it.name).isEqualTo("InFooBar")
                 assertThat(it.qualifiedName).isEqualTo("foo.bar.InFooBar")
-                assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "InFooBar"))
+                assertThat(it.asClassName().java)
+                    .isEqualTo(ClassName.get("foo.bar", "InFooBar"))
+                if (invocation.isKsp) {
+                    assertThat(it.asClassName().kotlin)
+                        .isEqualTo(KClassName("foo.bar", "InFooBar"))
+                }
             }
             invocation.processingEnv.requireTypeElement("foo.bar.Outer").let {
                 assertThat(it.packageName).isEqualTo("foo.bar")
                 assertThat(it.name).isEqualTo("Outer")
                 assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer")
-                assertThat(it.className).isEqualTo(
+                assertThat(it.asClassName().java).isEqualTo(
                     ClassName.get("foo.bar", "Outer")
                 )
+                if (invocation.isKsp) {
+                    assertThat(it.asClassName().kotlin).isEqualTo(
+                        KClassName("foo.bar", "Outer")
+                    )
+                }
             }
             invocation.processingEnv.requireTypeElement("foo.bar.Outer.Nested").let {
                 assertThat(it.packageName).isEqualTo("foo.bar")
                 assertThat(it.name).isEqualTo("Nested")
                 assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer.Nested")
-                assertThat(it.className).isEqualTo(
+                assertThat(it.asClassName().java).isEqualTo(
                     ClassName.get("foo.bar", "Outer", "Nested")
                 )
+                if (invocation.isKsp) {
+                    assertThat(it.asClassName().kotlin).isEqualTo(
+                        KClassName("foo.bar", "Outer", "Nested")
+                    )
+                }
             }
             if (invocation.isKsp) {
                 // these are KSP specific tests, typenames are tested elsewhere
@@ -98,11 +125,19 @@
                     assertThat(it.packageName).isEqualTo("kotlin")
                     assertThat(it.name).isEqualTo("Int")
                     assertThat(it.qualifiedName).isEqualTo("kotlin.Int")
+                    assertThat(it.asClassName().java).isEqualTo(JTypeName.INT.box())
+                    if (invocation.isKsp) {
+                        assertThat(it.asClassName().kotlin).isEqualTo(com.squareup.kotlinpoet.INT)
+                    }
                 }
                 invocation.processingEnv.requireTypeElement("kotlin.Int").let {
                     assertThat(it.packageName).isEqualTo("kotlin")
                     assertThat(it.name).isEqualTo("Int")
                     assertThat(it.qualifiedName).isEqualTo("kotlin.Int")
+                    assertThat(it.asClassName().java).isEqualTo(JTypeName.INT.box())
+                    if (invocation.isKsp) {
+                        assertThat(it.asClassName().kotlin).isEqualTo(com.squareup.kotlinpoet.INT)
+                    }
                 }
             }
         }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index c1ebb8e..f83ebeb 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -16,7 +16,7 @@
 
 package androidx.room.compiler.processing
 
-import androidx.room.compiler.processing.ksp.ERROR_TYPE_NAME
+import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.className
@@ -121,7 +121,7 @@
             val errorTypeName = if (it.isKsp) {
                 // in ksp, we lose the name when resolving the type.
                 // b/175246617
-                ERROR_TYPE_NAME
+                ERROR_JTYPE_NAME
             } else {
                 ClassName.get("", "NotExistingType")
             }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
index ad531fd..61eee03 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
@@ -60,16 +60,18 @@
             listOf("main", "lib").map {
                 it to invocation.kspResolver.requireClass("$it.Baz")
             }.forEach { (pkg, subject) ->
-                assertThat(subject.propertyType("intField").typeName(invocation.kspResolver))
+                assertThat(subject.propertyType("intField").asJTypeName(invocation.kspResolver))
                     .isEqualTo(TypeName.INT)
-                assertThat(subject.propertyType("listOfInts").typeName(invocation.kspResolver))
+                assertThat(subject.propertyType("listOfInts").asJTypeName(invocation.kspResolver))
                     .isEqualTo(
                         ParameterizedTypeName.get(
                             List::class.className(),
                             TypeName.INT.box()
                         )
                     )
-                assertThat(subject.propertyType("mutableMapOfAny").typeName(invocation.kspResolver))
+                assertThat(
+                    subject.propertyType("mutableMapOfAny").asJTypeName(invocation.kspResolver)
+                )
                     .isEqualTo(
                         ParameterizedTypeName.get(
                             Map::class.className(),
@@ -77,7 +79,7 @@
                             TypeName.OBJECT,
                         )
                     )
-                val typeName = subject.propertyType("nested").typeName(invocation.kspResolver)
+                val typeName = subject.propertyType("nested").asJTypeName(invocation.kspResolver)
                 check(typeName is ClassName)
                 assertThat(typeName.packageName()).isEqualTo(pkg)
                 assertThat(typeName.simpleNames()).containsExactly("Baz", "Nested")
@@ -114,11 +116,11 @@
             }.forEach { subject ->
                 assertWithMessage(subject.qualifiedName!!.asString())
                     .that(
-                        subject.propertyType("intField").typeName(invocation.kspResolver)
+                        subject.propertyType("intField").asJTypeName(invocation.kspResolver)
                     ).isEqualTo(TypeName.INT)
                 assertWithMessage(subject.qualifiedName!!.asString())
                     .that(
-                        subject.propertyType("listOfInts").typeName(invocation.kspResolver)
+                        subject.propertyType("listOfInts").asJTypeName(invocation.kspResolver)
                     ).isEqualTo(
                         ParameterizedTypeName.get(
                             List::class.className(),
@@ -126,14 +128,15 @@
                         )
                     )
                 val propertyType = subject.propertyType("incompleteGeneric")
-                val typeName = propertyType.typeName(invocation.kspResolver)
+                val typeName = propertyType.asJTypeName(invocation.kspResolver)
                 assertWithMessage(subject.qualifiedName!!.asString())
                     .that(
                         typeName
                     ).isEqualTo(
                         ClassName.get(List::class.java)
                     )
-                val nestedTypeName = subject.propertyType("nested").typeName(invocation.kspResolver)
+                val nestedTypeName =
+                    subject.propertyType("nested").asJTypeName(invocation.kspResolver)
                 assertWithMessage(subject.qualifiedName!!.asString())
                     .that(nestedTypeName)
                     .isEqualTo(
@@ -159,23 +162,23 @@
         runKspTest(sources = listOf(subjectSrc)) { invocation ->
             val subject = invocation.kspResolver.requireClass("Foo")
             assertThat(
-                subject.propertyType("errorField").typeName(invocation.kspResolver)
-            ).isEqualTo(ERROR_TYPE_NAME)
+                subject.propertyType("errorField").asJTypeName(invocation.kspResolver)
+            ).isEqualTo(ERROR_JTYPE_NAME)
             assertThat(
-                subject.propertyType("listOfError").typeName(invocation.kspResolver)
+                subject.propertyType("listOfError").asJTypeName(invocation.kspResolver)
             ).isEqualTo(
                 ParameterizedTypeName.get(
                     List::class.className(),
-                    ERROR_TYPE_NAME
+                    ERROR_JTYPE_NAME
                 )
             )
             assertThat(
-                subject.propertyType("mutableMapOfDontExist").typeName(invocation.kspResolver)
+                subject.propertyType("mutableMapOfDontExist").asJTypeName(invocation.kspResolver)
             ).isEqualTo(
                 ParameterizedTypeName.get(
                     Map::class.className(),
                     String::class.className(),
-                    ERROR_TYPE_NAME
+                    ERROR_JTYPE_NAME
                 )
             )
             invocation.assertCompilationResult {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
index 9eb4b0d..f9c6d5f 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
@@ -99,8 +99,8 @@
             subject.getField("errorType").type.let { type ->
                 assertThat(type.isError()).isTrue()
                 assertThat(type.typeArguments).isEmpty()
-                assertThat(type.typeName).isEqualTo(ERROR_TYPE_NAME)
-                assertThat(type.typeElement!!.className).isEqualTo(ERROR_TYPE_NAME)
+                assertThat(type.typeName).isEqualTo(ERROR_JTYPE_NAME)
+                assertThat(type.typeElement!!.className).isEqualTo(ERROR_JTYPE_NAME)
             }
 
             subject.getField("listOfErrorType").type.let { type ->
@@ -108,7 +108,7 @@
                 assertThat(type.typeArguments).hasSize(1)
                 type.typeArguments.single().let { typeArg ->
                     assertThat(typeArg.isError()).isTrue()
-                    assertThat(typeArg.typeName).isEqualTo(ERROR_TYPE_NAME)
+                    assertThat(typeArg.typeName).isEqualTo(ERROR_JTYPE_NAME)
                 }
             }
             invocation.assertCompilationResult {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/JavaPoetTestExt.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/JavaPoetTestExt.kt
deleted file mode 100644
index 8f7be96..0000000
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/JavaPoetTestExt.kt
+++ /dev/null
@@ -1,27 +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.room.compiler.processing.util
-
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-import kotlin.reflect.KClass
-
-val UNIT_CLASS_NAME = ClassName.get("kotlin", "Unit")
-val CONTINUATION_CLASS_NAME = ClassName.get("kotlin.coroutines", "Continuation")
-
-fun KClass<*>.typeName() = TypeName.get(this.java)
-fun KClass<*>.className() = ClassName.get(this.java)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt
new file mode 100644
index 0000000..df6eeec
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/PoetTestExt.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.room.compiler.processing.util
+
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.JTypeVariableName
+import com.squareup.kotlinpoet.javapoet.KParameterizedTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeVariableName
+import kotlin.reflect.KClass
+
+val UNIT_CLASS_NAME = ClassName.get("kotlin", "Unit")
+val CONTINUATION_CLASS_NAME = ClassName.get("kotlin.coroutines", "Continuation")
+
+// TODO(b/247242378): Migrate usages to asJTypeName() and asJClassName()
+// @Deprecated(
+//     message = "Use asJTypeName() to be clear it's a JavaPoet converter",
+//     replaceWith = ReplaceWith("asJTypeName()")
+// )
+fun KClass<*>.typeName() = TypeName.get(this.java)
+
+// TODO(b/247242378): Migrate usages to asJTypeName() and asJClassName()
+// @Deprecated(
+//     message = "Use asJClassName() to be clear it's a JavaPoet converter",
+//     replaceWith = ReplaceWith("asJClassName()")
+// )
+fun KClass<*>.className() = ClassName.get(this.java)
+
+fun KClass<*>.asJTypeName() = TypeName.get(this.java)
+fun KClass<*>.asJClassName() = ClassName.get(this.java)
+
+fun KClass<*>.asKTypeName() = this.asTypeName()
+fun KClass<*>.asKClassName() = this.asClassName()
+
+/**
+ * Dumps the typename with its bounds in a given depth, making tests more readable.
+ */
+fun JTypeName.dumpToString(depth: Int): String {
+    return dump(this, depth).toString()
+}
+
+/**
+ * Dumps the typename with its bounds in a given depth, making tests more readable.
+ */
+fun KTypeName.dumpToString(depth: Int): String {
+    return dump(this, depth).toString()
+}
+
+private fun dump(typeName: Any, depth: Int): TypeNameNode? {
+    if (depth < 0) return null
+    return when (typeName) {
+        is JParameterizedTypeName -> TypeNameNode(
+            text = typeName.toString(),
+            typeArgs = typeName.typeArguments.mapNotNull { dump(it, depth - 1) }
+        )
+        is KParameterizedTypeName -> TypeNameNode(
+            text = typeName.toString(),
+            typeArgs = typeName.typeArguments.mapNotNull { dump(it, depth - 1) }
+        )
+        is JTypeVariableName -> TypeNameNode(
+            text = typeName.toString(),
+            bounds = typeName.bounds.mapNotNull { dump(it, depth - 1) }
+        )
+        is KTypeVariableName -> TypeNameNode(
+            text = typeName.toString(),
+            bounds = typeName.bounds.mapNotNull { dump(it, depth - 1) }
+        )
+        else -> TypeNameNode(text = typeName.toString())
+    }
+}
+
+private data class TypeNameNode(
+    val text: String,
+    val bounds: List<TypeNameNode> = emptyList(),
+    val typeArgs: List<TypeNameNode> = emptyList()
+) {
+    override fun toString(): String {
+        return buildString {
+            appendLine(text)
+            bounds.forEach {
+                appendLine(it.toString().prependIndent("> "))
+            }
+            typeArgs.forEach {
+                appendLine(it.toString().prependIndent("| "))
+            }
+        }.trim()
+    }
+}
diff --git a/room/room-compiler/lint-baseline.xml b/room/room-compiler/lint-baseline.xml
new file mode 100644
index 0000000..0b7f2c4
--- /dev/null
+++ b/room/room-compiler/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+        errorLine1="    @VisibleForTesting"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt"/>
+    </issue>
+
+</issues>
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt
index 4cbf4637..3e60540 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/AutoMigration.kt
@@ -16,11 +16,11 @@
 
 package androidx.room.vo
 
+import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.migration.bundle.EntityBundle
 import androidx.room.migration.bundle.FieldBundle
 import androidx.room.util.SchemaDiffResult
-import com.squareup.javapoet.ClassName
 
 /**
  * Stores the changes detected in a database schema between the old and new versions.
@@ -34,10 +34,10 @@
 ) {
     val specClassName = specElement?.className
 
-    fun getImplTypeName(databaseClassName: ClassName): ClassName {
-        return ClassName.get(
-            databaseClassName.packageName(),
-            "${databaseClassName.simpleNames().joinToString("_")}_AutoMigration_${from}_${to}_Impl"
+    fun getImplTypeName(databaseClassName: XClassName): XClassName {
+        return XClassName.get(
+            databaseClassName.packageName,
+            "${databaseClassName.simpleNames.joinToString("_")}_AutoMigration_${from}_${to}_Impl"
         )
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
index c6cdbe8..55cbec8 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -24,7 +24,7 @@
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.codegen.addOriginatingElement
-import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.codegen.toXClassName
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SupportDbTypeNames
@@ -32,7 +32,6 @@
 import androidx.room.migration.bundle.EntityBundle
 import androidx.room.migration.bundle.FtsEntityBundle
 import androidx.room.vo.AutoMigration
-import com.squareup.javapoet.TypeName
 import com.squareup.kotlinpoet.javapoet.toKClassName
 
 /**
@@ -42,7 +41,7 @@
     private val dbElement: XTypeElement,
     val autoMigration: AutoMigration,
     private val codeLanguage: CodeLanguage
-) : TypeWriter(autoMigration.getImplTypeName(dbElement.className)) {
+) : TypeWriter(autoMigration.getImplTypeName(dbElement.asClassName())) {
     private val addedColumns = autoMigration.schemaDiff.addedColumns
     private val addedTables = autoMigration.schemaDiff.addedTables
     private val renamedTables = autoMigration.schemaDiff.renamedTables
@@ -52,17 +51,16 @@
     override fun createTypeSpecBuilder(): XTypeSpec.Builder {
         val builder = XTypeSpec.classBuilder(
             codeLanguage,
-            autoMigration.getImplTypeName(dbElement.className)
+            autoMigration.getImplTypeName(dbElement.asClassName())
         )
         builder.apply {
             addOriginatingElement(dbElement)
-            superclass(RoomTypeNames.MIGRATION)
+            superclass(RoomTypeNames.MIGRATION.toXClassName())
 
             if (autoMigration.specClassName != null) {
                 builder.addProperty(
-                    typeName = RoomTypeNames.AUTO_MIGRATION_SPEC,
+                    typeName = RoomTypeNames.AUTO_MIGRATION_SPEC.toXClassName(),
                     name = "callback",
-                    nullability = XNullability.NONNULL,
                     visibility = VisibilityModifier.PRIVATE,
                     initExpr = if (!autoMigration.isSpecProvided) {
                         XCodeBlock.builder(codeLanguage).apply(
@@ -97,9 +95,8 @@
             )
             if (autoMigration.isSpecProvided) {
                 addParameter(
-                    typeName = RoomTypeNames.AUTO_MIGRATION_SPEC,
+                    typeName = RoomTypeNames.AUTO_MIGRATION_SPEC.toXClassName(),
                     name = "callback",
-                    nullability = XNullability.NONNULL
                 )
                 addStatement("this.callback = callback")
             }
@@ -114,11 +111,9 @@
             isOverridden = true,
         ).apply {
                 addParameter(
-                    typeName = SupportDbTypeNames.DB,
+                    typeName = SupportDbTypeNames.DB.toXClassName(),
                     name = "database",
-                    nullability = XNullability.NONNULL
                 )
-                returns(TypeName.VOID, XNullability.NONNULL)
                 addMigrationStatements(this)
                 if (autoMigration.specClassName != null) {
                     addStatement("callback.onPostMigrate(database)")
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
index 6e5db80..7a3e9c5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -17,6 +17,8 @@
 package androidx.room.writer
 
 import androidx.annotation.NonNull
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.codegen.toXClassName
 import androidx.room.compiler.processing.MethodSpecHelper
 import androidx.room.compiler.processing.addOriginatingElement
 import androidx.room.ext.AndroidTypeNames
@@ -368,15 +370,16 @@
 
             returns(ParameterizedTypeName.get(CommonTypeNames.LIST, RoomTypeNames.MIGRATION))
             val autoMigrationsList = database.autoMigrations.map { autoMigrationResult ->
-                val implTypeName = autoMigrationResult.getImplTypeName(database.typeName)
+                val implTypeName =
+                    autoMigrationResult.getImplTypeName(database.typeName.toXClassName())
                 if (autoMigrationResult.isSpecProvided) {
                     CodeBlock.of(
                         "new $T(autoMigrationSpecsMap.get($T.class))",
-                        implTypeName,
+                        implTypeName.toJavaPoet(),
                         autoMigrationResult.specClassName
                     )
                 } else {
-                    CodeBlock.of("new $T()", implTypeName)
+                    CodeBlock.of("new $T()", implTypeName.toJavaPoet())
                 }
             }
             addStatement(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
index 349b407..4cfe79c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
@@ -17,19 +17,19 @@
 package androidx.room.writer
 
 import androidx.room.RoomProcessor
+import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.codegen.XTypeSpec.Builder.Companion.apply
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.writeTo
 import androidx.room.ext.S
-import com.squareup.kotlinpoet.javapoet.JClassName
 import com.squareup.kotlinpoet.javapoet.toKClassName
 import kotlin.reflect.KClass
 
 /**
  * Base class for all writers that can produce a class.
  */
-abstract class TypeWriter(private val className: JClassName) {
+abstract class TypeWriter(private val className: XClassName) {
     private val metadata = mutableMapOf<KClass<*>, Any>()
 
     abstract fun createTypeSpecBuilder(): XTypeSpec.Builder
diff --git a/wear/watchface/watchface/lint-baseline.xml b/wear/watchface/watchface/lint-baseline.xml
new file mode 100644
index 0000000..3e45aa6
--- /dev/null
+++ b/wear/watchface/watchface/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Did you mean `@get:RestrictTo`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+        errorLine1="    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/watchface/ComplicationSlot.kt"/>
+    </issue>
+
+</issues>
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 7fb57ff..51d8a7d 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -1340,6 +1340,10 @@
                     } catch (e: Exception) {
                         InteractiveInstanceManager
                             .takePendingWallpaperInteractiveWatchFaceInstance()?.let {
+                                Log.e(
+                                    TAG,
+                                    "takePendingWallpaperInteractiveWatchFaceInstance failed"
+                                )
                                 it.callback.onInteractiveWatchFaceCrashed(
                                     CrashInfoParcel(e)
                                 )
@@ -1362,6 +1366,7 @@
                     pendingWallpaperInstance.callback.onInteractiveWatchFaceCreated(instance)
                     instance
                 } catch (e: Exception) {
+                    Log.e(TAG, "createInteractiveInstance failed")
                     pendingWallpaperInstance.callback.onInteractiveWatchFaceCrashed(
                         CrashInfoParcel(e)
                     )
diff --git a/window/window/lint-baseline.xml b/window/window/lint-baseline.xml
new file mode 100644
index 0000000..a84ae6d
--- /dev/null
+++ b/window/window/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+
+    <issue
+        id="SupportAnnotationUsage"
+        message="Did you mean `@get:VisibleForTesting`? Without `get:` this annotates the constructor parameter itself instead of the associated getter."
+        errorLine1="    @VisibleForTesting"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/window/layout/SidecarCompat.kt"/>
+    </issue>
+
+</issues>