diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0848349e..4d750217 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -23,12 +23,12 @@ plugins {
android {
namespace = "com.example.platform"
- compileSdk = 35
+ compileSdk = 36
defaultConfig {
applicationId = "com.example.platform"
minSdk = 21
- targetSdk = 35
+ targetSdk = 36
versionCode = 1
versionName = "1.0"
@@ -92,6 +92,7 @@ dependencies {
implementation(project(":samples:user-interface:constraintlayout"))
implementation(project(":samples:user-interface:draganddrop"))
implementation(project(":samples:user-interface:haptics"))
+ implementation(project(":samples:user-interface:live-updates"))
implementation(project(":samples:user-interface:picture-in-picture"))
implementation(project(":samples:user-interface:predictiveback"))
implementation(project(":samples:user-interface:quicksettings"))
diff --git a/app/src/main/java/com/example/platform/app/ApiSurface.kt b/app/src/main/java/com/example/platform/app/ApiSurface.kt
index a98278fe..ea995c2d 100644
--- a/app/src/main/java/com/example/platform/app/ApiSurface.kt
+++ b/app/src/main/java/com/example/platform/app/ApiSurface.kt
@@ -141,6 +141,12 @@ val UserInterfaceHapticsApiSurface = ApiSurface(
null,
)
+val UserInterfaceLiveUpdatesApiSurface = ApiSurface(
+ "live-updates",
+ "User Interface - Live Updates",
+ null,
+)
+
val UserInterfacePictureInPictureApiSurface = ApiSurface(
"user-interface-picture-in-picture",
"User Interface - Picture In Picture",
@@ -204,6 +210,7 @@ val API_SURFACES = listOf(
UserInterfaceConstraintLayoutApiSurface,
UserInterfaceDragAndDropApiSurface,
UserInterfaceHapticsApiSurface,
+ UserInterfaceLiveUpdatesApiSurface,
UserInterfacePictureInPictureApiSurface,
UserInterfacePredictiveBackApiSurface,
UserInterfaceQuickSettingsApiSurface,
diff --git a/app/src/main/java/com/example/platform/app/SampleDemo.kt b/app/src/main/java/com/example/platform/app/SampleDemo.kt
index 1c87b11a..4b27c359 100644
--- a/app/src/main/java/com/example/platform/app/SampleDemo.kt
+++ b/app/src/main/java/com/example/platform/app/SampleDemo.kt
@@ -111,6 +111,7 @@ import com.example.platform.ui.haptics.Resist
import com.example.platform.ui.haptics.Wobble
import com.example.platform.ui.insets.ImmersiveMode
import com.example.platform.ui.insets.WindowInsetsAnimationActivity
+import com.example.platform.ui.live_updates.LiveUpdateSample
import com.example.platform.ui.predictiveback.PBHostingActivity
import com.example.platform.ui.quicksettings.QuickSettings
import com.example.platform.ui.share.receiver.ShareReceiverActivity
@@ -993,6 +994,20 @@ val SAMPLE_DEMOS by lazy {
tags = listOf("Haptics"),
content = { Wobble() }
),
+ ComposableSampleDemo(
+ id = "live-updates",
+ name = "Live Updates - ProgressStyle implementation",
+ description = "Usage of ProgressStyle with Live update treatment",
+ documentation = "https://developer.android.com/about/versions/16/features/progress-centric-notifications",
+ minSdk = Build.VERSION_CODES.BAKLAVA,
+ apiSurface = UserInterfaceLiveUpdatesApiSurface,
+ content = {
+ MinSdkBox(minSdk = Build.VERSION_CODES.BAKLAVA) {
+ //noinspection NewApi
+ LiveUpdateSample()
+ }
+ },
+ ),
ActivitySampleDemo(
id = "picture-in-picture-video-playback",
name = "Picture in Picture (PiP) - Video playback",
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 23b2ce6d..3e3dbbd9 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -14,7 +14,7 @@
# limitations under the License.
#
[versions]
-agp = "8.8.1"
+agp = "8.9.1"
fragmentCompose = "1.8.6"
kotlin = "2.1.10"
coreKtx = "1.15.0"
@@ -51,6 +51,7 @@ androidxTestExtTruth = "1.5.0"
androidxTestRules = "1.5.0"
androidxTestRunner = "1.5.2"
androidxUiAutomator = "2.2.0"
+material3Android = "1.3.2"
media3 = "1.5.0"
constraintlayout = "2.1.4"
glide-compose = "1.0.0-beta01"
@@ -162,6 +163,7 @@ androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
androidx-draganddrop = "androidx.draganddrop:draganddrop:1.0.0"
androidx-dynamicanimation = "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03"
+androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
androidx-media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" }
androidx-media3-effect = { module = "androidx.media3:media3-effect", version.ref = "media3" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
@@ -185,6 +187,7 @@ android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
android-library = { id = "com.android.library", version.ref = "agp" }
+compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
affectedmoduledetector = { id = "com.dropbox.affectedmoduledetector", version = "0.2.0" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version = "0.7.0" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c0..a4b76b95 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4a69aff0..e2847c82 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Fri Feb 14 20:10:12 KST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/samples/user-interface/live-updates/build.gradle.kts b/samples/user-interface/live-updates/build.gradle.kts
new file mode 100644
index 00000000..b03e52b1
--- /dev/null
+++ b/samples/user-interface/live-updates/build.gradle.kts
@@ -0,0 +1,32 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.compose)
+}
+
+android {
+ namespace = "com.example.platform.ui.live_updates"
+ compileSdk = 36
+
+ defaultConfig {
+ minSdk = 21
+ targetSdk = 36
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
+ buildFeatures {
+ viewBinding = true
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.material)
+ implementation(libs.accompanist.permissions)
+ implementation(libs.androidx.constraintlayout)
+ implementation(libs.androidx.material3.android)
+}
\ No newline at end of file
diff --git a/samples/user-interface/live-updates/src/main/AndroidManifest.xml b/samples/user-interface/live-updates/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..165bdb3a
--- /dev/null
+++ b/samples/user-interface/live-updates/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/user-interface/live-updates/src/main/java/com/example/platform/ui/live_updates/LiveUpdateSample.kt b/samples/user-interface/live-updates/src/main/java/com/example/platform/ui/live_updates/LiveUpdateSample.kt
new file mode 100644
index 00000000..8eebd009
--- /dev/null
+++ b/samples/user-interface/live-updates/src/main/java/com/example/platform/ui/live_updates/LiveUpdateSample.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.live_updates
+
+import android.annotation.SuppressLint
+import android.app.NotificationManager
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.layout.Box
+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.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
+import com.google.accompanist.permissions.shouldShowRationale
+import kotlinx.coroutines.launch
+
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+fun LiveUpdateSample() {
+ val notificationManager =
+ LocalContext.current.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ SnackbarNotificationManager.initialize(LocalContext.current.applicationContext, notificationManager)
+ val scope = rememberCoroutineScope()
+ val snackbarHostState = remember { SnackbarHostState() }
+ Scaffold(
+ snackbarHost = {
+ SnackbarHost(hostState = snackbarHostState)
+ },
+ ) { contentPadding ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(contentPadding),
+ ) {
+ Text(stringResource( R.string.live_update_summary_text))
+ Spacer(modifier = Modifier.height(4.dp))
+ NotificationPermission()
+ Button(onClick = {
+ onCheckout()
+ scope.launch {
+ snackbarHostState.showSnackbar("Order placed")
+ }
+ }) {
+ Text("Checkout")
+ }
+ }
+ }
+}
+
+fun onCheckout() {
+ SnackbarNotificationManager.start()
+}
+
+@OptIn(ExperimentalPermissionsApi::class)
+@Composable
+fun NotificationPermission() {
+ @SuppressLint("InlinedApi") // Granted at install time on API <33.
+ val notificationPermissionState = rememberPermissionState(
+ android.Manifest.permission.POST_NOTIFICATIONS,
+ )
+ if (!notificationPermissionState.status.isGranted) {
+ NotificationPermissionCard(
+ shouldShowRationale = notificationPermissionState.status.shouldShowRationale,
+ onGrantClick = {
+ notificationPermissionState.launchPermissionRequest()
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ )
+ }
+}
+
+@Composable
+private fun NotificationPermissionCard(
+ shouldShowRationale: Boolean,
+ onGrantClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Card(
+ modifier = modifier,
+ ) {
+ Text(
+ text = stringResource(R.string.permission_message),
+ modifier = Modifier.padding(16.dp),
+ )
+ if (shouldShowRationale) {
+ Text(
+ text = stringResource(R.string.permission_rationale),
+ modifier = Modifier.padding(horizontal = 10.dp),
+ )
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(10.dp),
+ contentAlignment = Alignment.BottomEnd,
+ ) {
+ Button(onClick = onGrantClick) {
+ Text(text = stringResource(R.string.permission_grant))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/user-interface/live-updates/src/main/java/com/example/platform/ui/live_updates/SnackbarNotificationManager.kt b/samples/user-interface/live-updates/src/main/java/com/example/platform/ui/live_updates/SnackbarNotificationManager.kt
new file mode 100644
index 00000000..ddcfc312
--- /dev/null
+++ b/samples/user-interface/live-updates/src/main/java/com/example/platform/ui/live_updates/SnackbarNotificationManager.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.live_updates
+
+import android.app.Notification
+import android.app.Notification.ProgressStyle
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.content.Context
+import android.graphics.Color
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import androidx.annotation.RequiresApi
+import androidx.core.graphics.drawable.IconCompat
+
+object SnackbarNotificationManager {
+ private lateinit var notificationManager: NotificationManager
+ private lateinit var appContext: Context
+ const val CHANNEL_ID = "live_updates_channel_id"
+ private const val CHANNEL_NAME = "live_updates_channel_name"
+ private const val NOTIFICATION_ID = 1234
+
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun initialize(context: Context, notifManager: NotificationManager) {
+ notificationManager = notifManager
+ val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_DEFAULT)
+ appContext = context
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ private enum class OrderState(val delay: Long) {
+ INITIALIZING(5000) {
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ override fun buildNotification(): Notification.Builder {
+ return buildBaseNotification(appContext, INITIALIZING)
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setContentTitle("You order is being placed")
+ .setContentText("Confirming with bakery...")
+ .setStyle(buildBaseProgressStyle(INITIALIZING).setProgressIndeterminate(true))
+ }
+ },
+ FOOD_PREPARATION(9000) {
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ override fun buildNotification(): Notification.Builder {
+ return buildBaseNotification(appContext, FOOD_PREPARATION)
+ .setContentTitle("Your order is being prepared")
+ .setContentText("Next step will be delivery")
+ .setLargeIcon(
+ IconCompat.createWithResource(
+ appContext, R.drawable.cupcake
+ ).toIcon(appContext)
+ )
+ .setStyle(buildBaseProgressStyle(FOOD_PREPARATION).setProgress(25))
+ }
+ },
+ FOOD_ENROUTE(13000) {
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ override fun buildNotification(): Notification.Builder {
+ return buildBaseNotification(appContext, FOOD_ENROUTE)
+ .setContentTitle("Your order is on its way")
+ .setContentText("Enroute to destination")
+ .setStyle(
+ buildBaseProgressStyle(FOOD_ENROUTE)
+ .setProgressTrackerIcon(
+ IconCompat.createWithResource(
+ appContext, R.drawable.shopping_bag
+ ).toIcon(appContext)
+ )
+ .setProgress(50)
+ )
+ .setLargeIcon(
+ IconCompat.createWithResource(
+ appContext, R.drawable.cupcake
+ ).toIcon(appContext)
+ )
+ }
+ },
+ FOOD_ARRIVING(18000) {
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ override fun buildNotification(): Notification.Builder {
+ return buildBaseNotification(appContext, FOOD_ARRIVING)
+ .setContentTitle("Your order is arriving and has been dropped off")
+ .setContentText("Enjoy & don't forget to refrigerate any perishable items.")
+ .setStyle(
+ buildBaseProgressStyle(FOOD_ARRIVING)
+ .setProgressTrackerIcon(
+ IconCompat.createWithResource(
+ appContext, R.drawable.delivery_truck
+ ).toIcon(appContext)
+ )
+ .setProgress(75)
+ )
+ .setLargeIcon(
+ IconCompat.createWithResource(
+ appContext, R.drawable.cupcake
+ ).toIcon(appContext)
+ )
+ }
+ },
+ ORDER_COMPLETE(21000) {
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ override fun buildNotification(): Notification.Builder {
+ return buildBaseNotification(appContext, ORDER_COMPLETE)
+ .setContentTitle("Your order is complete.")
+ .setContentText("Thank you for using JetSnack for your snacking needs.")
+ .setStyle(
+ buildBaseProgressStyle(ORDER_COMPLETE)
+ .setProgressTrackerIcon(
+ IconCompat.createWithResource(
+ appContext, R.drawable.check_circle
+ ).toIcon(appContext)
+ )
+ .setProgress(100)
+ )
+ .setLargeIcon(
+ IconCompat.createWithResource(
+ appContext, R.drawable.cupcake
+ ).toIcon(appContext)
+ )
+ }
+ };
+
+
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ fun buildBaseProgressStyle(orderState: OrderState): ProgressStyle {
+ val pointColor = Color.valueOf(236f, 183f, 255f, 1f).toArgb()
+ val segmentColor = Color.valueOf(134f, 247f, 250f, 1f).toArgb()
+ var progressStyle = ProgressStyle()
+ .setProgressPoints(
+ listOf(
+ ProgressStyle.Point(25).setColor(pointColor),
+ ProgressStyle.Point(50).setColor(pointColor),
+ ProgressStyle.Point(75).setColor(pointColor),
+ ProgressStyle.Point(100).setColor(pointColor)
+ )
+ ).setProgressSegments(
+ listOf(
+ ProgressStyle.Segment(25).setColor(segmentColor),
+ ProgressStyle.Segment(25).setColor(segmentColor),
+ ProgressStyle.Segment(25).setColor(segmentColor),
+ ProgressStyle.Segment(25).setColor(segmentColor)
+
+ )
+ )
+ when (orderState) {
+ INITIALIZING -> {}
+ FOOD_PREPARATION -> {}
+ FOOD_ENROUTE -> progressStyle.setProgressPoints(
+ listOf(
+ ProgressStyle.Point(25).setColor(pointColor)
+ )
+ )
+
+ FOOD_ARRIVING -> progressStyle.setProgressPoints(
+ listOf(
+ ProgressStyle.Point(25).setColor(pointColor),
+ ProgressStyle.Point(50).setColor(pointColor)
+ )
+ )
+
+ ORDER_COMPLETE -> progressStyle.setProgressPoints(
+ listOf(
+ ProgressStyle.Point(25).setColor(pointColor),
+ ProgressStyle.Point(50).setColor(pointColor),
+ ProgressStyle.Point(75).setColor(pointColor)
+ )
+ )
+ }
+ return progressStyle
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun buildBaseNotification(appContext: Context, orderState: OrderState): Notification.Builder {
+ var notificationBuilder = Notification.Builder(appContext, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setOngoing(true)
+ .setColorized(true)
+
+ when (orderState) {
+ INITIALIZING -> {}
+ FOOD_PREPARATION -> {}
+ FOOD_ENROUTE -> {}
+ FOOD_ARRIVING ->
+ notificationBuilder
+ .addAction(
+ Notification.Action.Builder(null, "Got it", null).build()
+ )
+ .addAction(
+ Notification.Action.Builder(null, "Tip", null).build()
+ )
+ ORDER_COMPLETE ->
+ notificationBuilder
+ .addAction(
+ Notification.Action.Builder(
+ null, "Rate delivery", null).build()
+ )
+ }
+ return notificationBuilder
+ }
+
+ abstract fun buildNotification(): Notification.Builder
+ }
+
+ fun start() {
+ for (state in OrderState.entries) {
+ val notification = state.buildNotification().build()
+ Handler(Looper.getMainLooper()).postDelayed({
+ notificationManager.notify(NOTIFICATION_ID, notification)
+ }, state.delay)
+ }
+ }
+}
diff --git a/samples/user-interface/live-updates/src/main/res/drawable/check_circle.png b/samples/user-interface/live-updates/src/main/res/drawable/check_circle.png
new file mode 100644
index 00000000..6a89600e
Binary files /dev/null and b/samples/user-interface/live-updates/src/main/res/drawable/check_circle.png differ
diff --git a/samples/user-interface/live-updates/src/main/res/drawable/cupcake.jpg b/samples/user-interface/live-updates/src/main/res/drawable/cupcake.jpg
new file mode 100644
index 00000000..42e766d8
Binary files /dev/null and b/samples/user-interface/live-updates/src/main/res/drawable/cupcake.jpg differ
diff --git a/samples/user-interface/live-updates/src/main/res/drawable/delivery_car.png b/samples/user-interface/live-updates/src/main/res/drawable/delivery_car.png
new file mode 100644
index 00000000..e748e59d
Binary files /dev/null and b/samples/user-interface/live-updates/src/main/res/drawable/delivery_car.png differ
diff --git a/samples/user-interface/live-updates/src/main/res/drawable/delivery_truck.png b/samples/user-interface/live-updates/src/main/res/drawable/delivery_truck.png
new file mode 100644
index 00000000..7c607115
Binary files /dev/null and b/samples/user-interface/live-updates/src/main/res/drawable/delivery_truck.png differ
diff --git a/samples/user-interface/live-updates/src/main/res/drawable/ic_launcher_foreground.xml b/samples/user-interface/live-updates/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000..86d0b4a9
--- /dev/null
+++ b/samples/user-interface/live-updates/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/user-interface/live-updates/src/main/res/drawable/shopping_bag.png b/samples/user-interface/live-updates/src/main/res/drawable/shopping_bag.png
new file mode 100644
index 00000000..753dddba
Binary files /dev/null and b/samples/user-interface/live-updates/src/main/res/drawable/shopping_bag.png differ
diff --git a/samples/user-interface/live-updates/src/main/res/values/colors.xml b/samples/user-interface/live-updates/src/main/res/values/colors.xml
new file mode 100644
index 00000000..c8524cd9
--- /dev/null
+++ b/samples/user-interface/live-updates/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/samples/user-interface/live-updates/src/main/res/values/strings.xml b/samples/user-interface/live-updates/src/main/res/values/strings.xml
new file mode 100644
index 00000000..9ee41a09
--- /dev/null
+++ b/samples/user-interface/live-updates/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+ Live Updates
+
+ Grant
+ Please grant the notification permission.
+ Notifications are used for order tracking.
+ Clicking the checkout button will simulate the tracking of an order with notifications styled with ProgressStyle.
+ Checkout
+ Order placed
+
+
\ No newline at end of file
diff --git a/samples/user-interface/live-updates/src/main/res/values/themes.xml b/samples/user-interface/live-updates/src/main/res/values/themes.xml
new file mode 100644
index 00000000..9fa09aba
--- /dev/null
+++ b/samples/user-interface/live-updates/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 983b3354..42c9a1e1 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -59,6 +59,7 @@ include(":samples:user-interface:appwidgets")
include(":samples:user-interface:constraintlayout")
include(":samples:user-interface:draganddrop")
include(":samples:user-interface:haptics")
+include(":samples:user-interface:live-updates")
include(":samples:user-interface:picture-in-picture")
include(":samples:user-interface:predictiveback")
include(":samples:user-interface:quicksettings")