Skip to content

Commit 0981628

Browse files
authored
Add "Open on Watch" action to notifications (#1576)
* Add "Open on Watch" action to notifications If Confetti is installed, open directly. If not, open Play Store to install.
1 parent 57121c7 commit 0981628

File tree

9 files changed

+86
-37
lines changed

9 files changed

+86
-37
lines changed

androidApp/src/main/AndroidManifest.xml

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@
55
<uses-permission android:name="android.permission.INTERNET" />
66
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
77

8-
<!--
9-
<uses-permission android:name="androidx.car.app.MAP_TEMPLATES"/>
10-
-->
11-
128
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />
139

14-
<uses-sdk android:minSdkVersion="21" tools:ignore="GradleOverrides" tools:overrideLibrary="com.google.android.horologist.datalayer.phone,androidx.wear.remote.interactions" />
10+
<uses-sdk android:minSdkVersion="21" tools:ignore="GradleOverrides" tools:overrideLibrary="androidx.wear.phone.interactions,com.google.android.horologist.datalayer.phone,androidx.wear.remote.interactions" />
1511

1612
<application
1713
android:name=".ConfettiApplication"
@@ -23,28 +19,12 @@
2319
android:label="@string/app_name"
2420
android:theme="@style/Theme.Confetti"
2521
android:localeConfig="@xml/locale_config"
26-
android:enableOnBackInvokedCallback="true">
22+
android:enableOnBackInvokedCallback="true"
23+
tools:ignore="UnusedAttribute">
2724

2825
<meta-data
2926
android:name="firebase_crashlytics_collection_enabled"
3027
android:value="false" />
31-
<!--
32-
<meta-data
33-
android:name="com.google.android.gms.car.application"
34-
android:resource="@xml/automotive_app_desc"
35-
tools:ignore="MetadataTagInsideApplicationTag" />
36-
37-
<meta-data
38-
android:name="androidx.car.app.theme"
39-
android:resource="@style/NightAdjusted.Theme.Confetti"
40-
tools:ignore="MetadataTagInsideApplicationTag" />
41-
42-
<meta-data android:name="androidx.car.app.minCarApiLevel"
43-
android:value="1"
44-
tools:ignore="MetadataTagInsideApplicationTag" />
45-
-->
46-
47-
4828
<provider
4929
android:name="androidx.startup.InitializationProvider"
5030
android:authorities="${applicationId}.androidx-startup"
@@ -56,16 +36,7 @@
5636
android:value="androidx.startup"
5737
tools:node="remove" />
5838
</provider>
59-
<!--
60-
<service
61-
android:name=".car.ConfettiCarAppService"
62-
android:exported="true">
63-
<intent-filter>
64-
<action android:name="androidx.car.app.CarAppService"/>
65-
<category android:name="androidx.car.app.category.POI"/>
66-
</intent-filter>
67-
</service>
68-
-->
39+
6940
<activity
7041
android:name=".MainActivity"
7142
android:exported="true"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@array/android_wear_capabilities"/>

androidApp/src/main/res/values/wear.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
<string-array name="android_wear_capabilities">
33
<item>horologist_phone</item>
44
<item>data_layer_app_helper_device_phone</item>
5+
<item>confetti_mobile_app</item>
56
</string-array>
67
</resources>

shared/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ kotlin {
143143
implementation(libs.firebase.analytics)
144144
implementation(libs.compose.navigation)
145145

146+
implementation(libs.androidx.wear.phone.interactions)
147+
146148
api(libs.androidx.work.runtime.ktx)
147149

148150
api(libs.androidx.datastore)

shared/src/androidMain/kotlin/dev/johnoreilly/confetti/notifications/NotificationReceiver.kt

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ package dev.johnoreilly.confetti.notifications
33
import android.content.BroadcastReceiver
44
import android.content.Context
55
import android.content.Intent
6+
import androidx.concurrent.futures.await
67
import androidx.core.app.NotificationManagerCompat
8+
import androidx.core.net.toUri
9+
import androidx.wear.remote.interactions.RemoteActivityHelper
10+
import com.google.android.gms.wearable.CapabilityClient
11+
import com.google.android.gms.wearable.Wearable
712
import dev.johnoreilly.confetti.ConfettiRepository
813
import dev.johnoreilly.confetti.auth.Authentication
914
import kotlinx.coroutines.CoroutineScope
15+
import kotlinx.coroutines.Dispatchers
16+
import kotlinx.coroutines.asExecutor
1017
import kotlinx.coroutines.launch
18+
import kotlinx.coroutines.tasks.await
1119
import org.koin.core.component.KoinComponent
1220
import org.koin.core.component.inject
1321
import kotlin.coroutines.CoroutineContext
@@ -20,19 +28,23 @@ class NotificationReceiver: BroadcastReceiver(), KoinComponent {
2028
private val notificationManager: NotificationManagerCompat by inject()
2129

2230
override fun onReceive(context: Context, intent: Intent) {
23-
if (intent.action == "REMOVE_BOOKMARK") {
31+
if (intent.action == ActionRemoveBookmark) {
2432
doAsync {
2533
removeBookmark(intent)
2634
val notificationId = intent.getIntExtra("notificationId", -1)
2735
if (notificationId != -1) {
2836
notificationManager.cancel(notificationId)
2937
}
3038
}
39+
} else if (intent.action == ActionOpenOnWear) {
40+
doAsync {
41+
openOnWear(intent, context)
42+
}
3143
}
3244
}
3345

34-
private suspend fun removeBookmark(intent: Intent?) {
35-
val conference = intent?.getStringExtra("conferenceId") ?: return
46+
private suspend fun removeBookmark(intent: Intent) {
47+
val conference = intent.getStringExtra("conferenceId") ?: return
3648
val sessionId = intent.getStringExtra("sessionId") ?: return
3749
val user = authentication.currentUser.value ?: return
3850

@@ -46,4 +58,35 @@ class NotificationReceiver: BroadcastReceiver(), KoinComponent {
4658
val pendingResult = goAsync()
4759
appScope.launch(coroutineContext) { block() }.invokeOnCompletion { pendingResult.finish() }
4860
}
61+
62+
private suspend fun openOnWear(intent: Intent, context: Context) {
63+
val conference = intent.getStringExtra("conferenceId")
64+
val sessionId = intent.getStringExtra("sessionId")
65+
66+
val remoteActivityHelper = RemoteActivityHelper(context, Dispatchers.Default.asExecutor())
67+
68+
val capabilityClient = Wearable.getCapabilityClient(context)
69+
val installedNodes = capabilityClient.getCapability("confetti_wear_app", CapabilityClient.FILTER_ALL).await().nodes.map { it.id }
70+
71+
if (installedNodes.isNotEmpty()) {
72+
remoteActivityHelper.startRemoteActivity(
73+
Intent(Intent.ACTION_VIEW)
74+
.setData("https://confetti-app.dev/conference/$conference/session/$sessionId".toUri())
75+
.addCategory(Intent.CATEGORY_BROWSABLE),
76+
null // all devices
77+
).await()
78+
} else {
79+
remoteActivityHelper.startRemoteActivity(
80+
Intent(Intent.ACTION_VIEW)
81+
.setData("http://play.google.com/store/apps/details?id=dev.johnoreilly.confetti".toUri())
82+
.addCategory(Intent.CATEGORY_BROWSABLE),
83+
null // all devices
84+
).await()
85+
}
86+
}
87+
88+
companion object {
89+
val ActionRemoveBookmark = "REMOVE_BOOKMARK"
90+
val ActionOpenOnWear = "OPEN_ON_WEAR"
91+
}
4992
}

shared/src/androidMain/kotlin/dev/johnoreilly/confetti/notifications/SessionNotificationBuilder.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class SessionNotificationBuilder(
3636
.extend(
3737
NotificationCompat.WearableExtender()
3838
.setBridgeTag("session:reminder")
39+
.addAction(openOnWearAction(conferenceId, session.id, notificationId))
3940
)
4041
}
4142

@@ -53,7 +54,7 @@ class SessionNotificationBuilder(
5354
context,
5455
notificationId,
5556
Intent(context, NotificationReceiver::class.java).apply {
56-
action = "REMOVE_BOOKMARK"
57+
action = NotificationReceiver.ActionRemoveBookmark
5758
putExtra("conferenceId", conferenceId)
5859
putExtra("sessionId", sessionId)
5960
putExtra("notificationId", notificationId)
@@ -66,6 +67,23 @@ class SessionNotificationBuilder(
6667
.build()
6768
}
6869

70+
private fun openOnWearAction(conferenceId: String, sessionId: String, notificationId: Int): NotificationCompat.Action {
71+
val openOnWearIntent = PendingIntent.getBroadcast(
72+
context,
73+
notificationId,
74+
Intent(context, NotificationReceiver::class.java).apply {
75+
action = NotificationReceiver.ActionOpenOnWear
76+
putExtra("conferenceId", conferenceId)
77+
putExtra("sessionId", sessionId)
78+
putExtra("notificationId", notificationId)
79+
},
80+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
81+
)
82+
83+
return NotificationCompat.Action.Builder(null, "Open on Watch", openOnWearIntent)
84+
.build()
85+
}
86+
6987
fun createChannel(): NotificationChannelCompat.Builder {
7088
val name = "Upcoming sessions"
7189
val importance = NotificationManager.IMPORTANCE_DEFAULT

wearApp/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@
6060
android:host="confetti"
6161
android:scheme="confetti" />
6262
</intent-filter>
63+
64+
<intent-filter android:autoVerify="true">
65+
<action android:name="android.intent.action.VIEW" />
66+
<category android:name="android.intent.category.DEFAULT" />
67+
<category android:name="android.intent.category.BROWSABLE" />
68+
<data
69+
android:scheme="https"
70+
android:host="confetti-app.dev" />
71+
</intent-filter>
6372
</activity>
6473

6574
<service
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@array/android_wear_capabilities"/>

wearApp/src/main/res/values/wear.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
<string-array name="android_wear_capabilities">
33
<item>horologist_watch</item>
44
<item>data_layer_app_helper_device_watch</item>
5+
<item>confetti_wear_app</item>
56
</string-array>
67
</resources>

0 commit comments

Comments
 (0)