diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e4701a474..d02cdf75e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -76,7 +76,7 @@ wear = "1.3.0" wearComposeFoundation = "1.5.0-rc02" wearComposeMaterial = "1.5.0-rc02" wearComposeMaterial3 = "1.5.0-rc02" -wearOngoing = "1.0.0" +wearOngoing = "1.1.0" wearToolingPreview = "1.0.0" webkit = "1.14.0" diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index c2a0bb52f..a70ca62db 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -224,7 +224,21 @@ + + + + + + ?>(null) } + val listState = rememberScalingLazyListState() + MaterialTheme( colorScheme = dynamicColorScheme(LocalContext.current) ?: MaterialTheme.colorScheme ) { - // [START android_wear_ongoing_activity_ambientaware] AmbientAware { ambientState -> - // [START_EXCLUDE] - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { + ScalingLazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + horizontalAlignment = Alignment.CenterHorizontally, + autoCentering = AutoCenteringParams(itemIndex = 0) + ) { + item { Text(text = "Elapsed Time", style = MaterialTheme.typography.titleLarge) + } + item { Spacer(modifier = Modifier.height(8.dp)) - // [END_EXCLUDE] + } + item { ElapsedTime(ambientState = ambientState) - // [START_EXCLUDE] + } + item { Spacer(modifier = Modifier.height(8.dp)) + } + + val services = listOf( + AlwaysOnService1::class.java, + AlwaysOnService2::class.java, + AlwaysOnService3::class.java + ) + + items(services.size) { index -> + val serviceClass = services[index] + val isRunning = runningService == serviceClass SwitchButton( - checked = isOngoingActivity, + checked = isRunning, onCheckedChange = { newState -> - Log.d(TAG, "Switch button changed: $newState") - isOngoingActivity = newState - if (newState) { - Log.d(TAG, "Starting AlwaysOnService") - AlwaysOnService.startService(context) + if (runningService != null) { + Log.d(TAG, "Stopping ${runningService?.simpleName}") + context.stopService(Intent(context, runningService)) + } + Log.d(TAG, "Starting ${serviceClass.simpleName}") + val intent = Intent(context, serviceClass) + context.startForegroundService(intent) + runningService = serviceClass } else { - Log.d(TAG, "Stopping AlwaysOnService") - AlwaysOnService.stopService(context) + Log.d(TAG, "Stopping ${serviceClass.simpleName}") + context.stopService(Intent(context, serviceClass)) + runningService = null } }, contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp), ) { Text( - text = "Ongoing Activity", - style = MaterialTheme.typography.bodyExtraSmall, + text = "Ongoing Activity ${index + 1}", + style = MaterialTheme.typography.bodySmall, ) } } } - // [END_EXCLUDE] } - // [END android_wear_ongoing_activity_ambientaware] } } diff --git a/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt b/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt index 59ed0f8af..d52db8fd8 100644 --- a/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt +++ b/wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt @@ -16,12 +16,11 @@ package com.example.wear.snippets.alwayson -import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent -import android.content.Context import android.content.Intent +import android.os.SystemClock import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.content.getSystemService @@ -30,29 +29,19 @@ import androidx.wear.ongoing.OngoingActivity import androidx.wear.ongoing.Status import com.example.wear.R -class AlwaysOnService : LifecycleService() { +abstract class AlwaysOnServiceBase : LifecycleService() { private val notificationManager by lazy { getSystemService() } companion object { private const val TAG = "AlwaysOnService" - private const val NOTIFICATION_ID = 1001 - private const val CHANNEL_ID = "always_on_service_channel" + const val NOTIFICATION_ID = 1001 + const val CHANNEL_ID = "always_on_service_channel" private const val CHANNEL_NAME = "Always On Service" + @Volatile var isRunning = false private set - - fun startService(context: Context) { - Log.d(TAG, "Starting AlwaysOnService") - val intent = Intent(context, AlwaysOnService::class.java) - context.startForegroundService(intent) - } - - fun stopService(context: Context) { - Log.d(TAG, "Stopping AlwaysOnService") - context.stopService(Intent(context, AlwaysOnService::class.java)) - } } override fun onCreate() { @@ -66,9 +55,7 @@ class AlwaysOnService : LifecycleService() { super.onStartCommand(intent, flags, startId) Log.d(TAG, "onStartCommand: Service started with startId: $startId") - // Create and start foreground notification - val notification = createNotification() - startForeground(NOTIFICATION_ID, notification) + createNotification() Log.d(TAG, "onStartCommand: Service is now running as foreground service") @@ -93,8 +80,13 @@ class AlwaysOnService : LifecycleService() { Log.d(TAG, "createNotificationChannel: Notification channel created") } - // [START android_wear_ongoing_activity_create_notification] - private fun createNotification(): Notification { + abstract fun createNotification() +} + +class AlwaysOnService1 : AlwaysOnServiceBase() { + override fun createNotification() { + // Creates an ongoing activity that demonstrates how to link the touch intent to the always-on activity. + // [START android_wear_ongoing_activity_create_notification] val activityIntent = Intent(this, AlwaysOnActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP @@ -122,7 +114,6 @@ class AlwaysOnService : LifecycleService() { .setOngoing(true) // [START_EXCLUDE] - // Create an Ongoing Activity val ongoingActivityStatus = Status.Builder().addTemplate("Stopwatch running").build() // [END_EXCLUDE] @@ -139,7 +130,120 @@ class AlwaysOnService : LifecycleService() { ongoingActivity.apply(applicationContext) - return notificationBuilder.build() + val notification = notificationBuilder.build() + // [END android_wear_ongoing_activity_create_notification] + + startForeground(NOTIFICATION_ID, notification) + } +} + +class AlwaysOnService2 : AlwaysOnServiceBase() { + override fun createNotification() { + // Creates an ongoing activity with a static status text + + // [START android_wear_ongoing_activity_notification_builder] + // Create a PendingIntent to pass to the notification builder + val pendingIntent = + PendingIntent.getActivity( + this, + 0, + Intent(this, AlwaysOnActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + + val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("Always On Service") + .setContentText("Service is running in background") + .setSmallIcon(R.drawable.animated_walk) + // Category helps the system prioritize the ongoing activity + .setCategory(NotificationCompat.CATEGORY_WORKOUT) + .setContentIntent(pendingIntent) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setOngoing(true) // Important! + // [END android_wear_ongoing_activity_notification_builder] + + // [START android_wear_ongoing_activity_builder] + val ongoingActivity = + OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder) + // Sets the icon that appears on the watch face in active mode. + .setAnimatedIcon(R.drawable.animated_walk) + // Sets the icon that appears on the watch face in ambient mode. + .setStaticIcon(R.drawable.ic_walk) + // Sets the tap target to bring the user back to the app. + .setTouchIntent(pendingIntent) + .build() + // [END android_wear_ongoing_activity_builder] + + // [START android_wear_ongoing_activity_post_notification] + // This call modifies notificationBuilder to include the ongoing activity data. + ongoingActivity.apply(applicationContext) + + // Post the notification. + startForeground(NOTIFICATION_ID, notificationBuilder.build()) + // [END android_wear_ongoing_activity_post_notification] + } +} + +class AlwaysOnService3 : AlwaysOnServiceBase() { + override fun createNotification() { + // Creates an ongoing activity that demonstrates dynamic status text (a timer) + val activityIntent = + Intent(this, AlwaysOnActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + } + + val pendingIntent = + PendingIntent.getActivity( + this, + 0, + activityIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + + val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("Always On Service") + .setContentText("Service is running in background") + .setSmallIcon(R.drawable.animated_walk) + // Category helps the system prioritize the ongoing activity + .setCategory(NotificationCompat.CATEGORY_WORKOUT) + .setContentIntent(pendingIntent) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setOngoing(true) // Important! + + // [START android_wear_ongoing_activity_create_status] + // Define a template with placeholders for the activity type and the timer. + val statusTemplate = "#type# for #time#" + + // Set the start time for a stopwatch. + // Use SystemClock.elapsedRealtime() for time-based parts. + val runStartTime = SystemClock.elapsedRealtime() + + val ongoingActivityStatus = Status.Builder() + // Sets the template string. + .addTemplate(statusTemplate) + // Fills the #type# placeholder with a static text part. + .addPart("type", Status.TextPart("Run")) + // Fills the #time# placeholder with a stopwatch part. + .addPart("time", Status.StopwatchPart(runStartTime)) + .build() + // [END android_wear_ongoing_activity_create_status] + + // [START android_wear_ongoing_activity_set_status] + val ongoingActivity = + OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder) + // [START_EXCLUDE] + .setAnimatedIcon(R.drawable.animated_walk) + .setStaticIcon(R.drawable.ic_walk) + .setTouchIntent(pendingIntent) + // [END_EXCLUDE] + // Add the status to the OngoingActivity. + .setStatus(ongoingActivityStatus) + .build() + // [END android_wear_ongoing_activity_set_status] + + ongoingActivity.apply(applicationContext) + startForeground(NOTIFICATION_ID, notificationBuilder.build()) } - // [END android_wear_ongoing_activity_create_notification] }