Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/com.nextcloud.talk2/)

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

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

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

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

Expand Down
6 changes: 5 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ dependencies {
implementation 'com.github.nextcloud.android-common:ui:0.23.2'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:132.6834.0'

// unified push library for generic flavour
genericImplementation 'org.unifiedpush.android:connector:3.0.7'
genericImplementation 'org.unifiedpush.android:connector-ui:1.1.0'

gplayImplementation 'com.google.android.gms:play-services-base:18.6.0'
gplayImplementation "com.google.firebase:firebase-messaging:24.1.1"

Expand Down Expand Up @@ -401,4 +405,4 @@ detekt {

ksp {
arg('room.schemaLocation', "$projectDir/schemas")
}
}
21 changes: 21 additions & 0 deletions app/src/generic/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
~ SPDX-FileCopyrightText: 2021-2023 Marcel Hibbe <dev@mhibbe.de>
~ SPDX-FileCopyrightText: 2017-2019 Mario Danic <mario@lovelyhq.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<application>
<service android:name=".UnifiedPush"
android:exported="false">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.PUSH_EVENT"/>
</intent-filter>
</service>
</application>
</manifest>
143 changes: 143 additions & 0 deletions app/src/generic/java/com/nextcloud/talk/UnifiedPush.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk

import android.content.Context
import android.util.Log
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.jobs.NotificationWorker
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.power.PowerManagerUtils
import org.greenrobot.eventbus.EventBus
import org.unifiedpush.android.connector.FailedReason
import org.unifiedpush.android.connector.PushService
import org.unifiedpush.android.connector.UnifiedPush
import org.unifiedpush.android.connector.data.PushEndpoint
import org.unifiedpush.android.connector.data.PushMessage
import org.unifiedpush.android.connector.ui.SelectDistributorDialogsBuilder
import org.unifiedpush.android.connector.ui.UnifiedPushFunctions

class UnifiedPush : PushService() {
companion object {
private val TAG: String? = UnifiedPush::class.java.simpleName

private const val MESSAGE_RECEIVED_WAKE_LOCK_TIMEOUT = (60 * 1000L)

fun getNumberOfDistributorsAvailable(context: Context) =
UnifiedPush.getDistributors(context).size

fun registerForPushMessaging(context: Context, accountName: String, forceChoose: Boolean): Boolean {
var retVal = false

object : SelectDistributorDialogsBuilder(
context,
object : UnifiedPushFunctions {
override fun tryUseDefaultDistributor(callback: (Boolean) -> Unit) =
UnifiedPush.tryUseDefaultDistributor(context, callback).also {
Log.d(TAG, "tryUseDefaultDistributor()")
}

override fun getAckDistributor(): String? =
UnifiedPush.getAckDistributor(context).also {
Log.d(TAG, "getAckDistributor() = $it")
}

override fun getDistributors(): List<String> =
UnifiedPush.getDistributors(context).also {
Log.d(TAG, "getDistributors() = $it")
}

override fun register(instance: String) =
UnifiedPush.register(context, instance).also {
Log.d(TAG, "register($instance)")
}

override fun saveDistributor(distributor: String) =
UnifiedPush.saveDistributor(context, distributor).also {
Log.d(TAG, "saveDistributor($distributor)")
}
}
) {
override fun onManyDistributorsFound(distributors: List<String>) =
Log.d(TAG, "onManyDistributorsFound($distributors)").run {
// true return indicates to calling activity that it should wait whilst dialog is shown
retVal = true
super.onManyDistributorsFound(distributors)
}

override fun onDistributorSelected(distributor: String) =
super.onDistributorSelected(distributor).also {
// send message to main activity that it can move on after waiting
EventBus.getDefault().post(MainActivity.ProceedToConversationsListMessageEvent())
}
}.apply {
instances = listOf(accountName)
mayUseCurrent = !forceChoose
mayUseDefault = !forceChoose
}.run()

return retVal
}

fun unregisterForPushMessaging(context: Context, accountName: String) =
// try and unregister with unified push distributor
UnifiedPush.unregister(context, accountName).also {
Log.d(TAG, "unregisterForPushMessaging($accountName)")
}
}

override fun onMessage(message: PushMessage, instance: String) {
// wake lock to get the notification background job to execute more promptly since it will take eons to run
// if phone is dozing. default client ring time is 45 seconds so it should be more than that
PowerManagerUtils().acquireTimedPartialLock(MESSAGE_RECEIVED_WAKE_LOCK_TIMEOUT)

Log.d(TAG, "onMessage()")

val messageString = message.content.toString(Charsets.UTF_8)

if (messageString.isNotEmpty() && instance.isNotEmpty()) {
val messageData = Data.Builder()
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, messageString)
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, instance)
.putInt(
BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE,
NotificationWorker.Companion.BackendType.UNIFIED_PUSH.value
)
.build()
val notificationWork =
OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
WorkManager.getInstance(this).enqueue(notificationWork)

Log.d(TAG, "expedited NotificationWorker queued")
}
}

