Merge "Supporting ImmutableMap in Room." into androidx-main
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index d676337..8095df7 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -1441,18 +1441,6 @@
         }
     }
 
-    // Update the transparent snapshot if necessary
-    // This doesn't need to take the sync because it is updating thread local state.
-    (threadSnapshot.get() as? TransparentObserverMutableSnapshot)?.let {
-        threadSnapshot.set(
-            TransparentObserverMutableSnapshot(
-                currentGlobalSnapshot.get(),
-                it.specifiedReadObserver,
-                it.specifiedWriteObserver
-            )
-        )
-        it.dispose()
-    }
     return result
 }
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index 3448a42..aec9166 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -113,8 +113,10 @@
         if (!isObserving) {
             isObserving = true
             try {
-                applyMap.map.removeValueIf {
-                    it === scope
+                synchronized(applyMaps) {
+                    applyMap.map.removeValueIf {
+                        it === scope
+                    }
                 }
                 Snapshot.observe(readObserver, null, block)
             } finally {
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
index 2d7e1ec..66b607b 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
@@ -17,9 +17,13 @@
 package androidx.compose.runtime.snapshots
 
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import kotlin.concurrent.thread
 import kotlin.test.Test
 import kotlin.test.assertEquals
+import kotlin.test.assertNull
 
 class SnapshotStateObserverTests {
 
@@ -282,6 +286,44 @@
         assertEquals(1, changes2)
     }
 
+    @Test // regression test for 192677711
+    fun tryToReproduceRaceCondition() {
+        var running = true
+        var threadException: Exception? = null
+        try {
+            thread {
+                try {
+                    while (running) {
+                        Snapshot.sendApplyNotifications()
+                    }
+                } catch (e: Exception) {
+                    threadException = e
+                }
+            }
+
+            for (i in 1..10000) {
+                val state1 by mutableStateOf(0)
+                var state2 by mutableStateOf(true)
+                val observer = SnapshotStateObserver({}).apply {
+                    start()
+                }
+                repeat(1000) {
+                    observer.observeReads(Unit, {}) {
+                        @Suppress("UNUSED_EXPRESSION")
+                        state1
+                        if (state2) {
+                            state2 = false
+                        }
+                    }
+                }
+                assertNull(threadException)
+            }
+        } finally {
+            running = false
+        }
+        assertNull(threadException)
+    }
+
     private fun runSimpleTest(
         block: (modelObserver: SnapshotStateObserver, data: MutableState<Int>) -> Unit
     ) {
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
index 5ce7ca3..6ec7593 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
@@ -703,6 +703,27 @@
         nestedChild.dispose()
     }
 
+    @Test // Regression test for b/193006595
+    fun transparentSnapshotAdvancesCorrectly() {
+        val state = Snapshot.observe({}) {
+            // In a transparent snapshot, advance the global snapshot
+            Snapshot.notifyObjectsInitialized()
+
+            // Create an apply an object in a snapshot
+            val state = atomic {
+                mutableStateOf(0)
+            }
+
+            // Ensure that the object can be accessed in the observer
+            assertEquals(0, state.value)
+
+            state
+        }
+
+        // Ensure that the object can be accessed globally.
+        assertEquals(0, state.value)
+    }
+
     private var count = 0
 
     @BeforeTest
diff --git a/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml b/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml
index 5a120c7..d62742c 100644
--- a/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml
+++ b/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml
@@ -29,5 +29,8 @@
         <activity android:name="androidx.fragment.app.FragmentActivity"/>
         <activity android:name="androidx.compose.ui.window.ActivityWithFlagSecure"/>
         <activity android:name="androidx.compose.ui.RecyclerViewActivity" />
+        <activity
+            android:name="androidx.compose.ui.draw.NotHardwareAcceleratedActivity"
+            android:hardwareAccelerated="false" />
     </application>
 </manifest>
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/NotHardwareAcceleratedActivityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/NotHardwareAcceleratedActivityTest.kt
new file mode 100644
index 0000000..7657dec2
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/NotHardwareAcceleratedActivityTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.compose.ui.draw
+
+import android.os.Build
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.GOLDEN_UI
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.background
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class NotHardwareAcceleratedActivityTest {
+
+    @get:Rule
+    val composeTestRule = createAndroidComposeRule<NotHardwareAcceleratedActivity>()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_UI)
+
+    @Test
+    fun layerTransformationsApplied() {
+        composeTestRule.setContent {
+            Box(Modifier.size(200.dp).background(Color.White).testTag("box")) {
+                Box(
+                    Modifier
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
+                            layout(placeable.width, placeable.height) {
+                                val offset = 50.dp.roundToPx()
+                                placeable.placeWithLayer(offset, offset) {
+                                    alpha = 0.5f
+                                    scaleX = 0.5f
+                                    scaleY = 0.5f
+                                }
+                            }
+                        }
+                        .size(100.dp)
+                        .background(Color.Red)
+                )
+            }
+        }
+
+        composeTestRule.onNodeWithTag("box")
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "not_hardware_accelerated_activity")
+    }
+}
+
+class NotHardwareAcceleratedActivity : ComponentActivity()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 748621d..ed7fb62 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -614,7 +614,10 @@
         // We can't be confident that RenderNode is supported, so we try and fail over to
         // the ViewLayer implementation. We'll try even on on P devices, but it will fail
         // until ART allows things on the unsupported list on P.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isRenderNodeCompatible) {
