diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2737713f..38d754ac 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -57,7 +57,7 @@ material3Android = "1.3.2"
media3 = "1.5.0"
constraintlayout = "2.1.4"
glide-compose = "1.0.0-beta01"
-glance = "1.1.0"
+glance = "1.3.0-alpha01"
tensorflowLite = "2.9.0"
tensorflowLiteGpuDelegatePlugin = "0.4.4"
tensorflowLiteSupport = "0.4.2"
diff --git a/samples/user-interface/appwidgets/build.gradle.kts b/samples/user-interface/appwidgets/build.gradle.kts
index 8be71454..b7d7b28f 100644
--- a/samples/user-interface/appwidgets/build.gradle.kts
+++ b/samples/user-interface/appwidgets/build.gradle.kts
@@ -23,10 +23,10 @@ plugins {
android {
namespace = "com.example.platform.ui.appwidgets"
- compileSdk = 36
+ compileSdk = 37
defaultConfig {
- minSdk = 21
+ minSdk = 23
}
compileOptions {
diff --git a/samples/user-interface/appwidgets/src/main/AndroidManifest.xml b/samples/user-interface/appwidgets/src/main/AndroidManifest.xml
index 2baf4e0a..c24d88cc 100644
--- a/samples/user-interface/appwidgets/src/main/AndroidManifest.xml
+++ b/samples/user-interface/appwidgets/src/main/AndroidManifest.xml
@@ -104,6 +104,19 @@
android:name="android.appwidget.provider"
android:resource="@xml/sample_text_with_image_widget_info" />
+
+
+
+
+
+
+
= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ lifecycleScope.launch {
+ try {
+ val context = this@CanonicalLayoutActivity
+ val receiver = FullBleedImageAppWidgetReceiver::class.java
+ val glanceAppWidgetManager = GlanceAppWidgetManager(context)
+ val appWidgetManager = context.getSystemService(AppWidgetManager::class.java)
+
+ val providerInfo = appWidgetManager?.installedProviders?.firstOrNull {
+ it.provider.className == receiver.name
+ }
+
+ if (providerInfo?.generatedPreviewCategories == 0) {
+ val result = glanceAppWidgetManager.setWidgetPreviews(FullBleedImageAppWidgetReceiver::class)
+ val status = when (result) {
+ GlanceAppWidgetManager.SET_WIDGET_PREVIEWS_RESULT_SUCCESS -> "Success"
+ GlanceAppWidgetManager.SET_WIDGET_PREVIEWS_RESULT_RATE_LIMITED -> "Rate-Limited"
+ else -> "Error ($result)"
+ }
+ Log.i("CanonicalLayoutActivity", "Published previews for ${receiver.simpleName}: $status")
+ }
+ } catch (e: Exception) {
+ Log.e("CanonicalLayoutActivity", "Failed to set widget previews", e)
+ }
+ }
+ }
+
setContent {
MaterialTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
@@ -195,7 +246,7 @@ private fun CanonicalLayoutRow(widget: CanonicalLayoutRowData, modifier: Modifie
painter = painterResource(id = widget.imageRes),
contentDescription = "Screenshot of ${widget.rowTitle}",
contentScale = ContentScale.FillWidth,
- modifier = modifier,
+ modifier = modifier.fillMaxWidth(),
)
}
@@ -287,6 +338,12 @@ private val canonicalLayoutWidgets = listOf(
imageRes = R.drawable.cl_activity_row_text_image,
receiver = TextWithImageAppWidgetReceiver::class.java,
),
+ CanonicalLayoutRowData(
+ rowTitle = R.string.cl_title_full_bleed_image,
+ rowDescription = R.string.cl_description_full_bleed_image,
+ imageRes = R.drawable.cl_activity_row_full_bleed_image,
+ receiver = FullBleedImageAppWidgetReceiver::class.java,
+ ),
CanonicalLayoutRowData(
rowTitle = R.string.cl_title_grid,
rowDescription = R.string.cl_description_grid,
diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt
index e8e7518e..03e72789 100644
--- a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt
+++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.example.platform.ui.appwidgets.glance.layout.collections.data
import android.content.Context
@@ -16,11 +32,11 @@ import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Image
import com.example.platform.ui.appwidgets.glance.layout.utils.ImageUtils.getMaxPossibleImageSize
import com.example.platform.ui.appwidgets.glance.layout.utils.ImageUtils.getMaxWidgetMemoryAllowedSizeInBytes
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.runBlocking
import com.example.platform.ui.appwidgets.glance.layout.computeIfAbsent as computeIfAbsentExt
/**
* A fake in-memory implementation of repository that produces a list of
@@ -74,7 +90,7 @@ class FakeImageGridDataRepository {
val width = IMAGE_SIZE.coerceAtMost(imageSizeLimit.width)
val height = width * 9 / 16
- val mappedItems = runBlocking {
+ val mappedItems = coroutineScope {
items.map { item ->
async(Dispatchers.IO) {
var bitmap: Bitmap? = null
diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/FullBleedImageAppWidget.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/FullBleedImageAppWidget.kt
new file mode 100644
index 00000000..8f26e61a
--- /dev/null
+++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/FullBleedImageAppWidget.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2026 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 details; and limitations under the License.
+ */
+
+package com.example.platform.ui.appwidgets.glance.layout.text
+
+import android.content.Context
+import android.util.Log
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.glance.GlanceId
+import androidx.glance.GlanceTheme
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetManager
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.provideContent
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import com.example.platform.ui.appwidgets.glance.layout.collections.data.FakeImageGridDataRepository
+import com.example.platform.ui.appwidgets.glance.layout.collections.data.FakeImageGridDataRepository.Companion.getImageGridDataRepo
+import com.example.platform.ui.appwidgets.glance.layout.text.layout.FullBleedImageLayout
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+/**
+ * Glance widget showcasing Full Bleed Snap Scrolling, powered by [FakeImageGridDataRepository].
+ */
+class FullBleedImageAppWidget : GlanceAppWidget() {
+ override val sizeMode: SizeMode = SizeMode.Exact
+
+ override val previewSizeMode = SizeMode.Responsive(
+ setOf(
+ DpSize(109.dp, 115.dp)
+ )
+ )
+
+ override suspend fun provideGlance(context: Context, id: GlanceId) {
+ val repo = getImageGridDataRepo(id)
+
+ val initialData = withContext(Dispatchers.IO) {
+ repo.load(context)
+ }
+
+ provideContent {
+ val data by repo.data().collectAsState(initial = initialData)
+
+ GlanceTheme {
+ FullBleedImageLayout(
+ data = data
+ )
+ }
+ }
+ }
+
+ override suspend fun providePreview(context: Context, widgetCategory: Int) {
+ val repo = FakeImageGridDataRepository()
+
+ val initialData = withContext(Dispatchers.IO) {
+ repo.load(context)
+ }
+
+ provideContent {
+ GlanceTheme {
+ FullBleedImageLayout(
+ data = initialData.take(1)
+ )
+ }
+ }
+ }
+}
+
+/**
+ * Receiver for the Full Bleed Snap Scrolling widget.
+ */
+class FullBleedImageAppWidgetReceiver : GlanceAppWidgetReceiver() {
+ override val glanceAppWidget: GlanceAppWidget = FullBleedImageAppWidget()
+
+ override fun onDeleted(context: Context, appWidgetIds: IntArray) {
+ val glanceAppWidgetManager = GlanceAppWidgetManager(context)
+ appWidgetIds.forEach { id ->
+ try {
+ val glanceId = glanceAppWidgetManager.getGlanceIdBy(id)
+ FakeImageGridDataRepository.cleanUp(glanceId)
+ } catch (e: IllegalArgumentException) {
+ Log.w("FullBleedImageReceiver", "Skipping cleanup for invalid AppWidget ID: $id", e)
+ }
+ }
+ super.onDeleted(context, appWidgetIds)
+ }
+}
diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/FullBleedImageLayout.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/FullBleedImageLayout.kt
new file mode 100644
index 00000000..df8c63e3
--- /dev/null
+++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/FullBleedImageLayout.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.platform.ui.appwidgets.glance.layout.text.layout
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.DpSize
+import androidx.glance.GlanceModifier
+import androidx.glance.Image
+import androidx.glance.ImageProvider
+import androidx.glance.LocalContext
+import androidx.glance.LocalSize
+import androidx.glance.appwidget.cornerRadius
+import androidx.glance.appwidget.lazy.LazyColumn
+import androidx.glance.appwidget.lazy.VerticalScrollMode
+import androidx.glance.appwidget.lazy.items
+import androidx.glance.background
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Box
+import androidx.glance.layout.Column
+import androidx.glance.layout.ContentScale
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.fillMaxWidth
+import androidx.glance.layout.height
+import androidx.glance.layout.padding
+import androidx.glance.layout.size
+import androidx.glance.layout.width
+import androidx.glance.text.FontWeight
+import androidx.glance.text.Text
+import androidx.glance.text.TextStyle
+import androidx.glance.unit.ColorProvider
+import com.example.platform.ui.appwidgets.R
+import com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageGridItemData
+import com.example.platform.ui.appwidgets.glance.layout.collections.layout.NoDataContent
+import com.example.platform.ui.appwidgets.glance.layout.utils.ActionUtils.actionStartDemoActivity
+import com.example.platform.ui.appwidgets.glance.layout.utils.MediumWidgetPreview
+import com.example.platform.ui.appwidgets.glance.layout.utils.SmallWidgetPreview
+
+/**
+ * A full bleed snap scrolling gallery canonical layout using [ImageGridItemData].
+ *
+ * Each item displays an edge-to-edge background photo with overlaid
+ * title and caption details that auto-scale to fit the current widget size.
+ */
+@Composable
+fun FullBleedImageLayout(
+ data: List? = null,
+) {
+ val size = LocalSize.current
+ val isSmall = size.height <= 110.dp
+ val appName = LocalContext.current.getString(R.string.sample_full_bleed_image_app_widget_name)
+
+ Box(
+ modifier = GlanceModifier
+ .fillMaxSize()
+ .cornerRadius(16.dp)
+ ) {
+ if (data.isNullOrEmpty()) {
+ val context = LocalContext.current
+ NoDataContent(
+ noDataText = context.getString(R.string.sample_no_data_text),
+ noDataIconRes = R.drawable.sample_no_data_icon,
+ actionButtonText = context.getString(R.string.sample_learn_more_button_text),
+ actionButtonIcon = R.drawable.sample_info_icon,
+ actionButtonOnClick = actionStartDemoActivity("on-click of info button in no data view")
+ )
+ } else if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1) {
+ SnapScrollingGallery(
+ data = data,
+ isSmall = isSmall,
+ appName = appName,
+ size = size
+ )
+ } else {
+ // Show a standard scrolling list of items without Snap Scrolling
+ // TODO: Remove once Snap Scrolling gracefully degrades
+ GalleryList(
+ data = data,
+ isSmall = isSmall,
+ appName = appName,
+ size = size
+ )
+ }
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES_FULL.BAKLAVA_1)
+@Composable
+private fun SnapScrollingGallery(
+ data: List,
+ isSmall: Boolean,
+ appName: String,
+ size: DpSize,
+) {
+ GalleryList(
+ data = data,
+ isSmall = isSmall,
+ appName = appName,
+ size = size,
+ verticalScrollMode = VerticalScrollMode.SnapScrollMatchHeight(size.height)
+ )
+}
+
+@Composable
+private fun GalleryList(
+ data: List,
+ isSmall: Boolean,
+ appName: String,
+ size: DpSize,
+ verticalScrollMode: VerticalScrollMode = VerticalScrollMode.Normal
+) {
+ if (data.size == 1) {
+ // If there's only 1 item (like in the widget preview), render with fillMaxSize to
+ // bypass LazyColumn measurement issues where the generated widget preview item doesn't
+ // fill the widget bounds.
+ GalleryItemCard(
+ item = data[0],
+ isSmall = isSmall,
+ appName = appName,
+ modifier = GlanceModifier.fillMaxSize()
+ )
+ } else {
+ val limitedData = data.take(5)
+ LazyColumn(
+ modifier = GlanceModifier.fillMaxSize(),
+ verticalScrollMode = verticalScrollMode
+ ) {
+ items(limitedData, itemId = { item -> item.key.hashCode().toLong() }) { item ->
+ GalleryItemCard(
+ item = item,
+ isSmall = isSmall,
+ appName = appName,
+ modifier = GlanceModifier.width(size.width).height(size.height)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun GalleryItemCard(
+ item: ImageGridItemData,
+ isSmall: Boolean,
+ appName: String,
+ modifier: GlanceModifier = GlanceModifier,
+) {
+ val itemTitle = item.title ?: ""
+
+ Box(
+ modifier = modifier,
+ contentAlignment = Alignment.BottomStart
+ ) {
+ val imageProvider = item.image?.let { ImageProvider(it) } ?: ImageProvider(R.drawable.sample_placeholder_image)
+
+ Image(
+ provider = imageProvider,
+ contentDescription = item.imageContentDescription ?: itemTitle,
+ contentScale = ContentScale.Crop,
+ modifier = GlanceModifier.fillMaxSize()
+ )
+
+ val titleFontSize = if (isSmall) {
+ WidgetTextDimensions.primaryTextFontSizeAndMaxLines(itemTitle).first
+ } else {
+ WidgetTextDimensions.maxPrimaryTextFontSize
+ }
+
+ Column(
+ modifier = GlanceModifier
+ .fillMaxWidth()
+ // Implementing a partial gradient scrim by applying a background modifier directly
+ // to a text Column results in the gradient stretching to fill the entire widget.
+ .background(ImageProvider(R.drawable.sample_scrim_gradient))
+ .padding(WidgetTextDimensions.widgetPadding),
+ verticalAlignment = Alignment.Bottom,
+ ) {
+ // App Logo Icon styled as a fixed size monochrome asset above the Title as the Caption
+ Image(
+ provider = ImageProvider(R.drawable.sample_app_logo),
+ contentDescription = appName,
+ contentScale = ContentScale.Fit,
+ modifier = GlanceModifier.size(24.dp)
+ )
+
+ if (itemTitle.isNotEmpty()) {
+ Text(
+ text = itemTitle,
+ style = TextStyle(
+ color = ColorProvider(Color.White),
+ fontWeight = FontWeight.Bold,
+ fontSize = titleFontSize
+ ),
+ modifier = GlanceModifier.fillMaxWidth().padding(top = 4.dp)
+ )
+ }
+ }
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES_FULL.BAKLAVA_1)
+@SmallWidgetPreview
+@MediumWidgetPreview
+@Composable
+private fun FullBleedImageLayoutPreview() {
+ val previewItems = listOf(
+ ImageGridItemData(
+ key = "0",
+ title = "Yosemite Valley under clear blue skies",
+ image = null,
+ imageContentDescription = null
+ ),
+ ImageGridItemData(
+ key = "1",
+ title = "Mystical forest lake reflection at sunrise",
+ image = null,
+ imageContentDescription = null
+ )
+ )
+
+ FullBleedImageLayout(
+ data = previewItems
+ )
+}
diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt
index 4adb7121..417e2b9a 100644
--- a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt
+++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt
@@ -40,9 +40,9 @@ import androidx.glance.layout.padding
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import com.example.platform.ui.appwidgets.R
-import com.example.platform.ui.appwidgets.glance.layout.text.layout.LongTextLayoutDimensions.captionFontSizeAndMaxLines
-import com.example.platform.ui.appwidgets.glance.layout.text.layout.LongTextLayoutDimensions.widgetPadding
-import com.example.platform.ui.appwidgets.glance.layout.text.layout.LongTextLayoutDimensions.primaryTextFontSizeAndMaxLines
+import com.example.platform.ui.appwidgets.glance.layout.text.layout.WidgetTextDimensions.captionFontSizeAndMaxLines
+import com.example.platform.ui.appwidgets.glance.layout.text.layout.WidgetTextDimensions.widgetPadding
+import com.example.platform.ui.appwidgets.glance.layout.text.layout.WidgetTextDimensions.primaryTextFontSizeAndMaxLines
import com.example.platform.ui.appwidgets.glance.layout.utils.ActionUtils.actionStartDemoActivity
import com.example.platform.ui.appwidgets.glance.layout.utils.FontUtils.calculateFontSizeAndMaxLines
import com.example.platform.ui.appwidgets.glance.layout.utils.MediumWidgetPreview
@@ -221,7 +221,7 @@ data class LongTextLayoutData(
val caption: String,
)
-private object LongTextLayoutDimensions {
+internal object WidgetTextDimensions {
val widgetPadding = 16.dp
private val titleBarHeight: Dp
@Composable get() = if (LongTextLayoutSize.fromLocalSize() == LongTextLayoutSize.XSmall) {
@@ -242,14 +242,14 @@ private object LongTextLayoutDimensions {
}
// Upper and lower bounds for the caption.
- private val minCaptionFontSize = 12.sp // low - GM3 Label Medium
- private val maxCaptionFontSize = 14.sp // high - GM3 Label Large
+ internal val minCaptionFontSize = 12.sp // low - GM3 Label Medium
+ internal val maxCaptionFontSize = 14.sp // high - GM3 Label Large
// Upper bound for primary text.
- private val maxPrimaryTextFontSize = 28.sp // GM3 Headline Medium
+ internal val maxPrimaryTextFontSize = 28.sp // GM3 Headline Medium
// For a font size 16 of primary text, we want caption to be of size 14.
- private const val captionToPrimaryTextRatio = 0.875f
+ internal const val captionToPrimaryTextRatio = 0.875f
@Composable
fun primaryTextFontSizeAndMaxLines(text: String): Pair {
diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt
index 3ad49989..f472c638 100644
--- a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt
+++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.example.platform.ui.appwidgets.glance.layout.utils
import android.content.Context
diff --git a/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/cl_activity_row_full_bleed_image.png b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/cl_activity_row_full_bleed_image.png
new file mode 100644
index 00000000..7fda3a9c
Binary files /dev/null and b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/cl_activity_row_full_bleed_image.png differ
diff --git a/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/sample_full_bleed_image_preview.png b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/sample_full_bleed_image_preview.png
new file mode 100644
index 00000000..265b91f2
Binary files /dev/null and b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/sample_full_bleed_image_preview.png differ
diff --git a/samples/user-interface/appwidgets/src/main/res/drawable/sample_scrim_gradient.xml b/samples/user-interface/appwidgets/src/main/res/drawable/sample_scrim_gradient.xml
new file mode 100644
index 00000000..5c79e094
--- /dev/null
+++ b/samples/user-interface/appwidgets/src/main/res/drawable/sample_scrim_gradient.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml b/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml
index 19ba7de3..3fafe678 100644
--- a/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml
+++ b/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml
@@ -1,4 +1,19 @@
+
diff --git a/samples/user-interface/appwidgets/src/main/res/values/dimens.xml b/samples/user-interface/appwidgets/src/main/res/values/dimens.xml
index 1df1cc6a..50d6bdbe 100644
--- a/samples/user-interface/appwidgets/src/main/res/values/dimens.xml
+++ b/samples/user-interface/appwidgets/src/main/res/values/dimens.xml
@@ -134,5 +134,13 @@
624dp
422dp
+
+ 2
+ 2
+ 120dp
+ 48dp
+ 120dp
+ 48dp
+
\ No newline at end of file
diff --git a/samples/user-interface/appwidgets/src/main/res/values/strings.xml b/samples/user-interface/appwidgets/src/main/res/values/strings.xml
index f2518f03..e53e8022 100644
--- a/samples/user-interface/appwidgets/src/main/res/values/strings.xml
+++ b/samples/user-interface/appwidgets/src/main/res/values/strings.xml
@@ -89,4 +89,8 @@
Search Toolbar
Ideal for apps where search is paramount, this layout provides a dedicated search entry point while allowing for additional shortcuts based on available widget space.
+ Full Bleed Snap Scrolling
+ Full Bleed Snap Scrolling
+ A full bleed gallery widget demonstrating snap scrolling. Scroll vertically between featured items with an overlaid title and caption.
+
\ No newline at end of file
diff --git a/samples/user-interface/appwidgets/src/main/res/xml/sample_full_bleed_image_widget_info.xml b/samples/user-interface/appwidgets/src/main/res/xml/sample_full_bleed_image_widget_info.xml
new file mode 100644
index 00000000..6f076990
--- /dev/null
+++ b/samples/user-interface/appwidgets/src/main/res/xml/sample_full_bleed_image_widget_info.xml
@@ -0,0 +1,26 @@
+
+