From d44407fb813f65215a0389dde1a00c23b27f70b5 Mon Sep 17 00:00:00 2001 From: garanj Date: Tue, 28 Apr 2026 14:49:55 +0100 Subject: [PATCH] Change to avoid recreating notification and ongoing activity --- .../ForegroundOnlyWalkingWorkoutService.kt | 26 ++++++++++++++----- .../ForegroundOnlyWalkingWorkoutService.kt | 22 +++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/finished/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt b/finished/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt index 7bb734d..2a7124c 100644 --- a/finished/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt +++ b/finished/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt @@ -15,6 +15,7 @@ */ package com.android.example.wear.ongoingactivity +import android.Manifest import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager @@ -27,6 +28,7 @@ import android.content.res.Configuration import android.os.Binder import android.os.IBinder import android.util.Log +import androidx.annotation.RequiresPermission import androidx.core.app.NotificationCompat import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope @@ -65,6 +67,8 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { } private lateinit var notificationManager: NotificationManager + private lateinit var notification: Notification + private lateinit var ongoingActivity: OngoingActivity /* * Checks whether the bound activity has really gone away (in which case a foreground service @@ -144,8 +148,9 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { // we do nothing. if (!configurationChange && walkingWorkoutActive) { Log.d(TAG, "Start foreground service") - val notification = + notification = generateNotification(getString(R.string.walking_workout_notification_started_text)) + updateOngoingActivity(getString(R.string.walking_workout_notification_started_text)) // startForeground takes care of notificationManager.notify(...). startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) serviceRunningInForeground = true @@ -166,6 +171,7 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { configurationChange = false } + @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) fun startWalkingWorkout() { Log.d(TAG, "startWalkingWorkout()") @@ -212,13 +218,12 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { // Normally, you would listen to the location and sensor data and calculate your points with // an algorithm, but we are mocking the data to simply this so we can focus on learning about // the Ongoing Activity API. + @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) private suspend fun mockSensorAndLocationForWalkingWorkout() { for (walkingPoints in 0 until 100) { if (serviceRunningInForeground) { - val notification = generateNotification( - getString(R.string.walking_points_text, walkingPoints), - ) - notificationManager.notify(NOTIFICATION_ID, notification) + val updatedStatus = getString(R.string.walking_points_text, walkingPoints) + updateOngoingActivity(updatedStatus) } Log.d(TAG, "mockSensorAndLocationForWalkingWorkout(): $walkingPoints") walkingWorkoutsRepository.setWalkingPoints(walkingPoints) @@ -313,7 +318,7 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { .addTemplate(mainText) .build() - val ongoingActivity = + ongoingActivity = OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder) // Sets icon that will appear on the watch face in active mode. If it isn't set, // the watch face will use the static icon in active mode. @@ -340,6 +345,15 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { return notificationBuilder.build() } + @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) + private fun updateOngoingActivity(statusText: String) { + val status = Status.Builder() + .addTemplate(statusText) + .build() + + ongoingActivity.update(this, status) + } + /** * Class used for the client Binder. Since this service runs in the same process as its * clients, we don't need to deal with IPC. diff --git a/start/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt b/start/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt index cbe3bc2..dbde5ee 100644 --- a/start/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt +++ b/start/src/main/java/com/android/example/wear/ongoingactivity/ForegroundOnlyWalkingWorkoutService.kt @@ -15,6 +15,7 @@ */ package com.android.example.wear.ongoingactivity +import android.Manifest import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager @@ -27,9 +28,11 @@ import android.content.res.Configuration import android.os.Binder import android.os.IBinder import android.util.Log +import androidx.annotation.RequiresPermission import androidx.core.app.NotificationCompat import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope +import androidx.wear.ongoing.OngoingActivity import com.android.example.wear.ongoingactivity.data.WalkingWorkoutsRepository import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -63,6 +66,8 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { } private lateinit var notificationManager: NotificationManager + private lateinit var notification: Notification + private lateinit var ongoingActivity: OngoingActivity /* * Checks whether the bound activity has really gone away (in which case a foreground service @@ -141,9 +146,9 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { // NOTE: If this method is called due to a configuration change in MainActivity, // we do nothing. if (!configurationChange && walkingWorkoutActive) { - Log.d(TAG, "Start foreground service") - val notification = + notification = generateNotification(getString(R.string.walking_workout_notification_started_text)) + updateOngoingActivity(getString(R.string.walking_workout_notification_started_text)) // startForeground takes care of notificationManager.notify(...). startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) serviceRunningInForeground = true @@ -164,6 +169,7 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { configurationChange = false } + @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) fun startWalkingWorkout() { Log.d(TAG, "startWalkingWorkout()") @@ -210,13 +216,12 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { // Normally, you would listen to the location and sensor data and calculate your points with // an algorithm, but we are mocking the data to simply this so we can focus on learning about // the Ongoing Activity API. + @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) private suspend fun mockSensorAndLocationForWalkingWorkout() { for (walkingPoints in 0 until 100) { if (serviceRunningInForeground) { - val notification = generateNotification( - getString(R.string.walking_points_text, walkingPoints), - ) - notificationManager.notify(NOTIFICATION_ID, notification) + val updatedStatus = getString(R.string.walking_points_text, walkingPoints) + updateOngoingActivity(updatedStatus) } Log.d(TAG, "mockSensorAndLocationForWalkingWorkout(): $walkingPoints") walkingWorkoutsRepository.setWalkingPoints(walkingPoints) @@ -311,6 +316,11 @@ class ForegroundOnlyWalkingWorkoutService : LifecycleService() { return notificationBuilder.build() } + @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) + private fun updateOngoingActivity(statusText: String) { + // TODO: Update the Ongoing Activity. + } + /** * Class used for the client Binder. Since this service runs in the same process as its * clients, we don't need to deal with IPC.