Skip to content

Commit b8f144c

Browse files
author
gavine99
committed
changes to enable unified push notifications in generic build.
Signed-off-by: gavine99 <[email protected]>
1 parent 8e5ca5c commit b8f144c

File tree

22 files changed

+510
-111
lines changed

22 files changed

+510
-111
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
alt="Get it on F-Droid"
1414
height="80">](https://f-droid.org/packages/com.nextcloud.talk2/)
1515

16-
Please note that Notifications won't work with the F-Droid version due to missing Google Play Services.
16+
Please note that the F-Droid version uses UnifiedPush notifications and the Play Store version uses Google Play
17+
Services notifications.
1718

1819
|||||||
1920
|---|---|---|---|---|---|
@@ -63,7 +64,8 @@ Easy starting points are also reviewing [pull requests](https://github.com/nextc
6364
So you would like to contribute by testing? Awesome, we appreciate that very much.
6465

6566
To report a bug for the alpha or beta version, just create an issue on github like you would for the stable version and
66-
provide the version number. Please remember that Google Services are necessary to receive push notifications.
67+
provide the version number. Please remember that Google Services are necessary to receive push notifications in the
68+
Play Store version whereas the F-Droid version uses UnifiedPush notifications.
6769

6870
#### Beta versions (Release Candidates) :package:
6971

app/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ dependencies {
305305
implementation 'com.github.nextcloud.android-common:ui:0.23.2'
306306
implementation 'com.github.nextcloud-deps:android-talk-webrtc:132.6834.0'
307307

308+
// unified push library for generic flavour
309+
genericImplementation 'org.unifiedpush.android:connector:3.0.7'
310+
genericImplementation 'org.unifiedpush.android:connector-ui:1.1.0'
311+
308312
gplayImplementation 'com.google.android.gms:play-services-base:18.6.0'
309313
gplayImplementation "com.google.firebase:firebase-messaging:24.1.1"
310314

@@ -400,4 +404,4 @@ detekt {
400404

401405
ksp {
402406
arg('room.schemaLocation', "$projectDir/schemas")
403-
}
407+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!--
2+
~ Nextcloud Talk - Android Client
3+
~
4+
~ SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
5+
~ SPDX-FileCopyrightText: 2021-2023 Marcel Hibbe <[email protected]>
6+
~ SPDX-FileCopyrightText: 2017-2019 Mario Danic <[email protected]>
7+
~ SPDX-License-Identifier: GPL-3.0-or-later
8+
-->
9+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
10+
11+
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
12+
13+
<application>
14+
<service android:name=".UnifiedPush"
15+
android:exported="false">
16+
<intent-filter>
17+
<action android:name="org.unifiedpush.android.connector.PUSH_EVENT"/>
18+
</intent-filter>
19+
</service>
20+
</application>
21+
</manifest>
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Your Name <[email protected]>
5+
* SPDX-License-Identifier: GPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.talk
9+
10+
import android.content.Context
11+
import android.util.Log
12+
import androidx.work.Data
13+
import androidx.work.OneTimeWorkRequest
14+
import androidx.work.OutOfQuotaPolicy
15+
import androidx.work.WorkManager
16+
import com.nextcloud.talk.activities.MainActivity
17+
import com.nextcloud.talk.jobs.NotificationWorker
18+
import com.nextcloud.talk.utils.bundle.BundleKeys
19+
import com.nextcloud.talk.utils.power.PowerManagerUtils
20+
import org.greenrobot.eventbus.EventBus
21+
import org.unifiedpush.android.connector.FailedReason
22+
import org.unifiedpush.android.connector.PushService
23+
import org.unifiedpush.android.connector.UnifiedPush
24+
import org.unifiedpush.android.connector.data.PushEndpoint
25+
import org.unifiedpush.android.connector.data.PushMessage
26+
import org.unifiedpush.android.connector.ui.SelectDistributorDialogsBuilder
27+
import org.unifiedpush.android.connector.ui.UnifiedPushFunctions
28+
29+
class UnifiedPush : PushService() {
30+
companion object {
31+
private val TAG: String? = UnifiedPush::class.java.simpleName
32+
33+
private const val MESSAGE_RECEIVED_WAKE_LOCK_TIMEOUT = (60 * 1000L) // 60 seconds
34+
35+
fun getNumberOfDistributorsAvailable(context: Context) =
36+
UnifiedPush.getDistributors(context).size
37+
38+
fun registerForPushMessaging(context: Context, accountName: String, forceChoose: Boolean): Boolean {
39+
var retVal = false
40+
41+
object : SelectDistributorDialogsBuilder(
42+
context,
43+
object : UnifiedPushFunctions {
44+
override fun tryUseDefaultDistributor(callback: (Boolean) -> Unit) =
45+
UnifiedPush.tryUseDefaultDistributor(context, callback).also {
46+
Log.d(TAG, "tryUseDefaultDistributor()")
47+
}
48+
49+
override fun getAckDistributor(): String? =
50+
UnifiedPush.getAckDistributor(context).also {
51+
Log.d(TAG, "getAckDistributor() = $it")
52+
}
53+
54+
override fun getDistributors(): List<String> =
55+
UnifiedPush.getDistributors(context).also {
56+
Log.d(TAG, "getDistributors() = $it")
57+
}
58+
59+
override fun register(instance: String) =
60+
UnifiedPush.register(context, instance).also {
61+
Log.d(TAG, "register($instance)")
62+
}
63+
64+
override fun saveDistributor(distributor: String) =
65+
UnifiedPush.saveDistributor(context, distributor).also {
66+
Log.d(TAG, "saveDistributor($distributor)")
67+
}
68+
}
69+
) {
70+
override fun onManyDistributorsFound(distributors: List<String>) =
71+
Log.d(TAG, "onManyDistributorsFound($distributors)").run {
72+
// true return indicates to calling activity that it should wait whilst dialog is shown
73+
retVal = true
74+
super.onManyDistributorsFound(distributors)
75+
}
76+
77+
override fun onDistributorSelected(distributor: String) =
78+
super.onDistributorSelected(distributor).also {
79+
// send message to main activity that it can move on after waiting
80+
EventBus.getDefault().post(MainActivity.ProceedToConversationsListMessageEvent())
81+
}
82+
}.apply {
83+
instances = listOf(accountName)
84+
mayUseCurrent = !forceChoose
85+
mayUseDefault = !forceChoose
86+
}.run()
87+
88+
return retVal
89+
}
90+
91+
fun unregisterForPushMessaging(context: Context, accountName: String) =
92+
// try and unregister with unified push distributor
93+
UnifiedPush.unregister(context, accountName).also {
94+
Log.d(TAG, "unregisterForPushMessaging($accountName)")
95+
}
96+
}
97+
98+
override fun onMessage(message: PushMessage, instance: String) {
99+
// wake lock to get the notification background job to execute more promptly since it will take eons to run
100+
// if phone is dozing. default client ring time is 45 seconds so it should be more than that
101+
PowerManagerUtils().acquireTimedPartialLock(MESSAGE_RECEIVED_WAKE_LOCK_TIMEOUT)
102+
103+
Log.d(TAG, "onMessage()")
104+
105+
val messageString = message.content.toString(Charsets.UTF_8)
106+
107+
if (messageString.isNotEmpty() && instance.isNotEmpty()) {
108+
val messageData = Data.Builder()
109+
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, messageString)
110+
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, instance)
111+
.putInt(
112+
BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE,
113+
NotificationWorker.Companion.BackendType.UNIFIED_PUSH.value
114+
)
115+
.build()
116+
val notificationWork =
117+
OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData)
118+
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
119+
.build()
120+
WorkManager.getInstance(this).enqueue(notificationWork)
121+
122+
Log.d(TAG, "expedited NotificationWorker queued")
123+
}
124+
}
125+
126+
override fun onNewEndpoint(endpoint: PushEndpoint, instance: String) {
127+
Log.d(TAG, "onNewEndpoint(${endpoint.url}, $instance)")
128+
}
129+
130+
override fun onRegistrationFailed(reason: FailedReason, instance: String) =
131+
// the registration is not possible, eg. no network
132+
// force unregister to make sure cleaned up. re-register will be re-attempted next time
133+
UnifiedPush.unregister(this, instance).also {
134+
Log.d(TAG, "onRegistrationFailed(${reason.name}, $instance)")
135+
}
136+
137+
override fun onUnregistered(instance: String) =
138+
// this application is unregistered by the distributor from receiving push messages
139+
// force unregister to make sure cleaned up. re-register will be re-attempted next time
140+
UnifiedPush.unregister(this, instance).also {
141+
Log.d(TAG, "onUnregistered($instance)")
142+
}
143+
}

app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <[email protected]>
5+
* SPDX-FileCopyrightText: 2017-2018 Mario Danic <[email protected]>
6+
* SPDX-License-Identifier: GPL-3.0-or-later
7+
*/
8+
package com.nextcloud.talk.utils
9+
10+
import android.content.Context
11+
import com.nextcloud.talk.UnifiedPush.Companion.getNumberOfDistributorsAvailable
12+
import com.nextcloud.talk.UnifiedPush.Companion.registerForPushMessaging
13+
import com.nextcloud.talk.UnifiedPush.Companion.unregisterForPushMessaging
14+
import com.nextcloud.talk.interfaces.ClosedInterface
15+
16+
class ClosedInterfaceImpl : ClosedInterface {
17+
override fun providerInstallerInstallIfNeededAsync() { /* nothing */
18+
}
19+
20+
override fun isPushMessagingServiceAvailable(context: Context): Boolean {
21+
return (getNumberOfDistributorsAvailable(context) > 0)
22+
}
23+
24+
override fun pushMessagingProvider(): String {
25+
return "unifiedpush"
26+
}
27+
28+
override fun registerWithServer(context: Context, username: String?, forceChoose: Boolean): Boolean {
29+
// unified push available in generic build
30+
if (username == null) return false
31+
return registerForPushMessaging(context, username, forceChoose)
32+
}
33+
34+
override fun unregisterWithServer(context: Context, username: String?) {
35+
// unified push available in generic build
36+
if (username == null) return
37+
unregisterForPushMessaging(context, username)
38+
}
39+
}

app/src/gplay/java/com/nextcloud/talk/services/firebase/NCFirebaseMessagingService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class NCFirebaseMessagingService : FirebaseMessagingService() {
5050
val messageData = Data.Builder()
5151
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, subject)
5252
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, signature)
53+
.putInt(
54+
BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE,
55+
NotificationWorker.Companion.BackendType.FIREBASE_CLOUD_MESSAGING.value
56+
)
5357
.build()
5458
val notificationWork =
5559
OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData)

