From 875663c6060e9a3a7ac926e920c06f87e14109e1 Mon Sep 17 00:00:00 2001 From: gavine99 Date: Sun, 8 Sep 2024 09:32:04 +1000 Subject: [PATCH] changes to enable unified push notifications in generic build. Signed-off-by: gavine99 --- README.md | 6 +- app/build.gradle | 6 +- app/src/generic/AndroidManifest.xml | 21 +++ .../java/com/nextcloud/talk/UnifiedPush.kt | 143 ++++++++++++++++++ .../talk/utils/ClosedInterfaceImpl.java | 28 ---- .../talk/utils/ClosedInterfaceImpl.kt | 39 +++++ .../firebase/NCFirebaseMessagingService.kt | 4 + .../talk/utils/ClosedInterfaceImpl.kt | 19 ++- .../account/AccountVerificationActivity.kt | 8 +- .../nextcloud/talk/activities/MainActivity.kt | 24 ++- .../ConversationsListActivity.kt | 6 +- .../talk/diagnose/DiagnoseActivity.kt | 95 ++++++------ .../diagnose/DiagnoseContentComposable.kt | 6 +- .../talk/interfaces/ClosedInterface.kt | 8 +- .../nextcloud/talk/jobs/NotificationWorker.kt | 39 ++++- .../talk/jobs/PushRegistrationWorker.java | 5 +- .../talk/settings/SettingsActivity.kt | 34 ++++- .../nextcloud/talk/utils/bundle/BundleKeys.kt | 1 + .../talk/utils/power/PowerManagerUtils.kt | 8 + app/src/main/res/layout/activity_settings.xml | 37 ++++- app/src/main/res/values/strings.xml | 15 +- .../talk/utils/ClosedInterfaceImpl.java | 19 ++- gradle/verification-metadata.xml | 61 +++++++- 23 files changed, 518 insertions(+), 114 deletions(-) create mode 100644 app/src/generic/AndroidManifest.xml create mode 100644 app/src/generic/java/com/nextcloud/talk/UnifiedPush.kt delete mode 100644 app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java create mode 100644 app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt 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 521f5eac9..98755521f 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 f9536241f..3e48e29b7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3,6 +3,10 @@ true true + + + + @@ -12,6 +16,9 @@ + + + @@ -320,6 +327,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -13901,7 +13960,7 @@ - +