Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changes to enable unified push notifications in generic build. Signed… #4169

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
5 changes: 4 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ dependencies {
implementation 'com.github.nextcloud.android-common:ui:0.23.0'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:121.6167.0'

// unified push library for generic flavour
genericImplementation 'org.codeberg.UnifiedPush:android-connector:2.4.0'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
genericImplementation 'org.codeberg.UnifiedPush:android-connector:2.4.0'
genericImplementation 'org.unifiedpush.android:connector:2.5.0'


gplayImplementation 'com.google.android.gms:play-services-base:18.5.0'
gplayImplementation "com.google.firebase:firebase-messaging:24.0.1"

Expand Down Expand Up @@ -418,4 +421,4 @@ detekt {

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

<application>
<receiver android:exported="true" android:enabled="true" android:name=".receivers.UnifiedPush">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT"/>
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED"/>
</intent-filter>
</receiver>
</application>
</manifest>
163 changes: 163 additions & 0 deletions app/src/generic/java/com/nextcloud/talk/receivers/UnifiedPush.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Your Name <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.receivers

import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.jobs.NotificationWorker
import com.nextcloud.talk.utils.bundle.BundleKeys
import org.greenrobot.eventbus.EventBus
import org.unifiedpush.android.connector.MessagingReceiver
import org.unifiedpush.android.connector.PREF_MASTER
import org.unifiedpush.android.connector.PREF_MASTER_NO_DISTRIB_DIALOG_ACK
import org.unifiedpush.android.connector.UnifiedPush

class UnifiedPush : MessagingReceiver() {
private val TAG: String? = UnifiedPush::class.java.simpleName

companion object {
fun getNumberOfDistributorsAvailable(context: Context) : Int {
return UnifiedPush.getDistributors(context).size
}

private fun resetSeenNoDistributorsInfo(context: Context) {
context.getSharedPreferences(PREF_MASTER, Context.MODE_PRIVATE)
.edit().putBoolean(PREF_MASTER_NO_DISTRIB_DIALOG_ACK, false).apply()
}

fun registerForPushMessaging(context: Context, accountName: String): Boolean {
// if a distributor is registered and available, re-register to ensure in sync
if (UnifiedPush.getSavedDistributor(context) !== null) {
UnifiedPush.registerApp(context, accountName)
return false
}

val distributors = UnifiedPush.getDistributors(context)

// if no distributors available
if (distributors.isEmpty()) {
// if user has already been shown the info dialog, return
val preferences = context.getSharedPreferences(PREF_MASTER, Context.MODE_PRIVATE)
if (preferences.getBoolean(PREF_MASTER_NO_DISTRIB_DIALOG_ACK, false) == true) {
return false
}

// show user some info about unified push
val message = TextView(context)
val s = SpannableString(context.getString(R.string.unified_push_no_distributors_dialog_text))
Linkify.addLinks(s, Linkify.WEB_URLS)
message.text = s
message.movementMethod = LinkMovementMethod.getInstance()
message.setPadding(32, 32, 32, 32)
AlertDialog.Builder(context)
.setTitle(context.getString(R.string.unified_push_no_distributors_dialog_title))
.setView(message)
.setPositiveButton(context.getString(R.string.nc_ok)) {
_, _ -> preferences.edit().putBoolean(PREF_MASTER_NO_DISTRIB_DIALOG_ACK, true).apply()
}.setOnDismissListener {
// send message to main activity that it can move on to it's next default activity
EventBus.getDefault().post(MainActivity.ProceedToConversationsListMessageEvent())
}.show()

return true // have a dialog to show, need main activity to wait
}

// 1 distributor available
if (distributors.size == 1) {
UnifiedPush.saveDistributor(context, distributors.first())
UnifiedPush.registerApp(context, accountName)
return false
}

// multiple distributors available, show dialog for user to choose
val distributorsArray = distributors.toTypedArray()
val distributorsNameArray = distributorsArray.map {
try {
val ai = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.getApplicationInfo(
it,
PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong())
)
} else {
context.packageManager.getApplicationInfo(it, 0)
}
context.packageManager.getApplicationLabel(ai)
} catch (e: PackageManager.NameNotFoundException) {
it
} as String
}.toTypedArray()

AlertDialog.Builder(context)
.setTitle(context.getString(R.string.unified_push_choose_distributor_title))
.setItems(distributorsNameArray) { _, which ->
val distributor = distributorsArray[which]
UnifiedPush.saveDistributor(context, distributor)
UnifiedPush.registerApp(context, accountName)
}.setOnDismissListener {
// send message to main activity that it can move on to it's next default activity
EventBus.getDefault().post(MainActivity.ProceedToConversationsListMessageEvent())
}.show()

return true // have a dialog to show, need main activity to wait
}

fun unregisterForPushMessaging(context: Context, accountName: String) {
// try and unregister with unified push distributor
UnifiedPush.unregisterApp(context, instance = accountName)
resetSeenNoDistributorsInfo(context)
}
}

override fun onMessage(context: Context, message: ByteArray, instance: String) {
Log.d(TAG, "UP onMessage")

val messageString = message.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)
.build()
WorkManager.getInstance().enqueue(notificationWork)
}
}

override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
// called when a new endpoint is to be used for sending push messages
// do nothing
}

override fun onRegistrationFailed(context: Context, instance: String) {
// called when the registration is not possible, eg. no network
// just dump the registration to make sure it is cleaned up. re-register will be auto-reattempted
unregisterForPushMessaging(context, instance)

}

override fun onUnregistered(context: Context, instance: String) {
// called when this application is remotely unregistered from receiving push messages
unregisterForPushMessaging(context, instance)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
package com.nextcloud.talk.utils;


import android.content.Context;
import com.nextcloud.talk.interfaces.ClosedInterface;
import com.nextcloud.talk.receivers.UnifiedPush;
import androidx.annotation.NonNull;


public class ClosedInterfaceImpl implements ClosedInterface {
@Override
Expand All @@ -17,12 +21,29 @@ public void providerInstallerInstallIfNeededAsync() {
}

@Override
public boolean isGooglePlayServicesAvailable() {
return false;
public boolean isPushMessagingServiceAvailable(Context context) {
return (UnifiedPush.Companion.getNumberOfDistributorsAvailable(context) > 0);
}

@Override
public String pushMessagingProvider() {
return "unifiedpush";
}

@Override
public boolean registerWithServer(@NonNull Context context, String username) {
// unified push available in generic build
if (username == null)
return false;
return UnifiedPush.Companion.registerForPushMessaging(context, username);
}

@NonNull
@Override
public void setUpPushTokenRegistration() {
// no push notifications for generic build variant
public void unregisterWithServer(@NonNull Context context, @NonNull String username) {
// unified push available in generic build
if (username == null)
return;
UnifiedPush.Companion.unregisterForPushMessaging(context, username);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ 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
18 changes: 16 additions & 2 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 @@ -18,6 +19,7 @@ import autodagger.AutoInjector
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.security.ProviderInstaller
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.interfaces.ClosedInterface
import com.nextcloud.talk.jobs.GetFirebasePushTokenWorker
Expand All @@ -26,7 +28,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 Down Expand Up @@ -59,11 +67,17 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
}
}

override fun setUpPushTokenRegistration() {
override fun registerWithServer(context: Context, username: String?): 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,8 @@ class AccountVerificationActivity : BaseActivity() {
}

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

userManager.storeProfile(
username,
UserManager.UserAttributes(
Expand All @@ -260,8 +263,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)
} else {
Log.w(TAG, "Skipping push registration.")
runOnUiThread {
Expand Down
Loading