app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
package com.nextcloud.talk.utils
1010

11+
import android.content.Context
1112
import android.content.Intent
1213
import android.util.Log
1314
import androidx.work.ExistingPeriodicWorkPolicy
@@ -26,7 +27,13 @@ import java.util.concurrent.TimeUnit
2627
@AutoInjector(NextcloudTalkApplication::class)
2728
class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallListener {
2829

29-
override val isGooglePlayServicesAvailable: Boolean = isGPlayServicesAvailable()
30+
override fun isPushMessagingServiceAvailable(context: Context): Boolean {
31+
return isGPlayServicesAvailable()
32+
}
33+
34+
override fun pushMessagingProvider(): String {
35+
return "gplay"
36+
}
3037

3138
override fun providerInstallerInstallIfNeededAsync() {
3239
NextcloudTalkApplication.sharedApplication?.let {
@@ -59,11 +66,17 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
5966
}
6067
}
6168

62-
override fun setUpPushTokenRegistration() {
69+
override fun registerWithServer(context: Context, username: String?, forceChoose: Boolean): Boolean {
6370
val firebasePushTokenWorker = OneTimeWorkRequest.Builder(GetFirebasePushTokenWorker::class.java).build()
6471
WorkManager.getInstance().enqueue(firebasePushTokenWorker)
6572

6673
setUpPeriodicTokenRefreshFromFCM()
74+
75+
return false
76+
}
77+
78+
override fun unregisterWithServer(context: Context, username: String?) {
79+
// do nothing
6780
}
6881

6982
private fun setUpPeriodicTokenRefreshFromFCM() {

app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package com.nextcloud.talk.account
1010

1111
import android.annotation.SuppressLint
12+
import android.content.Context
1213
import android.content.Intent
1314
import android.content.pm.ActivityInfo
1415
import android.os.Bundle
@@ -235,6 +236,8 @@ class AccountVerificationActivity : BaseActivity() {
235236
}
236237

237238
private fun storeProfile(displayName: String?, userId: String, capabilitiesOverall: CapabilitiesOverall) {
239+
val activityContext: Context = this // for capture by lambda used by subscribe() below
240+
238241
userManager.storeProfile(
239242
username,
240243
UserManager.UserAttributes(
@@ -260,8 +263,8 @@ class AccountVerificationActivity : BaseActivity() {
260263
@SuppressLint("SetTextI18n")
261264
override fun onSuccess(user: User) {
262265
internalAccountId = user.id!!
263-
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
264-
ClosedInterfaceImpl().setUpPushTokenRegistration()
266+
if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) {
267+
ClosedInterfaceImpl().registerWithServer(activityContext, user.username, false)
265268
} else {
266269
Log.w(TAG, "Skipping push registration.")
267270
runOnUiThread {

0 commit comments

Comments
 (0)