diff --git a/README.md b/README.md index 4ea25808be..a58e2a5359 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 5d02952046..8798d8ec65 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' + gplayImplementation 'com.google.android.gms:play-services-base:18.5.0' gplayImplementation "com.google.firebase:firebase-messaging:24.0.1" @@ -418,4 +421,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 0000000000..842263df52 --- /dev/null +++ b/app/src/generic/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/app/src/generic/java/com/nextcloud/talk/receivers/UnifiedPush.kt b/app/src/generic/java/com/nextcloud/talk/receivers/UnifiedPush.kt new file mode 100644 index 0000000000..36c086ffa2 --- /dev/null +++ b/app/src/generic/java/com/nextcloud/talk/receivers/UnifiedPush.kt @@ -0,0 +1,163 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2024 Your Name + * 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) + } +} diff --git a/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java b/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java index cafaa071d5..5bad91d55c 100644 --- a/app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java +++ b/app/src/generic/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 com.nextcloud.talk.receivers.UnifiedPush; +import androidx.annotation.NonNull; + public class ClosedInterfaceImpl implements ClosedInterface { @Override @@ -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); } } 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 ac4e888aee..5e0d44aff5 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,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) 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 6a2fd0d3b9..dbc108bd9c 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 @@ -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 @@ -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 { @@ -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() { 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 0b3a3caefe..7a58cd884d 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,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( @@ -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 { 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 e56f4f8983..be58a0333c 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) @@ -257,6 +264,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { appPreferences.isDbRoomMigrated = true } + val activityContext: Context = this // for capture by lambda used by subscribe() below userManager.users.subscribe(object : SingleObserver> { override fun onSubscribe(d: Disposable) { // unused atm @@ -264,9 +272,13 @@ class MainActivity : BaseActivity(), ActionBarProvider { override fun onSuccess(users: List) { if (users.isNotEmpty()) { - ClosedInterfaceImpl().setUpPushTokenRegistration() runOnUiThread { - openConversationList() + var needUIFeedbackFromUser = ClosedInterfaceImpl().registerWithServer(activityContext, + users.first().username) + // if push registration does not need to show a dialog, open the conversation list now + if (needUIFeedbackFromUser == false) { + 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 9b9d74f549..6828e2af42 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt @@ -250,7 +250,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), @@ -1428,7 +1428,7 @@ class ConversationsListActivity : // whenever user allowed notifications, also check to ignore battery optimization if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (!PowerManagerUtils().isIgnoringBatteryOptimizations() && - ClosedInterfaceImpl().isGooglePlayServicesAvailable + ClosedInterfaceImpl().isPushMessagingServiceAvailable(context) ) { val dialogText = String.format( context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text), 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 68e69dc65d..1437141e9c 100644 --- a/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/diagnose/DiagnoseActivity.kt @@ -64,7 +64,8 @@ class DiagnoseActivity : BaseActivity() { @Inject lateinit var platformPermissionUtil: PlatformPermissionUtil - private var isGooglePlayServicesAvailable: Boolean = false + private var isPushMessagingServiceAvailable: Boolean = false + private var pushMessagingProvider: String = "" private val markdownText = SpannableStringBuilder() @@ -82,7 +83,8 @@ class DiagnoseActivity : BaseActivity() { super.onResume() supportActionBar?.show() - isGooglePlayServicesAvailable = ClosedInterfaceImpl().isGooglePlayServicesAvailable + isPushMessagingServiceAvailable = ClosedInterfaceImpl().isPushMessagingServiceAvailable(context) + pushMessagingProvider = ClosedInterfaceImpl().pushMessagingProvider() markdownText.clear() setupMetaValues() @@ -214,12 +216,14 @@ class DiagnoseActivity : BaseActivity() { addKey(context.resources.getString(R.string.nc_diagnose_android_version_title)) addValue(Build.VERSION.SDK_INT.toString()) - if (isGooglePlayServicesAvailable) { - addKey(context.resources.getString(R.string.nc_diagnose_gplay_available_title)) - addValue(context.resources.getString(R.string.nc_diagnose_gplay_available_yes)) - } else { - addKey(context.resources.getString(R.string.nc_diagnose_gplay_available_title)) - addValue(context.resources.getString(R.string.nc_diagnose_gplay_available_no)) + addKey(context.resources.getString(R.string.nc_diagnose_push_notifications_available_title)) + if (pushMessagingProvider == "gplay") { + addValue(context.resources.getString(R.string.nc_diagnose_push_notifications_gplay)) + } else if (pushMessagingProvider == "unifiedpush") { + addValue(context.resources.getString(R.string.nc_diagnose_push_notifications_unified_push)) + } + if (isPushMessagingServiceAvailable == false) { + addValue(context.resources.getString(R.string.nc_diagnose_push_notifications_available_no)) } } @@ -237,7 +241,7 @@ class DiagnoseActivity : BaseActivity() { addKey(context.resources.getString(R.string.nc_diagnose_flavor)) addValue(BuildConfig.FLAVOR) - if (isGooglePlayServicesAvailable) { + if (isPushMessagingServiceAvailable) { addKey(context.resources.getString(R.string.nc_diagnose_battery_optimization_title)) if (PowerManagerUtils().isIgnoringBatteryOptimizations()) { @@ -270,30 +274,32 @@ class DiagnoseActivity : BaseActivity() { ) ) - addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_title)) - if (appPreferences.pushToken.isNullOrEmpty()) { - addValue(context.resources.getString(R.string.nc_diagnose_firebase_push_token_missing)) - } else { - addValue("${appPreferences.pushToken.substring(0, 5)}...") - } + if (pushMessagingProvider == "gplay") { + addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_title)) + if (appPreferences.pushToken.isNullOrEmpty()) { + addValue(context.resources.getString(R.string.nc_diagnose_firebase_push_token_missing)) + } else { + addValue("${appPreferences.pushToken.substring(0, 5)}...") + } - addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_generated)) - if (appPreferences.pushTokenLatestGeneration != null && appPreferences.pushTokenLatestGeneration != 0L) { - addValue( - DisplayUtils.unixTimeToHumanReadable( - appPreferences - .pushTokenLatestGeneration + addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_generated)) + if (appPreferences.pushTokenLatestGeneration != null && appPreferences.pushTokenLatestGeneration != 0L) { + addValue( + DisplayUtils.unixTimeToHumanReadable( + appPreferences + .pushTokenLatestGeneration + ) ) - ) - } else { - addValue(context.resources.getString(R.string.nc_common_unknown)) - } + } else { + addValue(context.resources.getString(R.string.nc_common_unknown)) + } - addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_fetch)) - if (appPreferences.pushTokenLatestFetch != null && appPreferences.pushTokenLatestFetch != 0L) { - addValue(DisplayUtils.unixTimeToHumanReadable(appPreferences.pushTokenLatestFetch)) - } else { - addValue(context.resources.getString(R.string.nc_common_unknown)) + addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_fetch)) + if (appPreferences.pushTokenLatestFetch != null && appPreferences.pushTokenLatestFetch != 0L) { + addValue(DisplayUtils.unixTimeToHumanReadable(appPreferences.pushTokenLatestFetch)) + } else { + addValue(context.resources.getString(R.string.nc_common_unknown)) + } } } @@ -324,7 +330,7 @@ class DiagnoseActivity : BaseActivity() { ) ) - if (isGooglePlayServicesAvailable) { + if ((isPushMessagingServiceAvailable) && (pushMessagingProvider == "gplay")) { setupPushRegistrationDiagnose() } 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 eda9062443..cd761861cd 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,14 @@ */ 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?): 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 870bb94194..4145a7cfe6 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -148,7 +148,33 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor 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) + + val messageUser = inputData.getString(BundleKeys.KEY_NOTIFICATION_SIGNATURE) + val users = userManager!!.users.blockingGet() + if (users != null && users.size > 0) { + for (user in users) { + if (user.username == messageUser) { + signatureVerification = SignatureVerification(true, user) + break + } + } + } + if (signatureVerification === null) + return Result.failure() + } + else -> { + // message not received from a valid backend + return Result.failure() + } + } + initNcApiAndCredentials() notificationManager = NotificationManagerCompat.from(context!!) @@ -1050,5 +1076,13 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor 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 6473d0e06d..9d9165252b 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 e86439a59a..abb34ccdc3 100644 --- a/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt @@ -149,6 +149,7 @@ class SettingsActivity : BaseActivity(), SetPhoneNumberDialogFragment.SetPhoneNu ) setupDiagnose() + setupResetPushPreference() setupPrivacyUrl() setupSourceCodeUrl() binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME) @@ -262,8 +263,8 @@ class SettingsActivity : BaseActivity(), SetPhoneNumberDialogFragment.SetPhoneNu @Suppress("LongMethod") private fun setupNotificationPermissionSettings() { - if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { - binding.settingsGplayOnlyWrapper.visibility = View.VISIBLE + if (ClosedInterfaceImpl().isPushMessagingServiceAvailable(context)) { + binding.settingsPushNotificationsOnlyWrapper.visibility = View.VISIBLE setTroubleshootingClickListenersIfNecessary() @@ -327,8 +328,20 @@ class SettingsActivity : BaseActivity(), SetPhoneNumberDialogFragment.SetPhoneNu 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 } } @@ -464,6 +477,21 @@ class SettingsActivity : BaseActivity(), SetPhoneNumberDialogFragment.SetPhoneNu } } + private fun setupResetPushPreference() { + binding.resetPushNotificationsWrapper.setOnClickListener { + for (user in userManager.users.blockingGet()) { + ClosedInterfaceImpl().unregisterWithServer(binding.root.context, user.username) + } + + Snackbar.make( + binding.root, + resources!!.getString(R.string.prefs_reset_push_done), + Snackbar.LENGTH_LONG + ).show() + true + } + } + private fun setupLicenceSetting() { if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_gpl3_url))) { binding.settingsLicence.setOnClickListener { 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 53de01b275..1bf4e83fbb 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/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 36c164bc63..afe7830677 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -206,7 +206,7 @@ android:textStyle="bold"/> @@ -254,7 +254,7 @@ + android:text="@string/nc_diagnose_push_notifications_available_no" /> @@ -602,14 +603,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 13ed2b442f..037bab4c89 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,9 +191,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 not ignored. This should be changed! Battery optimization is ignored, all fine @@ -810,4 +815,12 @@ How to translate with transifex: Show ban reason Error occurred when unbanning participant Connection lost + Choose a UnifiedPush distributor to use for + notifications + You can use UnifiedPush + To enable push and chat notifications you need to install a + Unified Push distributor.\nFor more information visit https://unifiedpush.org + 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 94778d8176..7fc3530d67 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 void providerInstallerInstallIfNeededAsync() { } @Override - public boolean isGooglePlayServicesAvailable() { + public boolean isPushMessagingServiceAvailable(Context context) { + return false; + } + + @Override + 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 setUpPushTokenRegistration() { + 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 e5e82d0b49..0ff0914807 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -5631,7 +5631,15 @@ - + + + + + + + + +