diff --git a/README.md b/README.md
index 8bd80b0c7..471126c3e 100644
--- a/README.md
+++ b/README.md
@@ -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.
|||||||
|---|---|---|---|---|---|
@@ -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:
diff --git a/app/build.gradle b/app/build.gradle
index 6a63fb569..e119ea736 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
@@ -401,4 +405,4 @@ detekt {
ksp {
arg('room.schemaLocation', "$projectDir/schemas")
-}
\ No newline at end of file
+}
diff --git a/app/src/generic/AndroidManifest.xml b/app/src/generic/AndroidManifest.xml
new file mode 100644
index 000000000..25a74c141
--- /dev/null
+++ b/app/src/generic/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/generic/java/com/nextcloud/talk/UnifiedPush.kt b/app/src/generic/java/com/nextcloud/talk/UnifiedPush.kt
new file mode 100644
index 000000000..a1460adb7
--- /dev/null
+++ b/app/src/generic/java/com/nextcloud/talk/UnifiedPush.kt
@@ -0,0 +1,143 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Your Name
+ * 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 =
+ 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) =
+ 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)")
+ }
+}
diff --git a/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java b/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java
deleted file mode 100644
index cafaa071d..000000000
--- a/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Nextcloud Talk - Android Client
- *
- * SPDX-FileCopyrightText: 2022 Marcel Hibbe
- * SPDX-FileCopyrightText: 2017-2018 Mario Danic
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package com.nextcloud.talk.utils;
-
-
-import com.nextcloud.talk.interfaces.ClosedInterface;
-
-public class ClosedInterfaceImpl implements ClosedInterface {
- @Override
- public void providerInstallerInstallIfNeededAsync() {
- // does absolutely nothing :)
- }
-
- @Override
- public boolean isGooglePlayServicesAvailable() {
- return false;
- }
-
- @Override
- public void setUpPushTokenRegistration() {
- // no push notifications for generic build variant
- }
-}
diff --git a/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt b/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt
new file mode 100644
index 000000000..b6ce04192
--- /dev/null
+++ b/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt
@@ -0,0 +1,39 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2022 Marcel Hibbe
+ * SPDX-FileCopyrightText: 2017-2018 Mario Danic
+ * 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)
+ }
+}
diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/NCFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/NCFirebaseMessagingService.kt
index ac4e888ae..e98cd123d 100644
--- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/NCFirebaseMessagingService.kt
+++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/NCFirebaseMessagingService.kt
@@ -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)
diff --git a/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt b/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt
index 6a2fd0d3b..77c74e8e4 100644
--- a/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt
+++ b/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt
@@ -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
@@ -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 {
@@ -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
@@ -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() {
diff --git a/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt b/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt
index 1eb50c189..665e8b7ce 100644
--- a/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt
@@ -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
@@ -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(
@@ -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 {
diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
index 0828b475f..cc4e66576 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
@@ -46,10 +46,14 @@ import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
+import org.greenrobot.eventbus.ThreadMode
+import org.greenrobot.eventbus.Subscribe
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class MainActivity : BaseActivity(), ActionBarProvider {
+ class ProceedToConversationsListMessageEvent
+
lateinit var binding: ActivityMainBinding
@Inject
@@ -76,9 +80,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
})
// Set the default theme to replace the launch screen theme.
- setTheme(R.style.AppTheme)
binding = ActivityMainBinding.inflate(layoutInflater)
- setContentView(binding.root)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@@ -138,6 +140,11 @@ class MainActivity : BaseActivity(), ActionBarProvider {
super.onStop()
}
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onMessageEvent(event: ProceedToConversationsListMessageEvent) {
+ openConversationList()
+ }
+
private fun openConversationList() {
val intent = Intent(this, ConversationsListActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
@@ -255,6 +262,8 @@ class MainActivity : BaseActivity(), ActionBarProvider {
appPreferences.isDbRoomMigrated = true
}
+ // for capture by lambda used by subscribe() below
+ val activityContext: Context = this
userManager.users.subscribe(object : SingleObserver> {
override fun onSubscribe(d: Disposable) {
// unused atm
@@ -262,9 +271,16 @@ class MainActivity : BaseActivity(), ActionBarProvider {
override fun onSuccess(users: List) {
if (users.isNotEmpty()) {
- ClosedInterfaceImpl().setUpPushTokenRegistration()
runOnUiThread {
- openConversationList()
+ if (
+ !ClosedInterfaceImpl().registerWithServer(
+ activityContext,
+ users.first().username,
+ false)
+ ) {
+ // if push registration does not need to show a dialog, open the conversation list now
+ openConversationList()
+ }
}
} else {
runOnUiThread {
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
index 1086d9d8e..2335578db 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
@@ -280,7 +280,7 @@ class ConversationsListActivity :
// handle notification permission on API level >= 33
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
!platformPermissionUtil.isPostNotificationsPermissionGranted() &&
- ClosedInterfaceImpl().isGooglePlayServicesAvailable
+ ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
) {
requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
@@ -1724,7 +1724,7 @@ class ConversationsListActivity :
Log.d(TAG, "Notification permission was granted")
if (!PowerManagerUtils().isIgnoringBatteryOptimizations() &&
- ClosedInterfaceImpl().isGooglePlayServicesAvailable
+ ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
) {
val dialogText = String.format(
context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text),
@@ -1819,7 +1819,7 @@ class ConversationsListActivity :
return settingsOfUserAreWrong &&
shouldShowNotificationWarningByUserChoice() &&
- ClosedInterfaceImpl().isGooglePlayServicesAvailable
+ ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
}
private fun openConversation(textToPaste: String? = "") {
diff --git a/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseActivity.kt b/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseActivity.kt
index c2dcf88fa..2370d0551 100644
--- a/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseActivity.kt
@@ -70,7 +70,8 @@ class DiagnoseActivity : BaseActivity() {
@Inject
lateinit var platformPermissionUtil: PlatformPermissionUtil
- private var isGooglePlayServicesAvailable: Boolean = false
+ private var isPushMessagingServiceAvailable: Boolean = false
+ private var pushMessagingProvider: String = ""
sealed class DiagnoseElement {
data class DiagnoseHeadline(val headline: String) : DiagnoseElement()
@@ -89,7 +90,8 @@ class DiagnoseActivity : BaseActivity() {
)[DiagnoseViewModel::class.java]
val colorScheme = viewThemeUtils.getColorScheme(this)
- isGooglePlayServicesAvailable = ClosedInterfaceImpl().isGooglePlayServicesAvailable
+ isPushMessagingServiceAvailable = ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)
+ pushMessagingProvider = ClosedInterfaceImpl().pushMessagingProvider()
setContent {
val backgroundColor = colorResource(id = R.color.bg_default)
@@ -131,7 +133,7 @@ class DiagnoseActivity : BaseActivity() {
viewState = viewState,
onTestPushClick = { diagnoseViewModel.fetchTestPushResult() },
onDismissDialog = { diagnoseViewModel.dismissDialog() },
- isGooglePlayServicesAvailable = isGooglePlayServicesAvailable
+ pushMessagingProvider = pushMessagingProvider
)
}
}
@@ -225,17 +227,16 @@ class DiagnoseActivity : BaseActivity() {
value = Build.VERSION.SDK_INT.toString()
)
- if (isGooglePlayServicesAvailable) {
- addDiagnosisEntry(
- key = context.resources.getString(R.string.nc_diagnose_gplay_available_title),
- value = context.resources.getString(R.string.nc_diagnose_gplay_available_yes)
+ addDiagnosisEntry(
+ key = context.resources.getString(R.string.nc_diagnose_push_notifications_available_title),
+ value = context.resources.getString(
+ when (pushMessagingProvider) {
+ "gplay" -> R.string.nc_diagnose_push_notifications_gplay
+ "unifiedpush" -> R.string.nc_diagnose_push_notifications_unified_push
+ else -> R.string.nc_diagnose_push_notifications_available_no
+ }
)
- } else {
- addDiagnosisEntry(
- key = context.resources.getString(R.string.nc_diagnose_gplay_available_title),
- value = context.resources.getString(R.string.nc_diagnose_gplay_available_no)
- )
- }
+ )
}
@SuppressLint("SetTextI18n")
@@ -258,8 +259,8 @@ class DiagnoseActivity : BaseActivity() {
value = BuildConfig.FLAVOR
)
- if (isGooglePlayServicesAvailable) {
- setupAppValuesForGooglePlayServices()
+ if (isPushMessagingServiceAvailable) {
+ setupAppValuesForPushMessaging()
}
addDiagnosisEntry(
@@ -269,7 +270,7 @@ class DiagnoseActivity : BaseActivity() {
}
@Suppress("Detekt.LongMethod")
- private fun setupAppValuesForGooglePlayServices() {
+ private fun setupAppValuesForPushMessaging() {
addDiagnosisEntry(
key = context.resources.getString(R.string.nc_diagnose_battery_optimization_title),
value = if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
@@ -307,37 +308,39 @@ class DiagnoseActivity : BaseActivity() {
)
)
- addDiagnosisEntry(
- key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_title),
- value = if (appPreferences.pushToken.isNullOrEmpty()) {
- context.resources.getString(R.string.nc_diagnose_firebase_push_token_missing)
- } else {
- "${appPreferences.pushToken.substring(0, PUSH_TOKEN_PREFIX_END)}..."
- }
- )
+ if (pushMessagingProvider == "gplay") {
+ addDiagnosisEntry(
+ key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_title),
+ value = if (appPreferences.pushToken.isNullOrEmpty()) {
+ context.resources.getString(R.string.nc_diagnose_firebase_push_token_missing)
+ } else {
+ "${appPreferences.pushToken.substring(0, PUSH_TOKEN_PREFIX_END)}..."
+ }
+ )
- addDiagnosisEntry(
- key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_generated),
- value = if (appPreferences.pushTokenLatestGeneration != null &&
- appPreferences.pushTokenLatestGeneration != 0L
- ) {
- DisplayUtils.unixTimeToHumanReadable(
- appPreferences
- .pushTokenLatestGeneration
- )
- } else {
- context.resources.getString(R.string.nc_common_unknown)
- }
- )
+ addDiagnosisEntry(
+ key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_generated),
+ value = if (appPreferences.pushTokenLatestGeneration != null &&
+ appPreferences.pushTokenLatestGeneration != 0L
+ ) {
+ DisplayUtils.unixTimeToHumanReadable(
+ appPreferences
+ .pushTokenLatestGeneration
+ )
+ } else {
+ context.resources.getString(R.string.nc_common_unknown)
+ }
+ )
- addDiagnosisEntry(
- key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_fetch),
- value = if (appPreferences.pushTokenLatestFetch != null && appPreferences.pushTokenLatestFetch != 0L) {
- DisplayUtils.unixTimeToHumanReadable(appPreferences.pushTokenLatestFetch)
- } else {
- context.resources.getString(R.string.nc_common_unknown)
- }
- )
+ addDiagnosisEntry(
+ key = context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_fetch),
+ value = if (appPreferences.pushTokenLatestFetch != null && appPreferences.pushTokenLatestFetch != 0L) {
+ DisplayUtils.unixTimeToHumanReadable(appPreferences.pushTokenLatestFetch)
+ } else {
+ context.resources.getString(R.string.nc_common_unknown)
+ }
+ )
+ }
}
private fun setupAccountValues() {
@@ -371,7 +374,7 @@ class DiagnoseActivity : BaseActivity() {
translateBoolean(currentUser.capabilities?.notificationsCapability?.features?.isNotEmpty())
)
- if (isGooglePlayServicesAvailable) {
+ if ((isPushMessagingServiceAvailable) && (pushMessagingProvider == "gplay")) {
setupPushRegistrationDiagnose()
}
diff --git a/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseContentComposable.kt b/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseContentComposable.kt
index f6cfba19e..eaa2d732e 100644
--- a/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseContentComposable.kt
+++ b/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseContentComposable.kt
@@ -59,7 +59,7 @@ fun DiagnoseContentComposable(
viewState: NotificationUiState,
onTestPushClick: () -> Unit,
onDismissDialog: () -> Unit,
- isGooglePlayServicesAvailable: Boolean
+ pushMessagingProvider: String
) {
val context = LocalContext.current
Column(
@@ -96,7 +96,7 @@ fun DiagnoseContentComposable(
}
}
}
- if (isGooglePlayServicesAvailable) {
+ if (pushMessagingProvider == "gplay") {
ShowTestPushButton(onTestPushClick)
}
ShowNotificationData(isLoading, showDialog, context, viewState, onDismissDialog)
@@ -254,6 +254,6 @@ fun DiagnoseContentPreview() {
NotificationUiState.Success("Test notification successful"),
{},
{},
- true
+ "gplay"
)
}
diff --git a/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt b/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt
index eda906244..3b939caa1 100644
--- a/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt
+++ b/app/src/main/java/com/nextcloud/talk/interfaces/ClosedInterface.kt
@@ -7,9 +7,13 @@
*/
package com.nextcloud.talk.interfaces
+import android.content.Context
+
interface ClosedInterface {
- val isGooglePlayServicesAvailable: Boolean
+ fun isPushMessagingServiceAvailable(context: Context): Boolean
+ fun pushMessagingProvider(): String
fun providerInstallerInstallIfNeededAsync()
- fun setUpPushTokenRegistration()
+ fun registerWithServer(context: Context, username: String?, forceChoose: Boolean): Boolean
+ fun unregisterWithServer(context: Context, username: String?)
}
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
index f3b30a5ee..4eb58662e 100644
--- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
+++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
@@ -139,10 +139,38 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
private lateinit var notificationManager: NotificationManagerCompat
override fun doWork(): Result {
+ Log.d(TAG, "started work")
+
sharedApplication!!.componentApplication.inject(this)
context = applicationContext
- initDecryptedData(inputData)
+ when (inputData.getInt(BundleKeys.KEY_NOTIFICATION_BACKEND_TYPE, -1)) {
+ Companion.BackendType.FIREBASE_CLOUD_MESSAGING.value -> {
+ initDecryptedData(inputData)
+ }
+ Companion.BackendType.UNIFIED_PUSH.value -> {
+ pushMessage = LoganSquare.parse(
+ inputData.getString(BundleKeys.KEY_NOTIFICATION_SUBJECT),
+ DecryptedPushMessage::class.java
+ )
+
+ userManager.users.blockingGet()?.let {
+ for (user in it) {
+ if (user.username == inputData.getString(BundleKeys.KEY_NOTIFICATION_SIGNATURE)) {
+ signatureVerification = SignatureVerification(true, user)
+ break
+ }
+ }
+ }
+
+ Log.d(TAG, "parsed UP message")
+ }
+ else -> {
+ // message not received from a valid backend
+ return Result.failure()
+ }
+ }
+
initNcApiAndCredentials()
notificationManager = NotificationManagerCompat.from(context!!)
@@ -1028,5 +1056,14 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
private const val TIMER_START = 1
private const val TIMER_COUNT = 12
private const val TIMER_DELAY: Long = 5
+ private const val GET_ROOM_RETRY_COUNT: Long = 3
+ enum class BackendType(val value: Int) {
+ NONE(-1),
+ FIREBASE_CLOUD_MESSAGING(1),
+ UNIFIED_PUSH(2);
+ companion object {
+ fun fromInt(value: Int) = BackendType.values().first { it.value == value }
+ }
+ }
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java
index 6473d0e06..9d9165252 100644
--- a/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java
+++ b/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.java
@@ -40,15 +40,18 @@ public class PushRegistrationWorker extends Worker {
@Inject
OkHttpClient okHttpClient;
+ Context workerContext;
+
public PushRegistrationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
+ workerContext = context;
}
@NonNull
@Override
public Result doWork() {
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
- if (new ClosedInterfaceImpl().isGooglePlayServicesAvailable()) {
+ if (new ClosedInterfaceImpl().isPushMessagingServiceAvailable(workerContext)) {
Data data = getInputData();
String origin = data.getString("origin");
Log.d(TAG, "PushRegistrationWorker called via " + origin);
diff --git a/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt b/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt
index 73b88bbfb..dfa794c8f 100644
--- a/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt
@@ -161,6 +161,7 @@ class SettingsActivity :
)
setupDiagnose()
+ setupResetPushPreference()
setupPrivacyUrl()
setupSourceCodeUrl()
binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME)
@@ -292,8 +293,8 @@ class SettingsActivity :
@SuppressLint("StringFormatInvalid")
@Suppress("LongMethod")
private fun setupNotificationPermissionSettings() {
- if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
- binding.settingsGplayOnlyWrapper.visibility = View.VISIBLE
+ if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) {
+ binding.settingsPushNotificationsOnlyWrapper.visibility = View.VISIBLE
setTroubleshootingClickListenersIfNecessary()
@@ -375,8 +376,20 @@ class SettingsActivity :
binding.settingsNotificationsPermissionWrapper.visibility = View.GONE
}
} else {
- binding.settingsGplayOnlyWrapper.visibility = View.GONE
- binding.settingsGplayNotAvailable.visibility = View.VISIBLE
+ binding.settingsPushNotificationsOnlyWrapper.visibility = View.GONE
+
+ val pushMessagingProvider = ClosedInterfaceImpl().pushMessagingProvider()
+ if (pushMessagingProvider == "gplay") {
+ binding.settingsPushNotificationsNotAvailableText.append("\n".plus(
+ resources!!.getString(R.string.nc_diagnose_push_notifications_gplay).plus("\n").plus(resources!!
+ .getString(R.string.nc_diagnose_push_notifications_gplay_rectify))))
+ } else if (pushMessagingProvider == "unifiedpush") {
+ binding.settingsPushNotificationsNotAvailableText.append("\n".plus(
+ resources!!.getString(R.string.nc_diagnose_push_notifications_unified_push).plus("\n").plus
+ (resources!!.getString(R.string.nc_diagnose_push_notifications_unified_push_rectify))))
+ }
+
+ binding.settingsPushNotificationsNotAvailable.visibility = View.VISIBLE
}
}
@@ -535,6 +548,17 @@ class SettingsActivity :
}
}
+ private fun setupResetPushPreference() {
+ binding.resetPushNotificationsWrapper.setOnClickListener {
+ for (user in userManager.users.blockingGet()) {
+ ClosedInterfaceImpl().apply {
+ unregisterWithServer(binding.root.context, user.username)
+ registerWithServer(binding.root.context, user.username, true)
+ }
+ }
+ }
+ }
+
private fun setupLicenceSetting() {
if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_gpl3_url))) {
binding.settingsLicence.setOnClickListener {
@@ -932,7 +956,7 @@ class SettingsActivity :
binding.settingsShowNotificationWarningSwitch.isChecked =
appPreferences.showRegularNotificationWarning
- if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
+ if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) {
binding.settingsShowNotificationWarning.setOnClickListener {
val isChecked = binding.settingsShowNotificationWarningSwitch.isChecked
binding.settingsShowNotificationWarningSwitch.isChecked = !isChecked
diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
index 4080f315a..795a16010 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
@@ -32,6 +32,7 @@ object BundleKeys {
const val KEY_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL"
const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT"
const val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE"
+ const val KEY_NOTIFICATION_BACKEND_TYPE = "KEY_NOTIFICATION_BACKEND_TYPE"
const val KEY_INTERNAL_USER_ID = "KEY_INTERNAL_USER_ID"
const val KEY_CONVERSATION_TYPE = "KEY_CONVERSATION_TYPE"
const val KEY_INVITED_PARTICIPANTS = "KEY_INVITED_PARTICIPANTS"
diff --git a/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt
index d03033fba..28e87d022 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt
@@ -60,6 +60,14 @@ class PowerManagerUtils {
orientation = context!!.resources.configuration.orientation
}
+ fun acquireTimedPartialLock(msTimeout: Long): PowerManager.WakeLock {
+ // wakelock will be auto-released after the given time
+ val pm = context!!.getSystemService(POWER_SERVICE) as PowerManager
+ val wakeLock = pm!!.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:timedPartialwakelock")
+ wakeLock!!.acquire(msTimeout)
+ return wakeLock
+ }
+
fun isIgnoringBatteryOptimizations(): Boolean {
val packageName = context!!.packageName
val pm = context!!.getSystemService(POWER_SERVICE) as PowerManager
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index 28d31573a..5d1ae724b 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -205,7 +205,7 @@
android:textStyle="bold"/>
@@ -277,7 +277,7 @@
+ android:text="@string/nc_diagnose_push_notifications_available_no" />
@@ -655,14 +656,36 @@
android:id="@+id/diagnose_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="@dimen/headline_text_size"
- android:text="@string/nc_settings_diagnose_title"/>
+ android:text="@string/nc_settings_diagnose_title"
+ android:textSize="@dimen/headline_text_size" />
+ android:text="@string/nc_settings_diagnose_subtitle" />
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 887a4849f..91c7f7b15 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -201,9 +201,14 @@ How to translate with transifex:
App name
App version
Registered users
- Google Play services
- Google Play services are available
- Google Play services are not available. Notifications are not supported
+ Push notification services
+ Push notifications are not available.
+ Push notifications are using the Google Play engine.
+ An alternate version of Talk is available for devices
+ without the Google Play Apis.
+ Push notifications are using the UnifiedPush engine.
+ Notifications can be enabled via UnifiedPush.
+ Please see https://unifiedpush.org
Battery settings
Battery optimization is enabled which might cause issues. You should disable battery optimization!
Battery optimization is ignored, all fine
@@ -860,4 +865,8 @@ How to translate with transifex:
Unarchived %1$s
Conversation is archived
Local time: %1$s
+
+ Reset push notifications
+ Reset push notifications in case of push messaging issues
+ Push notifications reset
diff --git a/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java b/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java
index 94778d817..7fc3530d6 100644
--- a/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java
+++ b/app/src/qa/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java
@@ -8,7 +8,11 @@
package com.nextcloud.talk.utils;
+import android.content.Context;
import com.nextcloud.talk.interfaces.ClosedInterface;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
public class ClosedInterfaceImpl implements ClosedInterface {
@Override
@@ -17,12 +21,23 @@ public class ClosedInterfaceImpl implements ClosedInterface {
}
@Override
- public boolean isGooglePlayServicesAvailable() {
+ public boolean isPushMessagingServiceAvailable(Context context) {
return false;
}
@Override
- public void setUpPushTokenRegistration() {
+ public String pushMessagingProvider() {
+ return "qa";
+ }
+
+ @Override
+ public boolean registerWithServer(Context context, @Nullable String username) {
+ // no push notifications for qa build flavour :(
+ return false;
+ }
+
+ @Override
+ public void unregisterWithServer(@NonNull Context context, @Nullable String username) {
// no push notifications for qa build flavour :(
}
}
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 543800c01..b0b37a273 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -3,6 +3,10 @@
true
true
+
+
+
+
@@ -12,6 +16,9 @@
+
+
+
@@ -324,6 +331,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -14587,7 +14646,7 @@
-
+