override fun onNewEndpoint(endpoint: PushEndpoint, instance: String) {
Log.d(TAG, "onNewEndpoint(${endpoint.url}, $instance)")
}

override fun onRegistrationFailed(reason: FailedReason, instance: String) =
// the registration is not possible, eg. no network
// force unregister to make sure cleaned up. re-register will be re-attempted next time
UnifiedPush.unregister(this, instance).also {
Log.d(TAG, "onRegistrationFailed(${reason.name}, $instance)")
}

override fun onUnregistered(instance: String) =
// this application is unregistered by the distributor from receiving push messages
// force unregister to make sure cleaned up. re-register will be re-attempted next time
UnifiedPush.unregister(this, instance).also {
Log.d(TAG, "onUnregistered($instance)")
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2017-2018 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils

import android.content.Context
import com.nextcloud.talk.UnifiedPush.Companion.getNumberOfDistributorsAvailable
import com.nextcloud.talk.UnifiedPush.Companion.registerForPushMessaging
import com.nextcloud.talk.UnifiedPush.Companion.unregisterForPushMessaging
import com.nextcloud.talk.interfaces.ClosedInterface

class ClosedInterfaceImpl : ClosedInterface {
override fun providerInstallerInstallIfNeededAsync() { /* nothing */
}

override fun isPushMessagingServiceAvailable(context: Context): Boolean {
return (getNumberOfDistributorsAvailable(context) > 0)
}

override fun pushMessagingProvider(): String {
return "unifiedpush"
}

override fun registerWithServer(context: Context, username: String?, forceChoose: Boolean): Boolean {
// unified push available in generic build
if (username == null) return false
return registerForPushMessaging(context, username, forceChoose)
}

override fun unregisterWithServer(context: Context, username: String?) {
// unified push available in generic build
if (username == null) return
unregisterForPushMessaging(context, username)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class NCFirebaseMessagingService : FirebaseMessagingService() {
val messageData = Data.Builder()
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, subject)
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, signature)
.putInt(
BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE,
NotificationWorker.Companion.BackendType.FIREBASE_CLOUD_MESSAGING.value
)
.build()
val notificationWork =
OneTimeWorkRequest.Builder(NotificationWorker::class.java).setInputData(messageData)
Expand Down
19 changes: 16 additions & 3 deletions app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/
package com.nextcloud.talk.utils

import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.work.ExistingPeriodicWorkPolicy
Expand All @@ -26,7 +27,13 @@ import java.util.concurrent.TimeUnit
@AutoInjector(NextcloudTalkApplication::class)
class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallListener {

override val isGooglePlayServicesAvailable: Boolean = isGPlayServicesAvailable()
override fun isPushMessagingServiceAvailable(context: Context): Boolean {
return isGPlayServicesAvailable()
}

override fun pushMessagingProvider(): String {
return "gplay"
}

override fun providerInstallerInstallIfNeededAsync() {
NextcloudTalkApplication.sharedApplication?.let {
Expand All @@ -49,7 +56,7 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
val api = GoogleApiAvailability.getInstance()
val code =
NextcloudTalkApplication.sharedApplication?.let {
api.isGooglePlayServicesAvailable(it.applicationContext)
api.isPushMessagingAvailable(it.applicationContext)
}
return if (code == ConnectionResult.SUCCESS) {
true
Expand All @@ -59,11 +66,17 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
}
}

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

setUpPeriodicTokenRefreshFromFCM()

return false
}

override fun unregisterWithServer(context: Context, username: String?) {
// do nothing
}

private fun setUpPeriodicTokenRefreshFromFCM() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package com.nextcloud.talk.account

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Bundle
Expand Down Expand Up @@ -235,6 +236,9 @@ class AccountVerificationActivity : BaseActivity() {
}

private fun storeProfile(displayName: String?, userId: String, capabilitiesOverall: CapabilitiesOverall) {
// for capture by lambda used by subscribe() below
val activityContext: Context = this

userManager.storeProfile(
username,
UserManager.UserAttributes(
Expand All @@ -260,8 +264,8 @@ class AccountVerificationActivity : BaseActivity() {
@SuppressLint("SetTextI18n")
override fun onSuccess(user: User) {
internalAccountId = user.id!!
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
ClosedInterfaceImpl().setUpPushTokenRegistration()
if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) {
ClosedInterfaceImpl().registerWithServer(activityContext, user.username, false)
} else {
Log.w(TAG, "Skipping push registration.")
runOnUiThread {
Expand Down
Loading