diff --git a/build.gradle b/build.gradle index d7ba0168dd..4f84103bab 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,8 @@ allprojects { includeGroupByRegex 'com\\.github\\.chrisbanes' // PFLockScreen-Android includeGroupByRegex 'com\\.github\\.vector-im' + // UnifiedPush + includeGroupByRegex 'com\\.github\\.UnifiedPush' // Chat effects includeGroupByRegex 'com\\.github\\.jetradarmobile' diff --git a/newsfragment/3448.feature b/newsfragment/3448.feature new file mode 100644 index 0000000000..3f83f1bef5 --- /dev/null +++ b/newsfragment/3448.feature @@ -0,0 +1 @@ +Use UnifiedPush and allows user to have push without FCM. diff --git a/vector/build.gradle b/vector/build.gradle index 5049aa353a..a044e1815a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -311,6 +311,10 @@ android { buildFeatures { viewBinding true } + + packagingOptions { + exclude 'META-INF/lib_release.kotlin_module' + } } dependencies { @@ -344,6 +348,7 @@ dependencies { implementation 'androidx.multidex:multidex:2.0.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" @@ -360,6 +365,7 @@ dependencies { implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0" implementation "com.squareup.moshi:moshi-adapters:$moshi_version" + implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" @@ -455,8 +461,10 @@ dependencies { implementation "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" - // gplay flavor only - gplayImplementation('com.google.firebase:firebase-messaging:22.0.0') { + // UnifiedPush + implementation 'com.github.UnifiedPush:android-connector:1.2.0' + // UnifiedPush gplay flavor only + gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:1.1.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml deleted file mode 100644 index 3a7c107138..0000000000 --- a/vector/src/fdroid/AndroidManifest.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt index 7603e738d7..45dfa30f4a 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt @@ -17,56 +17,13 @@ package im.vector.app.push.fcm -import android.app.Activity import android.content.Context -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.pushers.PushersManager -import im.vector.app.fdroid.BackgroundSyncStarter -import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver -import im.vector.app.features.settings.VectorPreferences /** * This class has an alter ego in the gplay variant. */ object FcmHelper { - - fun isPushSupported(): Boolean = false - - /** - * Retrieves the FCM registration token. - * - * @return the FCM token or null if not received from FCM - */ - fun getFcmToken(context: Context): String? { - return null - } - - /** - * Store FCM token to the SharedPrefs - * - * @param context android context - * @param token the token to store - */ - fun storeFcmToken(context: Context, token: String?) { - // No op - } - - /** - * onNewToken may not be called on application upgrade, so ensure my shared pref is set - * - * @param activity the first launch Activity - */ - fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { - // No op - } - - fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) { - // try to stop all regardless of background mode - activeSessionHolder.getSafeActiveSession()?.stopAnyBackgroundSync() - AlarmSyncBroadcastReceiver.cancelAlarm(context) - } - - fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { - BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder) + fun isPlayServicesAvailable(context: Context): Boolean { + return false } } diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt index 6236aad65c..56c96f5b2b 100644 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -16,21 +16,28 @@ package im.vector.app.push.fcm import androidx.fragment.app.Fragment -import im.vector.app.fdroid.features.settings.troubleshoot.TestAutoStartBoot -import im.vector.app.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions -import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization +import im.vector.app.core.pushers.UPHelper +import im.vector.app.features.settings.troubleshoot.TestAutoStartBoot +import im.vector.app.features.settings.troubleshoot.TestBackgroundRestrictions +import im.vector.app.features.settings.troubleshoot.TestBatteryOptimization import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestDeviceSettings +import im.vector.app.features.settings.troubleshoot.TestNewEndpoint import im.vector.app.features.settings.troubleshoot.TestNotification +import im.vector.app.features.settings.troubleshoot.TestPushFromPushGateway import im.vector.app.features.settings.troubleshoot.TestPushRulesSettings import im.vector.app.features.settings.troubleshoot.TestSystemSettings +import im.vector.app.features.settings.troubleshoot.TestTokenRegistration import javax.inject.Inject class NotificationTroubleshootTestManagerFactory @Inject constructor( private val testSystemSettings: TestSystemSettings, private val testAccountSettings: TestAccountSettings, private val testDeviceSettings: TestDeviceSettings, + private val testNewEndpoint: TestNewEndpoint, + private val testTokenRegistration: TestTokenRegistration, + private val testPushFromPushGateway: TestPushFromPushGateway, private val testPushRulesSettings: TestPushRulesSettings, private val testAutoStartBoot: TestAutoStartBoot, private val testBackgroundRestrictions: TestBackgroundRestrictions, @@ -44,9 +51,15 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor( mgr.addTest(testAccountSettings) mgr.addTest(testDeviceSettings) mgr.addTest(testPushRulesSettings) - mgr.addTest(testAutoStartBoot) - mgr.addTest(testBackgroundRestrictions) - mgr.addTest(testBatteryOptimization) + if (UPHelper.distributorExists(fragment.requireContext())) { + mgr.addTest(testNewEndpoint) + mgr.addTest(testTokenRegistration) + mgr.addTest(testPushFromPushGateway) + } else { + mgr.addTest(testAutoStartBoot) + mgr.addTest(testBackgroundRestrictions) + mgr.addTest(testBatteryOptimization) + } mgr.addTest(testNotification) return mgr } diff --git a/vector/src/gplay/AndroidManifest.xml b/vector/src/gplay/AndroidManifest.xml index d849d5fb2d..5109ab90da 100755 --- a/vector/src/gplay/AndroidManifest.xml +++ b/vector/src/gplay/AndroidManifest.xml @@ -3,18 +3,12 @@ package="im.vector.app"> - - - - - + - + + - - + - + \ No newline at end of file diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt deleted file mode 100644 index 1107737888..0000000000 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2018 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package im.vector.app.gplay.features.settings.troubleshoot - -import android.content.Intent -import androidx.activity.result.ActivityResultLauncher -import androidx.appcompat.app.AppCompatActivity -import com.google.firebase.messaging.FirebaseMessaging -import im.vector.app.R -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.startAddGoogleAccountIntent -import im.vector.app.features.settings.troubleshoot.TroubleshootTest -import im.vector.app.push.fcm.FcmHelper -import timber.log.Timber -import javax.inject.Inject - -/* -* Test that app can successfully retrieve a token via firebase - */ -class TestFirebaseToken @Inject constructor(private val context: AppCompatActivity, - private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) { - - override fun perform(activityResultLauncher: ActivityResultLauncher) { - status = TestStatus.RUNNING - try { - FirebaseMessaging.getInstance().token - .addOnCompleteListener(context) { task -> - if (!task.isSuccessful) { - // Can't find where this constant is (not documented -or deprecated in docs- and all obfuscated) - description = when (val errorMsg = task.exception?.localizedMessage ?: "Unknown") { - "SERVICE_NOT_AVAILABLE" -> { - stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg) - } - "TOO_MANY_REGISTRATIONS" -> { - stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg) - } - "ACCOUNT_MISSING" -> { - quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) { - override fun doFix() { - startAddGoogleAccountIntent(context, activityResultLauncher) - } - } - stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg) - } - else -> { - stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg) - } - } - status = TestStatus.FAILED - } else { - task.result?.let { token -> - val tok = token.take(8) + "********************" - description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok) - Timber.e("Retrieved FCM token success [$tok].") - // Ensure it is well store in our local storage - FcmHelper.storeFcmToken(context, token) - } - status = TestStatus.SUCCESS - } - } - } catch (e: Throwable) { - description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, e.localizedMessage) - status = TestStatus.FAILED - } - } -} diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/EmbeddedDistrib.kt b/vector/src/gplay/java/im/vector/app/push/fcm/EmbeddedDistrib.kt new file mode 100644 index 0000000000..1ae67c3500 --- /dev/null +++ b/vector/src/gplay/java/im/vector/app/push/fcm/EmbeddedDistrib.kt @@ -0,0 +1,30 @@ +package im.vector.app.push.fcm + +import android.content.Context +import org.unifiedpush.android.embedded_fcm_distributor.GetEndpointHandler +import org.unifiedpush.android.embedded_fcm_distributor.EmbeddedDistributorReceiver + +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +val handlerFCM = object: GetEndpointHandler { + override fun getEndpoint(context: Context?, token: String, instance: String): String { + // Here token is the FCM Token, used by the gateway (sygnal) + return token + } +} + +class EmbeddedDistrib: EmbeddedDistributorReceiver(handlerFCM) diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt index f3bdcafb1c..4146059cdd 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt @@ -15,99 +15,21 @@ */ package im.vector.app.push.fcm -import android.app.Activity import android.content.Context -import android.widget.Toast -import androidx.core.content.edit import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability -import com.google.firebase.messaging.FirebaseMessaging -import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.di.DefaultSharedPreferences -import im.vector.app.core.pushers.PushersManager -import im.vector.app.features.settings.VectorPreferences -import timber.log.Timber /** * This class store the FCM token in SharedPrefs and ensure this token is retrieved. * It has an alter ego in the fdroid variant. */ object FcmHelper { - private val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" - - fun isPushSupported(): Boolean = true - /** - * Retrieves the FCM registration token. - * - * @return the FCM token or null if not received from FCM + * Check the device to make sure it has the Google Play Services APK. */ - fun getFcmToken(context: Context): String? { - return DefaultSharedPreferences.getInstance(context).getString(PREFS_KEY_FCM_TOKEN, null) - } - - /** - * Store FCM token to the SharedPrefs - * TODO Store in realm - * - * @param context android context - * @param token the token to store - */ - fun storeFcmToken(context: Context, - token: String?) { - DefaultSharedPreferences.getInstance(context).edit { - putString(PREFS_KEY_FCM_TOKEN, token) - } - } - - /** - * onNewToken may not be called on application upgrade, so ensure my shared pref is set - * - * @param activity the first launch Activity - */ - fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { - // if (TextUtils.isEmpty(getFcmToken(activity))) { - // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' - if (checkPlayServices(activity)) { - try { - FirebaseMessaging.getInstance().token - .addOnSuccessListener { token -> - storeFcmToken(activity, token) - if (registerPusher) { - pushersManager.registerPusherWithFcmKey(token) - } - } - .addOnFailureListener { e -> - Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") - } - } catch (e: Throwable) { - Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") - } - } else { - Toast.makeText(activity, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show() - Timber.e("No valid Google Play Services found. Cannot use FCM.") - } - } - - /** - * Check the device to make sure it has the Google Play Services APK. If - * it doesn't, display a dialog that allows users to download the APK from - * the Google Play Store or enable it in the device's system settings. - */ - private fun checkPlayServices(activity: Activity): Boolean { + fun isPlayServicesAvailable(context: Context): Boolean { val apiAvailability = GoogleApiAvailability.getInstance() - val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity) + val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) return resultCode == ConnectionResult.SUCCESS } - - @Suppress("UNUSED_PARAMETER") - fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) { - // No op - } - - @Suppress("UNUSED_PARAMETER") - fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { - // TODO FCM fallback - } } diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt index e96c603e60..673733d452 100644 --- a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -16,27 +16,34 @@ package im.vector.app.push.fcm import androidx.fragment.app.Fragment +import im.vector.app.core.pushers.UPHelper import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TestAccountSettings +import im.vector.app.features.settings.troubleshoot.TestAutoStartBoot +import im.vector.app.features.settings.troubleshoot.TestBackgroundRestrictions +import im.vector.app.features.settings.troubleshoot.TestBatteryOptimization import im.vector.app.features.settings.troubleshoot.TestDeviceSettings import im.vector.app.features.settings.troubleshoot.TestNotification import im.vector.app.features.settings.troubleshoot.TestPushRulesSettings import im.vector.app.features.settings.troubleshoot.TestSystemSettings -import im.vector.app.gplay.features.settings.troubleshoot.TestFirebaseToken +import im.vector.app.features.settings.troubleshoot.TestNewEndpoint import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices -import im.vector.app.gplay.features.settings.troubleshoot.TestPushFromPushGateway -import im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration +import im.vector.app.features.settings.troubleshoot.TestPushFromPushGateway +import im.vector.app.features.settings.troubleshoot.TestTokenRegistration import javax.inject.Inject class NotificationTroubleshootTestManagerFactory @Inject constructor( private val testSystemSettings: TestSystemSettings, private val testAccountSettings: TestAccountSettings, private val testDeviceSettings: TestDeviceSettings, - private val testBingRulesSettings: TestPushRulesSettings, private val testPlayServices: TestPlayServices, - private val testFirebaseToken: TestFirebaseToken, + private val testNewEndpoint: TestNewEndpoint, private val testTokenRegistration: TestTokenRegistration, private val testPushFromPushGateway: TestPushFromPushGateway, + private val testPushRulesSettings: TestPushRulesSettings, + private val testAutoStartBoot: TestAutoStartBoot, + private val testBackgroundRestrictions: TestBackgroundRestrictions, + private val testBatteryOptimization: TestBatteryOptimization, private val testNotification: TestNotification ) { @@ -45,11 +52,17 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor( mgr.addTest(testSystemSettings) mgr.addTest(testAccountSettings) mgr.addTest(testDeviceSettings) - mgr.addTest(testBingRulesSettings) - mgr.addTest(testPlayServices) - mgr.addTest(testFirebaseToken) - mgr.addTest(testTokenRegistration) - mgr.addTest(testPushFromPushGateway) + mgr.addTest(testPushRulesSettings) + if (UPHelper.distributorExists(fragment.requireContext())) { + mgr.addTest(testPlayServices) + mgr.addTest(testNewEndpoint) + mgr.addTest(testTokenRegistration) + mgr.addTest(testPushFromPushGateway) + } else { + mgr.addTest(testAutoStartBoot) + mgr.addTest(testBackgroundRestrictions) + mgr.addTest(testBatteryOptimization) + } mgr.addTest(testNotification) return mgr } diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index af8b5a888a..146af1223a 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -39,6 +39,14 @@ android:name="android.permission.WRITE_CALENDAR" tools:node="remove" /> + + + + + @@ -386,6 +394,30 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/sdk_provider_paths" /> + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index be8447d409..d8c480692e 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -59,7 +59,7 @@ import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.version.VersionProvider -import im.vector.app.push.fcm.FcmHelper +import im.vector.app.core.pushers.StateHelper import org.jitsi.meet.sdk.log.JitsiMeetDefaultLogHandler import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.MatrixConfiguration @@ -171,7 +171,7 @@ class VectorApplication : @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { Timber.i("App entered foreground") - FcmHelper.onEnterForeground(appContext, activeSessionHolder) + StateHelper.onEnterForeground(appContext, activeSessionHolder) activeSessionHolder.getSafeActiveSession()?.also { it.stopAnyBackgroundSync() } @@ -181,7 +181,7 @@ class VectorApplication : fun entersBackground() { Timber.i("App entered background") // call persistInfo notificationDrawerManager.persistInfo() - FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder) + StateHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder) } }) ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler) diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index 5896122393..21e43dddef 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -16,6 +16,7 @@ package im.vector.app.core.pushers +import android.content.Context import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.AppNameProvider @@ -33,37 +34,45 @@ class PushersManager @Inject constructor( private val stringProvider: StringProvider, private val appNameProvider: AppNameProvider ) { - suspend fun testPush(pushKey: String) { + suspend fun testPush(context: Context) { val currentSession = activeSessionHolder.getActiveSession() currentSession.testPush( - stringProvider.getString(R.string.pusher_http_url), - stringProvider.getString(R.string.pusher_app_id), - pushKey, + UPHelper.getPushGateway(context)!!, + getPusherAppId(context), + UPHelper.getUpEndpoint(context)!!, TEST_EVENT_ID ) } - fun registerPusherWithFcmKey(pushKey: String): UUID { + fun registerPusher(context: Context, pushKey: String, gateway: String): UUID { val currentSession = activeSessionHolder.getActiveSession() val profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(currentSession.myUserId.hashCode()) return currentSession.addHttpPusher( - pushKey, - stringProvider.getString(R.string.pusher_app_id), + pushKey, // this is the UnifiedPush endpoint + getPusherAppId(context), profileTag, localeProvider.current().language, appNameProvider.getAppName(), currentSession.sessionParams.deviceId ?: "MOBILE", - stringProvider.getString(R.string.pusher_http_url), + gateway, append = false, withEventIdOnly = true ) } - suspend fun unregisterPusher(pushKey: String) { + suspend fun unregisterPusher(context: Context, pushKey: String) { val currentSession = activeSessionHolder.getSafeActiveSession() ?: return - currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id)) + currentSession.removeHttpPusher(pushKey, getPusherAppId(context)) + } + + private fun getPusherAppId(context: Context) : String { + return if (UPHelper.isEmbeddedDistributor(context)) { + stringProvider.getString(R.string.pusher_app_id) + } else { + stringProvider.getString(R.string.up_pusher_app_id) + } } companion object { diff --git a/vector/src/main/java/im/vector/app/core/pushers/StateHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/StateHelper.kt new file mode 100644 index 0000000000..97bbdd279b --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/pushers/StateHelper.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.pushers + +import android.content.Context +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.receiver.AlarmSyncBroadcastReceiver +import im.vector.app.core.receiver.BackgroundSyncStarter +import im.vector.app.features.settings.VectorPreferences + +object StateHelper { + fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) { + // try to stop all regardless of background mode + activeSessionHolder.getSafeActiveSession()?.stopAnyBackgroundSync() + AlarmSyncBroadcastReceiver.cancelAlarm(context) + } + + fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { + BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder) + } +} diff --git a/vector/src/main/java/im/vector/app/core/pushers/UPHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UPHelper.kt new file mode 100644 index 0000000000..5f34ae4f3a --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/pushers/UPHelper.kt @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.core.pushers + +import android.content.Context +import android.content.pm.PackageManager +import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import im.vector.app.R +import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.push.fcm.FcmHelper +import org.unifiedpush.android.connector.Registration +import timber.log.Timber +import java.net.URI + +/** + * This class store the UnifiedPush Endpoint in SharedPrefs and ensure this token is retrieved. + * It has an alter ego in the fdroid variant. + */ +object UPHelper { + private const val PREFS_UP_ENDPOINT = "UP_ENDPOINT" + private const val PREFS_PUSH_GATEWAY = "PUSH_GATEWAY" + + /** + * Retrieves the UnifiedPush Endpoint. + * + * @return the UnifiedPush Endpoint or null if not received + */ + fun getUpEndpoint(context: Context): String? { + return DefaultSharedPreferences.getInstance(context).getString(PREFS_UP_ENDPOINT, null) + } + + /** + * Store UnifiedPush Endpoint to the SharedPrefs + * TODO Store in realm + * + * @param context android context + * @param endpoint the endpoint to store + */ + fun storeUpEndpoint(context: Context, + endpoint: String?) { + DefaultSharedPreferences.getInstance(context).edit { + putString(PREFS_UP_ENDPOINT, endpoint) + } + } + + /** + * Retrieves the Push Gateway. + * + * @return the Push Gateway or null if not defined + */ + fun getPushGateway(context: Context): String? { + return DefaultSharedPreferences.getInstance(context).getString(PREFS_PUSH_GATEWAY, null) + } + + /** + * Store Push Gateway to the SharedPrefs + * TODO Store in realm + * + * @param context android context + * @param gateway the push gateway to store + */ + fun storePushGateway(context: Context, + gateway: String?) { + DefaultSharedPreferences.getInstance(context).edit { + putString(PREFS_PUSH_GATEWAY, gateway) + } + } + + fun registerUnifiedPush(context: Context) { + val up = Registration() + if (up.getDistributor(context).isNotEmpty()) { + up.registerApp(context) + return + } + val distributors = up.getDistributors(context).toMutableList() + /** + * Check if it is the gplay flavour AND GServices are not available + */ + if (!FcmHelper.isPlayServicesAvailable(context)) { + distributors.remove(context.packageName) + } + when (distributors.size) { + 0 -> { + /** + * Fallback with sync service + */ + } + 1 -> { + up.saveDistributor(context, distributors.first()) + up.registerApp(context) + } + else -> { + val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(context) + builder.setTitle(context.getString(R.string.unifiedpush_getdistributors_dialog_title)) + + val distributorsArray = distributors.toTypedArray() + val distributorsNameArray = distributorsArray.map { + if (it == context.packageName) { + context.getString(R.string.unifiedpush_getdistributors_dialog_fcm_fallback) + } else { + try { + val ai = context.packageManager.getApplicationInfo(it, 0) + context.packageManager.getApplicationLabel(ai) + } catch (e: PackageManager.NameNotFoundException) { + it + } as String + } + }.toTypedArray() + builder.setItems(distributorsNameArray) { _, which -> + val distributor = distributorsArray[which] + up.saveDistributor(context, distributor) + Timber.i("Saving distributor: $distributor") + up.registerApp(context) + } + val dialog: AlertDialog = builder.create() + dialog.show() + } + } + } + + fun unregister(context: Context) { + val up = Registration() + up.unregisterApp(context) + } + + fun customOrDefaultGateway(context: Context, endpoint: String?): String { + // if we use the embedded distributor, + // register app_id type upfcm on sygnal + // the pushkey if FCM key + val up = Registration() + if (up.getDistributor(context) == context.packageName) { + return context.getString(R.string.pusher_http_url) + } + // else, unifiedpush, and pushkey is an endpoint + val default = context.getString(R.string.default_push_gateway_http_url) + endpoint?.let { + val uri = URI(it) + val custom = "${it.split(uri.rawPath)[0]}/_matrix/push/v1/notify" + Timber.i("Testing $custom") + /** + * TODO: + * if GET custom returns """{"unifiedpush":{"gateway":"matrix"}}""" + * return custom + */ + } + return default + } + + fun hasEndpoint(context: Context): Boolean { + getUpEndpoint(context)?.let { + return true + } + return false + } + + fun distributorExists(context: Context): Boolean { + val up = Registration() + return up.getDistributor(context).isNotEmpty() + } + + fun isEmbeddedDistributor(context: Context) : Boolean { + val up = Registration() + return up.getDistributor(context) == context.packageName + } +} diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt similarity index 50% rename from vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt rename to vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt index 4cefeadb62..04d4cfeb0e 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt @@ -17,43 +17,61 @@ * limitations under the License. */ -package im.vector.app.gplay.push.fcm +package im.vector.app.core.pushers +import android.content.Context import android.content.Intent import android.os.Handler import android.os.Looper +import android.widget.Toast import androidx.lifecycle.Lifecycle import androidx.lifecycle.ProcessLifecycleOwner import androidx.localbroadcastmanager.content.LocalBroadcastManager -import com.google.firebase.messaging.FirebaseMessagingService -import com.google.firebase.messaging.RemoteMessage +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import im.vector.app.BuildConfig -import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.network.WifiDetector -import im.vector.app.core.pushers.PushersManager import im.vector.app.features.badge.BadgeProxy import im.vector.app.features.notifications.NotifiableEventResolver -import im.vector.app.features.notifications.NotifiableMessageEvent import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationUtils -import im.vector.app.features.notifications.SimpleNotifiableEvent +import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences -import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event +import org.unifiedpush.android.connector.MessagingReceiver +import org.unifiedpush.android.connector.MessagingReceiverHandler import timber.log.Timber +@JsonClass(generateAdapter = true) +data class UnifiedPushMessage( + val notification: Notification + ) + +@JsonClass(generateAdapter = true) +data class Notification( + @Json(name = "event_id") val eventId: String = "", + @Json(name = "room_id") val roomId: String = "", + var unread: Int = 0, + val counts: Counts = Counts() + ) + +data class Counts( + val unread: Int = 0 + ) + /** - * Class extending FirebaseMessagingService. + * UnifiedPush handler. */ -class VectorFirebaseMessagingService : FirebaseMessagingService() { +val upHandler = object: MessagingReceiverHandler { private lateinit var notificationDrawerManager: NotificationDrawerManager private lateinit var notifiableEventResolver: NotifiableEventResolver @@ -69,9 +87,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { Handler(Looper.getMainLooper()) } - override fun onCreate() { - super.onCreate() - with(vectorComponent()) { + fun initVar(context: Context) { + with(context.vectorComponent()) { notificationDrawerManager = notificationDrawerManager() notifiableEventResolver = notifiableEventResolver() pusherManager = pusherManager() @@ -85,17 +102,34 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * Called when message is received. * * @param message the message + * @param instance connection, for multi-account */ - override fun onMessageReceived(message: RemoteMessage) { + override fun onMessage(context: Context?, message: String, instance: String) { + initVar(context!!) if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.d("## onMessageReceived() %s", message.data.toString()) + Timber.d("## onMessageReceived() %s", message) + } + Timber.d("## onMessage() received") + + val moshi: Moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + lateinit var notification: Notification + + if (UPHelper.isEmbeddedDistributor(context)) { + notification = moshi.adapter(Notification::class.java) + .fromJson(message) ?: return + } else { + val data = moshi.adapter(UnifiedPushMessage::class.java) + .fromJson(message) ?: return + notification = data.notification + notification.unread = notification.counts.unread } - Timber.d("## onMessageReceived() from FCM with priority %s", message.priority) // Diagnostic Push - if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) { + if (notification.eventId == PushersManager.TEST_EVENT_ID) { val intent = Intent(NotificationUtils.PUSH_ACTION) - LocalBroadcastManager.getInstance(this).sendBroadcast(intent) + LocalBroadcastManager.getInstance(context).sendBroadcast(intent) return } @@ -109,7 +143,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { // we are in foreground, let the sync do the things? Timber.d("PUSH received in a foreground state, ignore") } else { - onMessageReceivedInternal(message.data) + onMessageReceivedInternal(context, notification) } } } @@ -120,59 +154,71 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * when the InstanceID token is initially generated, so this is where * you retrieve the token. */ - override fun onNewToken(refreshedToken: String) { - Timber.i("onNewToken: FCM Token has been updated") - FcmHelper.storeFcmToken(this, refreshedToken) + override fun onNewEndpoint(context: Context?, endpoint: String, instance: String) { + initVar(context!!) + Timber.i("onNewEndpoint: adding $endpoint") if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { - pusherManager.registerPusherWithFcmKey(refreshedToken) + val gateway = UPHelper.customOrDefaultGateway(context, endpoint) + if (UPHelper.getUpEndpoint(context) != endpoint + || UPHelper.getPushGateway(context) != gateway) { + UPHelper.storePushGateway(context, gateway) + UPHelper.storeUpEndpoint(context, endpoint) + pusherManager.registerPusher(context, endpoint, gateway) + } else { + Timber.i("onNewEndpoint: skipped") + } } + val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED + vectorPreferences.setFdroidSyncBackgroundMode(mode) } - /** - * Called when the FCM server deletes pending messages. This may be due to: - * - Too many messages stored on the FCM server. - * This can occur when an app's servers send a bunch of non-collapsible messages to FCM servers while the device is offline. - * - The device hasn't connected in a long time and the app server has recently (within the last 4 weeks) - * sent a message to the app on that device. - * - * It is recommended that the app do a full sync with the app server after receiving this call. - */ - override fun onDeletedMessages() { - Timber.v("## onDeletedMessages()") + override fun onRegistrationFailed(context: Context?, instance: String) { + Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show() + } + + override fun onRegistrationRefused(context: Context?, instance: String) { + Toast.makeText(context, "Push service registration refused by server", Toast.LENGTH_LONG).show() + } + + override fun onUnregistered(context: Context?, instance: String) { + Timber.d("Unifiedpush: Unregistered") + initVar(context!!) + val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY + vectorPreferences.setFdroidSyncBackgroundMode(mode) + runBlocking { + try { + pusherManager.unregisterPusher(context, UPHelper.getUpEndpoint(context)!!) + } catch (e: Exception) { + Timber.d("Probably unregistering a non existant pusher") + } + } } /** * Internal receive method * - * @param data Data map containing message data as key/value pairs. - * For Set of keys use data.keySet(). + * @param notification Notification containing message data. */ - private fun onMessageReceivedInternal(data: Map) { + private fun onMessageReceivedInternal(context: Context, notification: Notification) { try { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.d("## onMessageReceivedInternal() : $data") - } else { - Timber.d("## onMessageReceivedInternal() : $data") + Timber.d("## onMessageReceivedInternal() : $notification") } // update the badge counter - val unreadCount = data["unread"]?.let { Integer.parseInt(it) } ?: 0 - BadgeProxy.updateBadgeCount(applicationContext, unreadCount) + BadgeProxy.updateBadgeCount(context, notification.unread) val session = activeSessionHolder.getSafeActiveSession() if (session == null) { Timber.w("## Can't sync from push, no current session") } else { - val eventId = data["event_id"] - val roomId = data["room_id"] - - if (isEventAlreadyKnown(eventId, roomId)) { + if (isEventAlreadyKnown(notification.eventId, notification.roomId)) { Timber.d("Ignoring push, event already known") } else { // Try to get the Event content faster Timber.d("Requesting event in fast lane") - getEventFastLane(session, roomId, eventId) + getEventFastLane(session, notification.roomId, notification.eventId) Timber.d("Requesting background sync") session.requireBackgroundSync() @@ -227,87 +273,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { } return false } - - private fun handleNotificationWithoutSyncingMode(data: Map, session: Session?) { - if (session == null) { - Timber.e("## handleNotificationWithoutSyncingMode cannot find session") - return - } - - // The Matrix event ID of the event being notified about. - // This is required if the notification is about a particular Matrix event. - // It may be omitted for notifications that only contain updated badge counts. - // This ID can and should be used to detect duplicate notification requests. - val eventId = data["event_id"] ?: return // Just ignore - - val eventType = data["type"] - if (eventType == null) { - // Just add a generic unknown event - val simpleNotifiableEvent = SimpleNotifiableEvent( - session.myUserId, - eventId, - null, - true, // It's an issue in this case, all event will bing even if expected to be silent. - title = getString(R.string.notification_unknown_new_event), - description = "", - type = null, - timestamp = System.currentTimeMillis(), - soundName = Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT, - isPushGatewayEvent = true - ) - notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent) - notificationDrawerManager.refreshNotificationDrawer() - } else { - val event = parseEvent(data) ?: return - - val notifiableEvent = notifiableEventResolver.resolveEvent(event, session) - - if (notifiableEvent == null) { - Timber.e("Unsupported notifiable event $eventId") - if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.e("--> $event") - } - } else { - if (notifiableEvent is NotifiableMessageEvent) { - if (notifiableEvent.senderName.isNullOrEmpty()) { - notifiableEvent.senderName = data["sender_display_name"] ?: data["sender"] ?: "" - } - if (notifiableEvent.roomName.isNullOrEmpty()) { - notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: "" - } - } - - notifiableEvent.isPushGatewayEvent = true - notifiableEvent.matrixID = session.myUserId - notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) - notificationDrawerManager.refreshNotificationDrawer() - } - } - } - - private fun findRoomNameBestEffort(data: Map, session: Session?): String? { - var roomName: String? = data["room_name"] - val roomId = data["room_id"] - if (null == roomName && null != roomId) { - // Try to get the room name from our store - roomName = session?.getRoom(roomId)?.roomSummary()?.displayName - } - return roomName - } - - /** - * Try to create an event from the FCM data - * - * @param data the FCM data - * @return the event or null if required data are missing - */ - private fun parseEvent(data: Map?): Event? { - return Event( - eventId = data?.get("event_id") ?: return null, - senderId = data["sender"], - roomId = data["room_id"] ?: return null, - type = data["type"] ?: return null, - originServerTs = System.currentTimeMillis() - ) - } } + +class VectorMessagingReceiver : MessagingReceiver(upHandler) diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/core/receiver/AlarmSyncBroadcastReceiver.kt similarity index 99% rename from vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt rename to vector/src/main/java/im/vector/app/core/receiver/AlarmSyncBroadcastReceiver.kt index b94e99208b..37a10cccda 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/receiver/AlarmSyncBroadcastReceiver.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.fdroid.receiver +package im.vector.app.core.receiver import android.app.AlarmManager import android.app.PendingIntent diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt b/vector/src/main/java/im/vector/app/core/receiver/BackgroundSyncStarter.kt similarity index 96% rename from vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt rename to vector/src/main/java/im/vector/app/core/receiver/BackgroundSyncStarter.kt index 7221e2b065..b3789ef6df 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt +++ b/vector/src/main/java/im/vector/app/core/receiver/BackgroundSyncStarter.kt @@ -14,11 +14,10 @@ * limitations under the License. */ -package im.vector.app.fdroid +package im.vector.app.core.receiver import android.content.Context import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences import timber.log.Timber diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt b/vector/src/main/java/im/vector/app/core/receiver/OnApplicationUpgradeOrRebootReceiver.kt similarity index 94% rename from vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt rename to vector/src/main/java/im/vector/app/core/receiver/OnApplicationUpgradeOrRebootReceiver.kt index 797b5734a2..b1989eaab7 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/receiver/OnApplicationUpgradeOrRebootReceiver.kt @@ -15,14 +15,13 @@ * limitations under the License. */ -package im.vector.app.fdroid.receiver +package im.vector.app.core.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import im.vector.app.core.di.HasVectorInjector import im.vector.app.core.extensions.vectorComponent -import im.vector.app.fdroid.BackgroundSyncStarter import timber.log.Timber class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index 73a6c07d6a..767ef59aa6 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -29,8 +29,8 @@ import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.lookup.CallUserMapper import im.vector.app.features.call.utils.EglUtils import im.vector.app.features.call.vectorCallService +import im.vector.app.core.pushers.UPHelper import im.vector.app.features.session.coroutineScope -import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher import org.matrix.android.sdk.api.extensions.orFalse @@ -262,7 +262,7 @@ class WebRtcCallManager @Inject constructor( audioManager.setMode(CallAudioManager.Mode.DEFAULT) // did we start background sync? so we should stop it if (isInBackground) { - if (FcmHelper.isPushSupported()) { + if (UPHelper.hasEndpoint(context)) { currentSession?.stopAnyBackgroundSync() } else { // for fdroid we should not stop, it should continue syncing @@ -367,7 +367,7 @@ class WebRtcCallManager @Inject constructor( // and thus won't be able to received events. For example if the call is // accepted on an other session this device will continue ringing if (isInBackground) { - if (FcmHelper.isPushSupported()) { + if (UPHelper.hasEndpoint(context)) { // only for push version as fdroid version is already doing it? currentSession?.startAutomaticBackgroundSync(30, 0) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 251ef48b4f..ce0cdff64a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -68,7 +68,7 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState -import im.vector.app.push.fcm.FcmHelper +import im.vector.app.core.pushers.UPHelper import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService @@ -165,7 +165,7 @@ class HomeActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice()) + UPHelper.registerUnifiedPush(this) sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java) views.drawerLayout.addDrawerListener(drawerListener) if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index ce8fe2379a..5d5b264517 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -21,6 +21,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.os.Build +import android.text.TextUtils import android.view.View import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity @@ -29,6 +30,7 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.getAllChildFragments import im.vector.app.core.extensions.toOnOff +import im.vector.app.core.pushers.UPHelper import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.devtools.GossipingEventsSerializer @@ -55,6 +57,7 @@ import java.io.File import java.io.IOException import java.io.OutputStreamWriter import java.net.HttpURLConnection +import java.net.URL import java.util.Locale import java.util.zip.GZIPOutputStream import javax.inject.Inject @@ -276,6 +279,13 @@ class BugReporter @Inject constructor( .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) .addFormDataPart("server_version", serverVersion) + // UnifiedPush + // Only include the UP endpoint base url to exclude private user tokens in the path or parameters + builder.addFormDataPart("unifiedpush_endpoint", strippedUrl(UPHelper.getUpEndpoint(context)).toString()) + .addFormDataPart("unifiedpush_gateway", UPHelper.getPushGateway(context)) + .addFormDataPart("unifiedpush_distributor_exists", UPHelper.distributorExists(context).toString()) + .addFormDataPart("unifiedpush_is_embedded_distributor", UPHelper.isEmbeddedDistributor(context).toString()) + val buildNumber = context.getString(R.string.build_number) if (buildNumber.isNotEmpty() && buildNumber != "0") { builder.addFormDataPart("build_number", buildNumber) @@ -454,6 +464,16 @@ class BugReporter @Inject constructor( } } + fun strippedUrl(url: String?): String? { + if (TextUtils.isEmpty(url)) return null + return try { + val parsed = URL(url) + "${parsed.protocol}://${parsed.host}" + } catch (e: Exception) { + "Malformed url" + } + } + /** * Send a bug report either with email or with Vector. */ diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 40f575c853..ac829be371 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -37,16 +37,17 @@ import im.vector.app.core.pushers.PushersManager import im.vector.app.core.utils.isIgnoringBatteryOptimizations import im.vector.app.core.utils.requestDisablingBatteryOptimization import im.vector.app.features.notifications.NotificationUtils +import im.vector.app.core.pushers.UPHelper import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.BackgroundSyncModeChooserDialog import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsBaseFragment import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener -import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.pushrules.RuleIds import org.matrix.android.sdk.api.pushrules.RuleKind +import timber.log.Timber import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml @@ -150,7 +151,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } findPreference(VectorPreferences.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY)?.let { - it.isVisible = !FcmHelper.isPushSupported() + it.isVisible = !UPHelper.hasEndpoint(requireContext()) } findPreference(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let { @@ -254,7 +255,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( private fun refreshPref() { // This pref may have change from troubleshoot pref fragment - if (!FcmHelper.isPushSupported()) { + if (!UPHelper.hasEndpoint(requireContext())) { findPreference(VectorPreferences.SETTINGS_START_ON_BOOT_PREFERENCE_KEY) ?.isChecked = vectorPreferences.autoStartOnBoot() } @@ -294,13 +295,22 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( private fun updateEnabledForDevice(preference: Preference?) { val switchPref = preference as SwitchPreference if (switchPref.isChecked) { - FcmHelper.getFcmToken(requireContext())?.let { - pushManager.registerPusherWithFcmKey(it) - } + UPHelper.registerUnifiedPush(requireContext()) } else { - FcmHelper.getFcmToken(requireContext())?.let { + UPHelper.getUpEndpoint(requireContext())?.let { lifecycleScope.launch { - runCatching { pushManager.unregisterPusher(it) } + runCatching { + try { + pushManager.unregisterPusher(requireContext(), it) + } catch (e: Exception) { + Timber.d("Probably unregistering a non existant pusher") + } + try { + UPHelper.unregister(requireContext()) + } catch (e: Exception) { + Timber.d("Probably unregistering to a non-saved distributor") + } + } .fold( { session.refreshPushers() }, { diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAutoStartBoot.kt similarity index 93% rename from vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt rename to vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAutoStartBoot.kt index e46a07f712..f5516874e4 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAutoStartBoot.kt @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.app.fdroid.features.settings.troubleshoot +package im.vector.app.features.settings.troubleshoot import android.content.Intent import androidx.activity.result.ActivityResultLauncher import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.settings.VectorPreferences -import im.vector.app.features.settings.troubleshoot.TroubleshootTest import javax.inject.Inject /** diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestBackgroundRestrictions.kt similarity index 95% rename from vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt rename to vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestBackgroundRestrictions.kt index abdd696724..62f617dfce 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestBackgroundRestrictions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 New Vector Ltd + * Copyright (c) 2021 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.app.fdroid.features.settings.troubleshoot +package im.vector.app.features.settings.troubleshoot import android.content.Intent import android.net.ConnectivityManager @@ -23,7 +23,6 @@ import androidx.core.content.getSystemService import androidx.core.net.ConnectivityManagerCompat import im.vector.app.R import im.vector.app.core.resources.StringProvider -import im.vector.app.features.settings.troubleshoot.TroubleshootTest import javax.inject.Inject class TestBackgroundRestrictions @Inject constructor(private val context: AppCompatActivity, diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestBatteryOptimization.kt similarity index 93% rename from vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt rename to vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestBatteryOptimization.kt index b1eeae6681..5f5eb42e94 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestBatteryOptimization.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.app.fdroid.features.settings.troubleshoot +package im.vector.app.features.settings.troubleshoot import android.content.Intent import androidx.activity.result.ActivityResultLauncher @@ -22,7 +22,6 @@ import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.isIgnoringBatteryOptimizations import im.vector.app.core.utils.requestDisablingBatteryOptimization -import im.vector.app.features.settings.troubleshoot.TroubleshootTest import javax.inject.Inject class TestBatteryOptimization @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNewEndpoint.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNewEndpoint.kt new file mode 100644 index 0000000000..76e1b7c662 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNewEndpoint.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.features.settings.troubleshoot + +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher +import androidx.appcompat.app.AppCompatActivity +import im.vector.app.R +import im.vector.app.core.pushers.UPHelper +import im.vector.app.core.resources.StringProvider +import javax.inject.Inject + +/* +* Test that app can successfully retrieve a new endpoint + */ +class TestNewEndpoint @Inject constructor(private val context: AppCompatActivity, + private val stringProvider: StringProvider + ) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_title) { + + override fun perform(activityResultLauncher: ActivityResultLauncher) { + status = TestStatus.RUNNING + + val endpoint = UPHelper.getUpEndpoint(context) + + if (UPHelper.isEmbeddedDistributor(context)) { + if (!endpoint.isNullOrEmpty()) { + status = TestStatus.SUCCESS + description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, endpoint) + } else { + status = TestStatus.FAILED + description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed) + } + } else { + if (!endpoint.isNullOrEmpty()) { + status = TestStatus.SUCCESS + description = stringProvider.getString(R.string.settings_troubleshoot_test_endpoint_success, endpoint) + } else { + status = TestStatus.FAILED + description = stringProvider.getString(R.string.settings_troubleshoot_test_endpoint_failed) + } + } + } +} diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushFromPushGateway.kt similarity index 91% rename from vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt rename to vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushFromPushGateway.kt index d429b293b2..2b17694839 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushFromPushGateway.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright (c) 2021 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.app.gplay.features.settings.troubleshoot +package im.vector.app.features.settings.troubleshoot import android.content.Intent import androidx.activity.result.ActivityResultLauncher @@ -24,8 +24,7 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.pushers.PushersManager import im.vector.app.core.resources.StringProvider import im.vector.app.features.session.coroutineScope -import im.vector.app.features.settings.troubleshoot.TroubleshootTest -import im.vector.app.push.fcm.FcmHelper +import im.vector.app.core.pushers.UPHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -48,12 +47,12 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat override fun perform(activityResultLauncher: ActivityResultLauncher) { pushReceived = false - val fcmToken = FcmHelper.getFcmToken(context) ?: run { + UPHelper.getUpEndpoint(context) ?: run { status = TestStatus.FAILED return } action = activeSessionHolder.getActiveSession().coroutineScope.launch { - val result = runCatching { pushersManager.testPush(fcmToken) } + val result = runCatching { pushersManager.testPush(context) } withContext(Dispatchers.Main) { status = result diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestTokenRegistration.kt similarity index 82% rename from vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt rename to vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestTokenRegistration.kt index f400c17d46..eb92ed00af 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestTokenRegistration.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.app.gplay.features.settings.troubleshoot +package im.vector.app.features.settings.troubleshoot import android.content.Intent import androidx.activity.result.ActivityResultLauncher @@ -26,8 +26,7 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.PushersManager import im.vector.app.core.resources.StringProvider -import im.vector.app.features.settings.troubleshoot.TroubleshootTest -import im.vector.app.push.fcm.FcmHelper +import im.vector.app.core.pushers.UPHelper import javax.inject.Inject /** @@ -37,11 +36,15 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc private val stringProvider: StringProvider, private val pushersManager: PushersManager, private val activeSessionHolder: ActiveSessionHolder) - : TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) { + : TroubleshootTest(R.string.settings_troubleshoot_test_pusher_registration_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { // Check if we have a registered pusher for this token - val fcmToken = FcmHelper.getFcmToken(context) ?: run { + val pushToken = UPHelper.getUpEndpoint(context) ?: run { + status = TestStatus.FAILED + return + } + val pushGateway = UPHelper.getPushGateway(context) ?: run { status = TestStatus.FAILED return } @@ -50,14 +53,14 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc return } val pushers = session.getPushers().filter { - it.pushKey == fcmToken && it.state == PusherState.REGISTERED + it.pushKey == pushToken && it.state == PusherState.REGISTERED } if (pushers.isEmpty()) { - description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_failed, + description = stringProvider.getString(R.string.settings_troubleshoot_test_pusher_registration_failed, stringProvider.getString(R.string.sas_error_unknown)) quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) { override fun doFix() { - val workId = pushersManager.registerPusherWithFcmKey(fcmToken) + val workId = pushersManager.registerPusher(context, pushToken, pushGateway) WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> if (workInfo != null) { if (workInfo.state == WorkInfo.State.SUCCEEDED) { @@ -72,7 +75,7 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc status = TestStatus.FAILED } else { - description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_success) + description = stringProvider.getString(R.string.settings_troubleshoot_test_pusher_registration_success) status = TestStatus.SUCCESS } } diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml index ef0374edc2..559ac64fd3 100755 --- a/vector/src/main/res/values/config.xml +++ b/vector/src/main/res/values/config.xml @@ -14,7 +14,11 @@ --> + https://matrix.org/_matrix/push/v1/notify + + + https://up.schildi.chat/_matrix/push/v1/notify im.vector.app.android diff --git a/vector/src/main/res/values/config_sc.xml b/vector/src/main/res/values/config_sc.xml new file mode 100755 index 0000000000..89af6b984f --- /dev/null +++ b/vector/src/main/res/values/config_sc.xml @@ -0,0 +1,6 @@ + + + + de.spiritcroc.riotx.up + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 78c3e99aad..60b6ce51b9 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1131,6 +1131,16 @@ ${app_name} uses Google Play Services to deliver push messages but it doesn’t seem to be configured correctly:\n%1$s Fix Play Services + + Endpoint/FCM + Endpoint successfully retrieved:\n%1$s + Failed to retrieved Endpoint. + + + Choose a distributor + FCM Fallback + + Firebase Token FCM token successfully retrieved:\n%1$s Failed to retrieved FCM token:\n%1$s @@ -1142,6 +1152,12 @@ [%1$s]\nThis error is out of control of ${app_name}. There is no Google account on the phone. Please open the account manager and add a Google account. Add Account + + Pusher Registration + Pusher successfully registered to HomeServer. + Failed to register pusher to HomeServer:\n%1$s + + Token Registration FCM token successfully registered to homeserver. Failed to register FCM token to homeserver:\n%1$s