+        if (isHardwareAccelerated &&
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+            isRenderNodeCompatible
+        ) {
             try {
                 return RenderNodeLayer(
                     this,
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt
index 07406d5..a917fde5 100644
--- a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt
@@ -35,6 +35,6 @@
         Assert.assertEquals(CURRENT_API, registry.api)
         // Intentionally fails in IDE, because we use different API version in
         // studio and command line
-        Assert.assertEquals(3, registry.minApi)
+        Assert.assertEquals(8, registry.minApi)
     }
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
index 7d79231..b6d549f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
@@ -16,6 +16,8 @@
 
 package androidx.room.compiler.processing.ksp
 
+import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticFileMemberContainer
+import com.google.devtools.ksp.KspExperimental
 import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.google.devtools.ksp.symbol.KSDeclaration
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
@@ -42,14 +44,33 @@
  * Node that this is not necessarily the parent declaration. e.g. when a property is declared in
  * a constructor, its containing type is actual two levels up.
  */
+@OptIn(KspExperimental::class)
 internal fun KSDeclaration.findEnclosingMemberContainer(
     env: KspProcessingEnv
 ): KspMemberContainer? {
-    return findEnclosingAncestorClassDeclaration()?.let {
+    val memberContainer = findEnclosingAncestorClassDeclaration()?.let {
         env.wrapClassDeclaration(it)
     } ?: this.containingFile?.let {
         env.wrapKSFile(it)
     }
+    memberContainer?.let {
+        return it
+    }
+    // in compiled files, we may not find it. Try using the binary name
+
+    val ownerJvmClassName = when (this) {
+        is KSPropertyDeclaration -> env.resolver.getOwnerJvmClassName(this)
+        is KSFunctionDeclaration -> env.resolver.getOwnerJvmClassName(this)
+        else -> null
+    } ?: return null
+    // Binary name of a top level type is its canonical name. So we just load it directly by
+    // that value
+    env.findTypeElement(ownerJvmClassName)?.let {
+        return it
+    }
+    // When a top level function/property is compiled, its containing class does not exist in KSP,
+    // neither the file. So instead, we synthesize one
+    return KspSyntheticFileMemberContainer(ownerJvmClassName)
 }
 
 private fun KSDeclaration.findEnclosingAncestorClassDeclaration(): KSClassDeclaration? {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt
index 76e7323..1e646c6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
 import com.google.devtools.ksp.symbol.KSPropertyDeclaration
 
@@ -43,8 +44,7 @@
         else -> error(
             """
             Unexpected overridee type for $this ($overridee).
-            Please file a bug with steps to reproduce.
-            https://issuetracker.google.com/issues/new?component=413107
+            Please file a bug at $ISSUE_TRACKER_LINK.
             """.trimIndent()
         )
     } ?: returnType
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 dd6e2bd..f8c7623 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
@@ -19,6 +19,7 @@
 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.KSDeclaration
@@ -255,8 +256,8 @@
     } catch (ex: NoSuchMethodException) {
         throw IllegalStateException(
             """
-            Room couldn't find the constructor it is looking for in JavaPoet. Please file a bug at
-            https://issuetracker.google.com/issues/new?component=413107
+            Room couldn't find the constructor it is looking for in JavaPoet.
+            Please file a bug at $ISSUE_TRACKER_LINK.
             """.trimIndent(),
             ex
         )
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
index cb4ee5f..f2a52bb 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.processing.XExecutableParameterElement
 import androidx.room.compiler.processing.XHasModifiers
 import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
 import com.google.devtools.ksp.isConstructor
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
 import com.google.devtools.ksp.symbol.Modifier
@@ -76,8 +77,10 @@
             val enclosingContainer = declaration.findEnclosingMemberContainer(env)
 
             checkNotNull(enclosingContainer) {
-                "XProcessing does not currently support annotations on top level " +
-                    "functions with KSP. Cannot process $declaration."
+                """
+                Couldn't find the container element for $declaration.
+                Please file a bug at $ISSUE_TRACKER_LINK.
+                """
             }
 
             return when {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
index ae3b1f7..e2a0758 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
@@ -18,6 +18,7 @@
 
 import androidx.room.compiler.processing.XFiler
 import androidx.room.compiler.processing.XMessager
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
 import com.google.devtools.ksp.processing.CodeGenerator
 import com.google.devtools.ksp.processing.Dependencies
 import com.google.devtools.ksp.symbol.KSFile
@@ -87,8 +88,8 @@
                 Diagnostic.Kind.WARNING,
                 """
                     No dependencies are reported for $fileName which will prevent
-                    incremental compilation. Please file a bug at:
-                    https://issuetracker.google.com/issues/new?component=413107
+                    incremental compilation.
+                    Please file a bug at $ISSUE_TRACKER_LINK.
                 """.trimIndent()
             )
             Dependencies.ALL_FILES
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index 46889e0..1726c96 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -79,7 +79,7 @@
         )
     }
 
-    override fun findTypeElement(qName: String): XTypeElement? {
+    override fun findTypeElement(qName: String): KspTypeElement? {
         return typeElementStore[qName]
     }
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
new file mode 100644
index 0000000..35dc043
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.room.compiler.processing.ksp.synthetic
+
+import androidx.room.compiler.processing.XAnnotation
+import androidx.room.compiler.processing.XAnnotationBox
+import androidx.room.compiler.processing.XEquality
+import androidx.room.compiler.processing.ksp.KspMemberContainer
+import androidx.room.compiler.processing.ksp.KspType
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.squareup.javapoet.ClassName
+import kotlin.reflect.KClass
+
+/**
+ * When a top level function/member is compiled, the generated Java class does not exist in KSP.
+ *
+ * This wrapper synthesizes one from the JVM binary name
+ *
+ * https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.1
+ */
+internal class KspSyntheticFileMemberContainer(
+    private val binaryName: String
+) : KspMemberContainer, XEquality {
+    override val equalityItems: Array<out Any?> by lazy {
+        arrayOf(binaryName)
+    }
+
+    override val type: KspType?
+        get() = null
+
+    override val declaration: KSDeclaration?
+        get() = null
+
+    override val className: ClassName by lazy {
+        val packageName = binaryName.substringBeforeLast(
+            delimiter = '.',
+            missingDelimiterValue = ""
+        )
+        val shortNames = if (packageName == "") {
+            binaryName
+        } else {
+            binaryName.substring(packageName.length + 1)
+        }.split('$')
+        ClassName.get(
+            packageName,
+            shortNames.first(),
+            *shortNames.drop(1).toTypedArray()
+        )
+    }
+
+    override fun kindName(): String {
+        return "synthethic top level file"
+    }
+
+    override val fallbackLocationText: String
+        get() = binaryName
+
+    override val docComment: String?
+        get() = null
+
+    override fun <T : Annotation> getAnnotations(annotation: KClass<T>): List<XAnnotationBox<T>> {
+        return emptyList()
+    }
+
+    override fun getAllAnnotations(): List<XAnnotation> {
+        return emptyList()
+    }
+
+    override fun hasAnnotation(annotation: KClass<out Annotation>): Boolean {
+        return false
+    }
+
+    override fun hasAnnotationWithPackage(pkg: String): Boolean {
+        return false
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/ErrorMessages.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/ErrorMessages.kt
new file mode 100644
index 0000000..1386a08
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/ErrorMessages.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.room.compiler.processing.util
+
+/**
+ * Link to the issue tracker to report bugs.
+ */
+internal val ISSUE_TRACKER_LINK = "https://issuetracker.google.com/issues/new?component=413107"
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt
index 5582ac2..4054f04 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt
@@ -23,7 +23,7 @@
 import androidx.room.compiler.processing.util.kspProcessingEnv
 import androidx.room.compiler.processing.util.kspResolver
 import androidx.room.compiler.processing.util.runKspTest
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import com.google.devtools.ksp.KspExperimental
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
 import com.google.devtools.ksp.symbol.KSPropertyDeclaration
@@ -61,34 +61,37 @@
             sources = listOf(appSrc),
             classpath = classpath
         ) { invocation ->
-            // b/188822146
-            // TODO add lib package here once Room updates to a version that includes the
-            //  https://github.com/google/ksp/issues/396 fix (1.5.0-1.0.0-alpha09)
-            val declarations = invocation.kspResolver.getDeclarationsFromPackage("app")
-            declarations.filterIsInstance<KSFunctionDeclaration>()
-                .toList().let { methods ->
-                    assertThat(methods).hasSize(1)
-                    methods.forEach { method ->
-                        val element = KspExecutableElement.create(
-                            env = invocation.kspProcessingEnv,
-                            declaration = method
-                        )
-                        assertThat(element.containing.isTypeElement()).isFalse()
-                        assertThat(element.isStatic()).isTrue()
+            listOf("lib", "app").forEach { pkg ->
+                val declarations = invocation.kspResolver.getDeclarationsFromPackage(pkg)
+                declarations.filterIsInstance<KSFunctionDeclaration>()
+                    .toList().let { methods ->
+                        assertWithMessage(pkg).that(methods).hasSize(1)
+                        methods.forEach { method ->
+                            val element = KspExecutableElement.create(
+                                env = invocation.kspProcessingEnv,
+                                declaration = method
+                            )
+                            assertWithMessage(pkg).that(
+                                element.containing.isTypeElement()
+                            ).isFalse()
+                            assertWithMessage(pkg).that(element.isStatic()).isTrue()
+                        }
                     }
-                }
-            declarations.filterIsInstance<KSPropertyDeclaration>()
-                .toList().let { properties ->
-                    assertThat(properties).hasSize(2)
-                    properties.forEach {
-                        val element = KspFieldElement.create(
-                            env = invocation.kspProcessingEnv,
-                            declaration = it
-                        )
-                        assertThat(element.containing.isTypeElement()).isFalse()
-                        assertThat(element.isStatic()).isTrue()
+                declarations.filterIsInstance<KSPropertyDeclaration>()
+                    .toList().let { properties ->
+                        assertWithMessage(pkg).that(properties).hasSize(2)
+                        properties.forEach {
+                            val element = KspFieldElement.create(
+                                env = invocation.kspProcessingEnv,
+                                declaration = it
+                            )
+                            assertWithMessage(pkg).that(
+                                element.containing.isTypeElement()
+                            ).isFalse()
+                            assertWithMessage(pkg).that(element.isStatic()).isTrue()
+                        }
                     }
-                }
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
new file mode 100644
index 0000000..1ef355a
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.room.compiler.processing.ksp.synthetic
+
+import androidx.room.compiler.processing.ksp.KspFieldElement
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.getField
+import androidx.room.compiler.processing.util.kspResolver
+import androidx.room.compiler.processing.util.runKspTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.symbol.KSPropertyDeclaration
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@OptIn(KspExperimental::class)
+class KspSyntheticFileMemberContainerTest {
+    @Test
+    fun topLevel_noPackage() {
+        val annotation = Source.kotlin(
+            "MyAnnotation.kt",
+            """
+            annotation class MyAnnotation
+            """.trimIndent()
+        )
+        val appSrc = Source.kotlin(
+            "App.kt",
+            """
+                @MyAnnotation
+                val appMember = 1
+            """.trimIndent()
+        )
+        runKspTest(
+            sources = listOf(annotation, appSrc)
+        ) { invocation ->
+            val elements = invocation.kspResolver.getSymbolsWithAnnotation("MyAnnotation").toList()
+            assertThat(elements).hasSize(1)
+            val className = elements.map {
+                val owner = invocation.kspResolver.getOwnerJvmClassName(it as KSPropertyDeclaration)
+                assertWithMessage(it.toString()).that(owner).isNotNull()
+                KspSyntheticFileMemberContainer(owner!!).className
+            }.first()
+            assertThat(className.packageName()).isEmpty()
+            assertThat(className.simpleNames()).containsExactly("AppKt")
+        }
+    }
+
+    @Test
+    fun nestedClassNames() {
+        fun buildSources(pkg: String) = listOf(
+            Source.java(
+                "$pkg.JavaClass",
+                """
+                package $pkg;
+                public class JavaClass {
+                    int member;
+                    public static class NestedClass {
+                        int member;
+                    }
+                    public class InnerClass {
+                        int member;
+                    }
+                }
+                """.trimIndent()
+            ),
+            Source.java(
+                "${pkg}JavaClass",
+                """
+                public class ${pkg}JavaClass {
+                    int member;
+                    public static class NestedClass {
+                        int member;
+                    }
+                    public class InnerClass {
+                        int member;
+                    }
+                }
+                """.trimIndent()
+            ),
+            Source.kotlin(
+                "$pkg/KotlinClass.kt",
+                """
+                package $pkg
+                class KotlinClass {
+                    val member = 1
+                    class NestedClass {
+                        val member = 1
+                    }
+                    inner class InnerClass {
+                        val member = 1
+                    }
+                }
+                """.trimIndent()
+            ),
+            Source.kotlin(
+                "KotlinClass.kt",
+                """
+                class ${pkg}KotlinClass {
+                    val member = 1
+                    class NestedClass {
+                        val member = 1
+                    }
+                    inner class InnerClass {
+                        val member = 1
+                    }
+                }
+                """.trimIndent()
+            )
+        )
+        val lib = compileFiles(buildSources("lib"))
+        runKspTest(
+            sources = buildSources("app"),
+            classpath = lib
+        ) { invocation ->
+            fun runTest(qName: String) {
+                invocation.processingEnv.requireTypeElement(qName).let { target ->
+                    val field = target.getField("member") as KspFieldElement
+                    val owner = invocation.kspResolver.getOwnerJvmClassName(field.declaration)
+                    assertWithMessage(qName).that(owner).isNotNull()
+                    val synthetic = KspSyntheticFileMemberContainer(owner!!)
+                    assertWithMessage(qName).that(target.className).isEqualTo(synthetic.className)
+                }
+            }
+            listOf("lib", "app").forEach { pkg ->
+                // test both top level and in package cases
+                listOf(pkg, "$pkg.").forEach { prefix ->
+                    runTest("${prefix}JavaClass")
+                    runTest("${prefix}JavaClass.NestedClass")
+                    runTest("${prefix}JavaClass.InnerClass")
+                    runTest("${prefix}KotlinClass")
+                    runTest("${prefix}KotlinClass.NestedClass")
+                    runTest("${prefix}KotlinClass.InnerClass")
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt
new file mode 100644
index 0000000..a480cb27
--- /dev/null
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.wear.compose.integration.demos
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.AppCard
+import androidx.wear.compose.material.Card
+import androidx.wear.compose.material.CardDefaults
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+import androidx.wear.compose.material.TitleCard
+
+@Composable
+fun CardDemo() {
+    Column(
+        modifier = Modifier.fillMaxSize()
+            .padding(start = 8.dp, end = 8.dp)
+            .verticalScroll(
+                rememberScrollState()
+            ),
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        Spacer(modifier = Modifier.size(30.dp))
+        Card(
+            onClick = {},
+            modifier = Modifier.fillMaxWidth()
+        ) {
+            Column(modifier = Modifier.fillMaxWidth()) {
+                Text("Basic unopinionated chip")
+                Text("Sets the shape")
+                Text("and the background")
+            }
+        }
+        Spacer(modifier = Modifier.size(4.dp))
+        AppCard(
+            onClick = {},
+            appName = { Text("AppName") },
+            title = { Text("AppCard") },
+            time = { Text("now") },
+            body = {
+                Column(modifier = Modifier.fillMaxWidth()) {
+                    Text("Some body content")
+                    Text("and some more body content")
+                }
+            },
+        )
+        Spacer(modifier = Modifier.size(4.dp))
+        TitleCard(
+            onClick = {},
+            title = { Text("TitleCard") },
+            time = { Text("now") },
+            body = {
+                Column(modifier = Modifier.fillMaxWidth()) {
+                    Text("Some body content")
+                    Text("and some more body content")
+                }
+            },
+        )
+        Spacer(modifier = Modifier.size(4.dp))
+        TitleCard(
+            onClick = {},
+            title = { Text("TitleCard") },
+            body = {
+                Column(modifier = Modifier.fillMaxWidth()) {
+                    Text("This title card doesn't show time")
+                }
+            }
+        )
+        Spacer(modifier = Modifier.size(4.dp))
+        TitleCard(
+            onClick = {},
+            title = { Text("Custom TitleCard") },
+            body = {
+                Column(modifier = Modifier.fillMaxWidth()) {
+                    Text("This title card emphasises the title with custom color")
+                }
+            },
+            titleColor = Color.Yellow
+        )
+        Spacer(modifier = Modifier.size(4.dp))
+        TitleCard(
+            onClick = {},
+            title = {
+                Column {
+                    Text("Custom TitleCard")
+                    Text("With a Coloured Secondary Label", color = Color.Yellow)
+                }
+            },
+            body = {
+                Column(modifier = Modifier.fillMaxWidth()) {
+                    Text("This title card emphasises the title with custom color")
+                }
+            },
+        )
+        Spacer(modifier = Modifier.size(4.dp))
+        TitleCard(
+            onClick = {},
+            title = { Text("TitleCard With an ImageBackground") },
+            body = {
+                Column(modifier = Modifier.fillMaxWidth()) {
+                    Text("Text coloured to stand out on the image")
+                }
+            },
+            backgroundPainter = CardDefaults.imageBackgroundPainter(
+                backgroundImagePainter = painterResource(id = R.drawable.backgroundimage1)
+            ),
+            bodyColor = MaterialTheme.colors.onSurface,
+            titleColor = MaterialTheme.colors.onSurface,
+        )
+        Spacer(modifier = Modifier.size(30.dp))
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
index 80814b4..3dc7725 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
@@ -30,5 +30,6 @@
             )
         ),
         ComposableDemo("Toggle Button") { ToggleButtons() },
+        ComposableDemo("Card") { CardDemo() },
     ),
 )
diff --git a/wear/compose/integration-tests/demos/src/main/res/drawable/backgroundimage1.png b/wear/compose/integration-tests/demos/src/main/res/drawable/backgroundimage1.png
new file mode 100644
index 0000000..fbb9332
--- /dev/null
+++ b/wear/compose/integration-tests/demos/src/main/res/drawable/backgroundimage1.png
Binary files differ
diff --git a/wear/compose/material/api/current.txt b/wear/compose/material/api/current.txt
index b7a3b71..5f663c0 100644
--- a/wear/compose/material/api/current.txt
+++ b/wear/compose/material/api/current.txt
@@ -33,14 +33,16 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter cardBackgroundPainter(optional long startBackgroundColor, optional long endBackgroundColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
     method public float getAppImageSize();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter imageBackgroundPainter(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush);
     property public final float AppImageSize;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     field public static final androidx.wear.compose.material.CardDefaults INSTANCE;
   }
 
   public final class CardKt {
-    method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
+    method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
     method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long contentColor, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void TitleCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? time, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long titleColor, optional long timeColor, optional long bodyColor);
   }
 
   @androidx.compose.runtime.Stable public interface ChipColors {
diff --git a/wear/compose/material/api/public_plus_experimental_current.txt b/wear/compose/material/api/public_plus_experimental_current.txt
index b7a3b71..5f663c0 100644
--- a/wear/compose/material/api/public_plus_experimental_current.txt
+++ b/wear/compose/material/api/public_plus_experimental_current.txt
@@ -33,14 +33,16 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter cardBackgroundPainter(optional long startBackgroundColor, optional long endBackgroundColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
     method public float getAppImageSize();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter imageBackgroundPainter(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush);
     property public final float AppImageSize;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     field public static final androidx.wear.compose.material.CardDefaults INSTANCE;
   }
 
   public final class CardKt {
-    method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
+    method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
     method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long contentColor, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void TitleCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? time, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long titleColor, optional long timeColor, optional long bodyColor);
   }
 
   @androidx.compose.runtime.Stable public interface ChipColors {
diff --git a/wear/compose/material/api/restricted_current.txt b/wear/compose/material/api/restricted_current.txt
index b7a3b71..5f663c0 100644
--- a/wear/compose/material/api/restricted_current.txt
+++ b/wear/compose/material/api/restricted_current.txt
@@ -33,14 +33,16 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter cardBackgroundPainter(optional long startBackgroundColor, optional long endBackgroundColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
     method public float getAppImageSize();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter imageBackgroundPainter(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush);
     property public final float AppImageSize;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     field public static final androidx.wear.compose.material.CardDefaults INSTANCE;
   }
 
   public final class CardKt {
-    method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
+    method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
     method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long contentColor, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void TitleCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? time, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long titleColor, optional long timeColor, optional long bodyColor);
   }
 
   @androidx.compose.runtime.Stable public interface ChipColors {
diff --git a/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt b/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt
index ef3a6ee1..674c6da 100644
--- a/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt
+++ b/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt
@@ -226,14 +226,82 @@
         ) { MaterialTheme.colors.onSurfaceVariant2 }
 
     @Test
-    public fun app_chip_gives_default_colors(): Unit =
-        verifyAppCardColors(
-            { MaterialTheme.colors.primary },
-            { MaterialTheme.colors.primary },
-            { MaterialTheme.colors.onSurfaceVariant },
-            { MaterialTheme.colors.onSurface },
-            { MaterialTheme.colors.onSurfaceVariant2 },
-        )
+    public fun app_card_gives_default_colors() {
+        var expectedAppImageColor = Color.Transparent
+        var expectedAppColor = Color.Transparent
+        var expectedTimeColor = Color.Transparent
+        var expectedTitleColor = Color.Transparent
+        var expectedBodyColor = Color.Transparent
+        var actualBodyColor = Color.Transparent
+        var actualTitleColor = Color.Transparent
+        var actualTimeColor = Color.Transparent
+        var actualAppColor = Color.Transparent
+        var actualAppImageColor = Color.Transparent
+        val testBackground = Color.White
+
+        rule.setContentWithTheme {
+            expectedAppImageColor = MaterialTheme.colors.primary
+            expectedAppColor = MaterialTheme.colors.primary
+            expectedTimeColor = MaterialTheme.colors.onSurfaceVariant
+            expectedTitleColor = MaterialTheme.colors.onSurface
+            expectedBodyColor = MaterialTheme.colors.onSurfaceVariant2
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(testBackground)
+            ) {
+                AppCard(
+                    onClick = {},
+                    appName = { actualAppColor = LocalContentColor.current },
+                    appImage = { actualAppImageColor = LocalContentColor.current },
+                    time = { actualTimeColor = LocalContentColor.current },
+                    body = { actualBodyColor = LocalContentColor.current },
+                    title = { actualTitleColor = LocalContentColor.current },
+                    modifier = Modifier.testTag(TEST_TAG)
+                )
+            }
+        }
+
+        assertEquals(expectedAppImageColor, actualAppImageColor)
+        assertEquals(expectedAppColor, actualAppColor)
+        assertEquals(expectedTimeColor, actualTimeColor)
+        assertEquals(expectedTitleColor, actualTitleColor)
+        assertEquals(expectedBodyColor, actualBodyColor)
+    }
+
+    @Test
+    public fun title_card_gives_default_colors() {
+        var expectedTimeColor = Color.Transparent
+        var expectedTitleColor = Color.Transparent
+        var expectedBodyColor = Color.Transparent
+        var actualBodyColor = Color.Transparent
+        var actualTitleColor = Color.Transparent
+        var actualTimeColor = Color.Transparent
+        val testBackground = Color.White
+
+        rule.setContentWithTheme {
+            expectedTimeColor = MaterialTheme.colors.onSurfaceVariant
+            expectedTitleColor = MaterialTheme.colors.onSurface
+            expectedBodyColor = MaterialTheme.colors.onSurfaceVariant2
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(testBackground)
+            ) {
+                TitleCard(
+                    onClick = {},
+                    time = { actualTimeColor = LocalContentColor.current },
+                    body = { actualBodyColor = LocalContentColor.current },
+                    title = { actualTitleColor = LocalContentColor.current },
+                    modifier = Modifier.testTag(TEST_TAG)
+                )
+            }
+        }
+
+        assertEquals(expectedTimeColor, actualTimeColor)
+        assertEquals(expectedTitleColor, actualTitleColor)
+        assertEquals(expectedBodyColor, actualBodyColor)
+    }
 
     private fun verifyColors(
         status: CardStatus,
@@ -261,55 +329,6 @@
 
         assertEquals(expectedContent, actualContent)
     }
-
-    private fun verifyAppCardColors(
-        appImageColor: @Composable () -> Color,
-        appColor: @Composable () -> Color,
-        timeColor: @Composable () -> Color,
-        titleColor: @Composable () -> Color,
-        bodyColor: @Composable () -> Color,
-    ) {
-        var expectedAppImageColor = Color.Transparent
-        var expectedAppColor = Color.Transparent
-        var expectedTimeColor = Color.Transparent
-        var expectedTitleColor = Color.Transparent
-        var expectedBodyColor = Color.Transparent
-        var actualBodyColor = Color.Transparent
-        var actualTitleColor = Color.Transparent
-        var actualTimeColor = Color.Transparent
-        var actualAppColor = Color.Transparent
-        var actualAppImageColor = Color.Transparent
-        val testBackground = Color.White
-
-        rule.setContentWithTheme {
-            expectedAppImageColor = appImageColor()
-            expectedAppColor = appColor()
-            expectedTimeColor = timeColor()
-            expectedTitleColor = titleColor()
-            expectedBodyColor = bodyColor()
-            Box(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .background(testBackground)
-            ) {
-                AppCard(
-                    onClick = {},
-                    appName = { actualAppColor = LocalContentColor.current },
-                    appImage = { actualAppImageColor = LocalContentColor.current },
-                    time = { actualTimeColor = LocalContentColor.current },
-                    body = { actualBodyColor = LocalContentColor.current },
-                    title = { actualTitleColor = LocalContentColor.current },
-                    modifier = Modifier.testTag(TEST_TAG)
-                )
-            }
-        }
-
-        assertEquals(expectedAppImageColor, actualAppImageColor)
-        assertEquals(expectedAppColor, actualAppColor)
-        assertEquals(expectedTimeColor, actualTimeColor)
-        assertEquals(expectedTitleColor, actualTitleColor)
-        assertEquals(expectedBodyColor, actualBodyColor)
-    }
 }
 
 public class CardFontTest {
@@ -373,6 +392,39 @@
         assertEquals(expectedTitleTextStyle, actualTitleTextStyle)
         assertEquals(expectedBodyTextStyle, actualBodyTextStyle)
     }
+
+    @Test
+    public fun title_card_gives_correct_text_style_base() {
+        var actualTimeTextStyle = TextStyle.Default
+        var actualTitleTextStyle = TextStyle.Default
+        var actualBodyTextStyle = TextStyle.Default
+        var expectedTimeTextStyle = TextStyle.Default
+        var expectedTitleTextStyle = TextStyle.Default
+        var expectedBodyTextStyle = TextStyle.Default
+
+        rule.setContentWithTheme {
+            expectedTimeTextStyle = MaterialTheme.typography.caption1
+            expectedTitleTextStyle = MaterialTheme.typography.button
+            expectedBodyTextStyle = MaterialTheme.typography.body1
+
+            TitleCard(
+                onClick = {},
+                time = {
+                    actualTimeTextStyle = LocalTextStyle.current
+                },
+                title = {
+                    actualTitleTextStyle = LocalTextStyle.current
+                },
+                body = {
+                    actualBodyTextStyle = LocalTextStyle.current
+                },
+                modifier = Modifier.testTag(TEST_TAG)
+            )
+        }
+        assertEquals(expectedTimeTextStyle, actualTimeTextStyle)
+        assertEquals(expectedTitleTextStyle, actualTitleTextStyle)
+        assertEquals(expectedBodyTextStyle, actualBodyTextStyle)
+    }
 }
 
 private fun ComposeContentTestRule.verifyHeight(expected: Dp, content: @Composable () -> Unit) {
diff --git a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
index 566c192..d1d93f4 100644
--- a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
+++ b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
@@ -40,6 +40,7 @@
 import androidx.compose.ui.draw.paint
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.LinearGradientShader
 import androidx.compose.ui.graphics.Shader
@@ -48,6 +49,7 @@
 import androidx.compose.ui.graphics.TileMode
 import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.unit.Dp
@@ -110,6 +112,7 @@
                 .matchParentSize()
                 .paint(
                     painter = backgroundPainter,
+                    contentScale = ContentScale.FillBounds
                 )
 
         val contentBoxModifier = Modifier
@@ -140,7 +143,10 @@
 
 /**
  * Opinionated Wear Material [Card] that offers a specific 5 slot layout to show information about
- * an application, e.g. a notification.
+ * an application, e.g. a notification. AppCards are designed to show interactive elements from
+ * multiple applications. They will typically be used by the system UI, e.g. for showing a list of
+ * notifications from different applications. However it could also be adapted by individual
+ * application developers to show information about different parts of their application.
  *
  * The first row of the layout has three slots, 1) a small optional application [Image] or [Icon] of
  * size [CardDefaults.AppImageSize]x[CardDefaults.AppImageSize] dp, 2) an application name
@@ -154,13 +160,13 @@
  * The rest of the [Card] contains the body content which can be either [Text] or an [Image].
  *
  * @param onClick Will be called when the user clicks the card
- * @param modifier Modifier to be applied to the card
  * @param appName A slot for displaying the application name, expected to be a single line of text
  * of [Typography.title3]
  * @param time A slot for displaying the time relevant to the contents of the card, expected to be a
- * short piece of right aligned text.
+ * short piece of end aligned text.
  * @param body A slot for displaying the details of the [Card], expected to be either [Text]
  * (single or multiple-line) or an [Image]
+ * @param modifier Modifier to be applied to the card
  * @param appImage A slot for a small ([CardDefaults.AppImageSize]x[CardDefaults.AppImageSize] )
  * [Image] or [Icon] associated with the application.
  * @param backgroundPainter A painter used to paint the background of the card. A card will
@@ -175,11 +181,11 @@
 @Composable
 public fun AppCard(
     onClick: () -> Unit,
-    modifier: Modifier = Modifier,
     appName: @Composable () -> Unit,
     time: @Composable () -> Unit,
     title: @Composable () -> Unit,
     body: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
     appImage: @Composable (() -> Unit)? = null,
     backgroundPainter: Painter = CardDefaults.cardBackgroundPainter(),
     appColor: Color = MaterialTheme.colors.primary,
@@ -233,6 +239,88 @@
 }
 
 /**
+ * Opinionated Wear Material [Card] that offers a specific 3 slot layout to show interactive
+ * information about an application, e.g. a message. TitleCards are designed for use within an
+ * application.
+ *
+ * The first row of the layout has two slots. 1. a start aligned title (emphasised with the
+ * [titleColor] and expected to be start aligned text). The title text is expected to be a maximum
+ * of 2 lines of text. 2. An optional time that the application activity has occurred shown at the
+ * end of the row, expected to be an end aligned [Text] composable showing a time relevant to the
+ * contents of the [Card].
+ *
+ * The rest of the [Card] contains the body content which is expected to be [Text] or a contained
+ * [Image].
+ *
+ * Overall the [title] and [body] text should be no more than 5 rows of text combined.
+ *
+ * @param onClick Will be called when the user clicks the card
+ * @param title A slot for displaying the title of the card, expected to be one or two lines of text
+ * of [Typography.button]
+ * @param body A slot for displaying the details of the [Card], expected to be either [Text]
+ * (single or multiple-line) or an [Image]. If [Text] then it is expected to be a maximum of 4 lines
+ * of text of [Typography.body1]
+ * @param modifier Modifier to be applied to the card
+ * @param time An optional slot for displaying the time relevant to the contents of the card,
+ * expected to be a short piece of end aligned text.
+ * @param backgroundPainter A painter used to paint the background of the card. A title card can
+ * have either a gradient background or an image background, use
+ * [CardDefaults.cardBackgroundPainter()] or [CardDefaults.imageBackgroundPainter()] to obtain an
+ * appropriate painter
+ * @param titleColor The default color to use for title() slot unless explicitly set.
+ * @param timeColor The default color to use for time() slot unless explicitly set.
+ * @param bodyColor The default color to use for body() slot unless explicitly set.
+ */
+@Composable
+public fun TitleCard(
+    onClick: () -> Unit,
+    title: @Composable () -> Unit,
+    body: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    time: @Composable (() -> Unit)? = null,
+    backgroundPainter: Painter = CardDefaults.cardBackgroundPainter(),
+    titleColor: Color = MaterialTheme.colors.onSurface,
+    timeColor: Color = MaterialTheme.colors.onSurfaceVariant,
+    bodyColor: Color = MaterialTheme.colors.onSurfaceVariant2,
+) {
+    Card(
+        onClick = onClick,
+        modifier = modifier,
+        backgroundPainter = backgroundPainter,
+        enabled = true,
+    ) {
+        Column {
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                verticalAlignment = Alignment.CenterVertically
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides titleColor,
+                    LocalTextStyle provides MaterialTheme.typography.button,
+                    content = title
+                )
+                if (time != null) {
+                    Spacer(modifier = Modifier.width(4.dp))
+                    Box(modifier = Modifier.weight(1.0f), contentAlignment = Alignment.CenterEnd) {
+                        CompositionLocalProvider(
+                            LocalContentColor provides timeColor,
+                            LocalTextStyle provides MaterialTheme.typography.caption1,
+                            content = time
+                        )
+                    }
+                }
+            }
+            Spacer(modifier = Modifier.height(8.dp))
+            CompositionLocalProvider(
+                LocalContentColor provides bodyColor,
+                LocalTextStyle provides MaterialTheme.typography.body1,
+                content = body
+            )
+        }
+    }
+}
+
+/**
  * Contains the default values used by [Card]
  */
 public object CardDefaults {
@@ -274,6 +362,35 @@
         return BrushPainter(FortyFiveDegreeLinearGradient(backgroundColors))
     }
 
+    /**
+     * Creates a [Painter] for the background of a [Card] that displays an Image with a scrim over
+     * the image to make sure that any content above the background will be legible.
+     *
+     * An Image background is a means to reinforce the meaning of information in a Card, e.g. To
+     * help to contextualize the information in a TitleCard
+     *
+     * Cards should have a content color that contrasts with the background image and scrim
+     *
+     * @param backgroundImagePainter The [Painter] to use to draw the background of the [Card]
+     * @param backgroundImageScrimBrush The [Brush] to use to paint a scrim over the background
+     * image to ensure that any text drawn over the image is legible
+     */
+    @Composable
+    public fun imageBackgroundPainter(
+        backgroundImagePainter: Painter,
+        backgroundImageScrimBrush: Brush = Brush.linearGradient(
+            colors = listOf(
+                MaterialTheme.colors.surface.copy(alpha = 1.0f),
+                MaterialTheme.colors.surface.copy(alpha = 0f)
+            )
+        )
+    ): Painter {
+        return ImageWithScrimPainter(
+            imagePainter = backgroundImagePainter,
+            brush = backgroundImageScrimBrush
+        )
+    }
+
     private val CardHorizontalPadding = 12.dp
     private val CardVerticalPadding = 12.dp