Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
16 changes: 15 additions & 1 deletion wear/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,21 @@
</activity>

<service
android:name=".snippets.alwayson.AlwaysOnService"
android:name=".snippets.alwayson.AlwaysOnService1"
android:foregroundServiceType="specialUse"
android:exported="false">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="For Ongoing Activity"/>
</service>
<service
android:name=".snippets.alwayson.AlwaysOnService2"
android:foregroundServiceType="specialUse"
android:exported="false">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="For Ongoing Activity"/>
</service>
<service
android:name=".snippets.alwayson.AlwaysOnService3"
android:foregroundServiceType="specialUse"
android:exported="false">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.example.wear.snippets.alwayson

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
Expand Down Expand Up @@ -141,46 +142,55 @@ fun ElapsedTime(ambientState: AmbientState) {
@Composable
fun WearApp() {
val context = LocalContext.current
var isOngoingActivity by rememberSaveable { mutableStateOf(AlwaysOnService.isRunning) }
var runningService by rememberSaveable { mutableStateOf<Class<*>?>(null) }

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) {
Text(text = "Elapsed Time", style = MaterialTheme.typography.titleLarge)
Spacer(modifier = Modifier.height(8.dp))
// [END_EXCLUDE]
ElapsedTime(ambientState = ambientState)
// [START_EXCLUDE]
Spacer(modifier = Modifier.height(8.dp))
SwitchButton(
checked = isOngoingActivity,
onCheckedChange = { newState ->
Log.d(TAG, "Switch button changed: $newState")
isOngoingActivity = newState

if (newState) {
Log.d(TAG, "Starting AlwaysOnService")
AlwaysOnService.startService(context)
} else {
Log.d(TAG, "Stopping AlwaysOnService")
AlwaysOnService.stopService(context)
}
},
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
text = "Ongoing Activity",
style = MaterialTheme.typography.bodyExtraSmall,
)

val services = listOf(
AlwaysOnService1::class.java,
AlwaysOnService2::class.java,
AlwaysOnService3::class.java
)

services.forEachIndexed { index, serviceClass ->
val isRunning = runningService == serviceClass
SwitchButton(
checked = isRunning,
onCheckedChange = { newState ->
if (newState) {
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 ${serviceClass.simpleName}")
context.stopService(Intent(context, serviceClass))
runningService = null
}
},
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
text = "Ongoing Activity ${index + 1}",
style = MaterialTheme.typography.bodyExtraSmall,
)
}
}
}
}
// [END_EXCLUDE]
}
// [END android_wear_ongoing_activity_ambientaware]
}
}
154 changes: 130 additions & 24 deletions wear/src/main/java/com/example/wear/snippets/alwayson/AlwaysOnService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<NotificationManager>() }

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() {
Expand All @@ -66,15 +55,15 @@ 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")

return START_STICKY
}



override fun onDestroy() {
Log.d(TAG, "onDestroy: Service destroyed")
isRunning = false
Expand All @@ -93,8 +82,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
Expand Down Expand Up @@ -122,7 +116,6 @@ class AlwaysOnService : LifecycleService() {
.setOngoing(true)

// [START_EXCLUDE]
// Create an Ongoing Activity
val ongoingActivityStatus = Status.Builder().addTemplate("Stopwatch running").build()
// [END_EXCLUDE]

Expand All @@ -139,7 +132,120 @@ class AlwaysOnService : LifecycleService() {

ongoingActivity.apply(applicationContext)

return notificationBuilder.build()
val notification = notificationBuilder.build()
// [END android_wear_ongoing_activity_create_notification] // where's the equivalent START? should this even be here? check the docs jjjjjjj

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]
}
// [END android_wear_ongoing_activity_create_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())
}
}
Loading