mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Merge pull request #6228 from vector-im/feature/bma/unifiedPush2
UnifiedPush
This commit is contained in:
commit
16ca26569f
51 changed files with 1451 additions and 190 deletions
|
@ -40,6 +40,7 @@
|
||||||
<w>sygnal</w>
|
<w>sygnal</w>
|
||||||
<w>threepid</w>
|
<w>threepid</w>
|
||||||
<w>uisi</w>
|
<w>uisi</w>
|
||||||
|
<w>unifiedpush</w>
|
||||||
<w>unpublish</w>
|
<w>unpublish</w>
|
||||||
<w>unwedging</w>
|
<w>unwedging</w>
|
||||||
<w>vctr</w>
|
<w>vctr</w>
|
||||||
|
|
1
changelog.d/3448.feature
Normal file
1
changelog.d/3448.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Use UnifiedPush and allows user to have push without FCM.
|
|
@ -89,6 +89,7 @@ ext.libs = [
|
||||||
],
|
],
|
||||||
squareup : [
|
squareup : [
|
||||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||||
|
'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi",
|
||||||
'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi",
|
'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi",
|
||||||
'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit",
|
'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit",
|
||||||
'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit"
|
'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit"
|
||||||
|
|
|
@ -9,6 +9,7 @@ ext.groups = [
|
||||||
'com.github.jetradarmobile',
|
'com.github.jetradarmobile',
|
||||||
'com.github.MatrixFrog',
|
'com.github.MatrixFrog',
|
||||||
'com.github.tapadoo',
|
'com.github.tapadoo',
|
||||||
|
'com.github.UnifiedPush',
|
||||||
'com.github.vector-im',
|
'com.github.vector-im',
|
||||||
'com.github.yalantis',
|
'com.github.yalantis',
|
||||||
'com.github.Zhuinden',
|
'com.github.Zhuinden',
|
||||||
|
|
58
docs/unifiedpush.md
Normal file
58
docs/unifiedpush.md
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# UnifiedPush
|
||||||
|
|
||||||
|
<!--- TOC -->
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Configuration in Element-Android and their forks](#configuration-in-element-android-and-their-forks)
|
||||||
|
* [Enabling and disabling the feature](#enabling-and-disabling-the-feature)
|
||||||
|
* [Override the configuration at runtime](#override-the-configuration-at-runtime)
|
||||||
|
* [Enabling the feature](#enabling-the-feature)
|
||||||
|
* [Disabling the feature](#disabling-the-feature)
|
||||||
|
* [Useful links](#useful-links)
|
||||||
|
|
||||||
|
<!--- END -->
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The recently started UnifiedPush project is an Android protocol and library for apps to be able to receive distributor-agnostic push notifications.
|
||||||
|
|
||||||
|
The *F-Droid* and *Gplay* flavors of Element Android support UnifiedPush, so the user can use any distributor installed on their devices. This would make it possible to have push notifications without depending on Google services or libraries. Currently, the main distributors are [ntfy](https://ntfy.sh) which does not require any setup (like manual registration) to use the public server and [NextPush](https://github.com/UP-NextPush/android), available as a nextcloud application.
|
||||||
|
|
||||||
|
The *Gplay* variant uses a UnifiedPush library which basically embed a FCM distributor built into the application (so a user doesn't need to do anything other than install the app to get FCM notifications). This variant uses Google Services to receive notifications if the user has not installed any distributor.
|
||||||
|
|
||||||
|
The *F-Droid* variant does not use this library to avoid any proprietary blob. It will use a polling service if the user has not installed any distributor.
|
||||||
|
|
||||||
|
In all cases, if there are other distributors available, the user will have to opt-in to one of them in the preferences.
|
||||||
|
|
||||||
|
## Configuration in Element-Android and their forks
|
||||||
|
|
||||||
|
### Enabling and disabling the feature
|
||||||
|
|
||||||
|
Allowing the user to use an alternative distributor can be changed in [Config](../vector-config/src/main/java/im/vector/app/config/Config.kt). The flag is named `ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS`. Default value is `true`.
|
||||||
|
|
||||||
|
#### Override the configuration at runtime
|
||||||
|
|
||||||
|
On debug version, it is possible to override this configuration at runtime, using the `Feature` screen. The Feature is named `Allow external UnifiedPush distributors`.
|
||||||
|
|
||||||
|
#### Enabling the feature
|
||||||
|
|
||||||
|
This is the default behavior of Element Android.
|
||||||
|
|
||||||
|
If `ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS` is set to true, it allows any available external UnifiedPush distributor to be chosen by the user.
|
||||||
|
- For Gplay variant it means that FCM will be used by default, but user can choose another UnifiedPush distributor;
|
||||||
|
- For F-Droid variant, it means that background polling will be used by default, but user can choose another UnifiedPush distributor.
|
||||||
|
- On the UI, the setting to choose an alternative distributor will be visible to the user, and some tests in the notification troubleshoot screen will shown.
|
||||||
|
- For F-Droid, if the user has chosen a distributor, the settings to configure the background polling will be hidden.
|
||||||
|
|
||||||
|
#### Disabling the feature
|
||||||
|
|
||||||
|
If `ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS` is set to false, it prevents the usage of external UnifiedPush distributors.
|
||||||
|
- For Gplay variant it means that only FCM will be used;
|
||||||
|
- For F-Droid variant, it means that only background polling will be used.
|
||||||
|
- On the UI, the setting to choose an alternative distributor will be hidden to the user, and some tests in the notification troubleshoot screen will be hidden.
|
||||||
|
|
||||||
|
### Useful links
|
||||||
|
|
||||||
|
- UnifiedPush official website: [https://unifiedpush.org/](https://unifiedpush.org/)
|
||||||
|
- List of available distributors can be retrieved here: [https://unifiedpush.org/users/distributors/](https://unifiedpush.org/users/distributors/)
|
||||||
|
- UnifiedPush project discussion can occurs here: [#unifiedpush:matrix.org](https://matrix.to/#/#unifiedpush:matrix.org)
|
|
@ -1,5 +1,6 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
|
id 'kotlin-android'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -13,4 +14,8 @@ android {
|
||||||
sourceCompatibility versions.sourceCompat
|
sourceCompatibility versions.sourceCompat
|
||||||
targetCompatibility versions.targetCompat
|
targetCompatibility versions.targetCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
39
vector-config/src/main/java/im/vector/app/config/Config.kt
Normal file
39
vector-config/src/main/java/im/vector/app/config/Config.kt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.config
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of flags to configure the application.
|
||||||
|
*/
|
||||||
|
object Config {
|
||||||
|
/**
|
||||||
|
* Flag to allow external UnifiedPush distributors to be chosen by the user.
|
||||||
|
*
|
||||||
|
* Set to true to allow any available external UnifiedPush distributor to be chosen by the user.
|
||||||
|
* - For Gplay variant it means that FCM will be used by default, but user can choose another UnifiedPush distributor;
|
||||||
|
* - For F-Droid variant, it means that background polling will be used by default, but user can choose another UnifiedPush distributor.
|
||||||
|
*
|
||||||
|
* Set to false to prevent usage of external UnifiedPush distributors.
|
||||||
|
* - For Gplay variant it means that only FCM will be used;
|
||||||
|
* - For F-Droid variant, it means that only background polling will be available to the user.
|
||||||
|
*
|
||||||
|
* *Note*: When the app is already installed on users' phone:
|
||||||
|
* - Changing the value from `false` to `true` will let the user be able to select an external UnifiedPush distributor;
|
||||||
|
* - Changing the value from `true` to `false` will force the app to return to the background sync / Firebase Push.
|
||||||
|
*/
|
||||||
|
const val ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS = true
|
||||||
|
}
|
|
@ -17,7 +17,11 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Note: pusher_http_url should have path '/_matrix/push/v1/notify' -->
|
<!-- Note: pusher_http_url should have path '/_matrix/push/v1/notify' -->
|
||||||
|
<!-- It is the push gateway for FCM embedded distributor -->
|
||||||
<string name="pusher_http_url" translatable="false">https://matrix.org/_matrix/push/v1/notify</string>
|
<string name="pusher_http_url" translatable="false">https://matrix.org/_matrix/push/v1/notify</string>
|
||||||
|
<!-- Note: default_push_gateway_http_url should have path '/_matrix/push/v1/notify' -->
|
||||||
|
<!-- It is the push gateway for UnifiedPush -->
|
||||||
|
<string name="default_push_gateway_http_url" translatable="false">https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify</string>
|
||||||
<!-- Note: pusher_app_id cannot exceed 64 chars -->
|
<!-- Note: pusher_app_id cannot exceed 64 chars -->
|
||||||
<string name="pusher_app_id" translatable="false">im.vector.app.android</string>
|
<string name="pusher_app_id" translatable="false">im.vector.app.android</string>
|
||||||
|
|
||||||
|
|
|
@ -366,6 +366,7 @@ dependencies {
|
||||||
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0"
|
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0"
|
||||||
|
|
||||||
implementation libs.squareup.moshi
|
implementation libs.squareup.moshi
|
||||||
|
implementation libs.squareup.moshiKt
|
||||||
kapt libs.squareup.moshiKotlin
|
kapt libs.squareup.moshiKotlin
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
@ -461,8 +462,10 @@ dependencies {
|
||||||
// Analytics
|
// Analytics
|
||||||
implementation 'com.posthog.android:posthog:1.1.2'
|
implementation 'com.posthog.android:posthog:1.1.2'
|
||||||
|
|
||||||
// gplay flavor only
|
// UnifiedPush
|
||||||
gplayImplementation('com.google.firebase:firebase-messaging:23.0.0') {
|
implementation 'com.github.UnifiedPush:android-connector:2.0.0'
|
||||||
|
// UnifiedPush gplay flavor only
|
||||||
|
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.0.0') {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.ui.robot.settings
|
package im.vector.app.ui.robot.settings
|
||||||
|
|
||||||
import androidx.test.espresso.Espresso.pressBack
|
import androidx.test.espresso.Espresso.pressBack
|
||||||
|
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed
|
||||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.espresso.tools.clickOnPreference
|
import im.vector.app.espresso.tools.clickOnPreference
|
||||||
|
@ -41,7 +42,18 @@ class SettingsNotificationsRobot {
|
||||||
clickOn(R.string.settings_call_notifications_preferences)
|
clickOn(R.string.settings_call_notifications_preferences)
|
||||||
pressBack()
|
pressBack()
|
||||||
*/
|
*/
|
||||||
|
// Email notification. No Emails are configured so we show go to the screen to add email
|
||||||
|
clickOnPreference(R.string.settings_notification_emails_no_emails)
|
||||||
|
assertDisplayed(R.string.settings_emails_and_phone_numbers_title)
|
||||||
|
pressBack()
|
||||||
|
|
||||||
|
// Display the notification method change dialog
|
||||||
|
clickOnPreference(R.string.settings_notification_method)
|
||||||
|
pressBack()
|
||||||
|
|
||||||
clickOnPreference(R.string.settings_notification_troubleshoot)
|
clickOnPreference(R.string.settings_notification_troubleshoot)
|
||||||
|
// Give time for the tests to perform
|
||||||
|
Thread.sleep(12_000)
|
||||||
pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,11 @@ class DebugFeaturesStateFactory @Inject constructor(
|
||||||
key = DebugFeatureKeys.onboardingCombinedLogin,
|
key = DebugFeatureKeys.onboardingCombinedLogin,
|
||||||
factory = VectorFeatures::isOnboardingCombinedLoginEnabled
|
factory = VectorFeatures::isOnboardingCombinedLoginEnabled
|
||||||
),
|
),
|
||||||
|
createBooleanFeature(
|
||||||
|
label = "Allow external UnifiedPush distributors",
|
||||||
|
key = DebugFeatureKeys.allowExternalUnifiedPushDistributors,
|
||||||
|
factory = VectorFeatures::allowExternalUnifiedPushDistributors
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,9 @@ class DebugVectorFeatures(
|
||||||
override fun isOnboardingCombinedLoginEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedLogin)
|
override fun isOnboardingCombinedLoginEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedLogin)
|
||||||
?: vectorFeatures.isOnboardingCombinedLoginEnabled()
|
?: vectorFeatures.isOnboardingCombinedLoginEnabled()
|
||||||
|
|
||||||
|
override fun allowExternalUnifiedPushDistributors(): Boolean = read(DebugFeatureKeys.allowExternalUnifiedPushDistributors)
|
||||||
|
?: vectorFeatures.allowExternalUnifiedPushDistributors()
|
||||||
|
|
||||||
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
|
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
|
||||||
?: vectorFeatures.isScreenSharingEnabled()
|
?: vectorFeatures.isScreenSharingEnabled()
|
||||||
|
|
||||||
|
@ -117,6 +120,7 @@ object DebugFeatureKeys {
|
||||||
val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize")
|
val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize")
|
||||||
val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register")
|
val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register")
|
||||||
val onboardingCombinedLogin = booleanPreferencesKey("onboarding-combined-login")
|
val onboardingCombinedLogin = booleanPreferencesKey("onboarding-combined-login")
|
||||||
|
val allowExternalUnifiedPushDistributors = booleanPreferencesKey("allow-external-unified-push-distributors")
|
||||||
val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
|
val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
|
||||||
val screenSharing = booleanPreferencesKey("screen-sharing")
|
val screenSharing = booleanPreferencesKey("screen-sharing")
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,20 @@
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".fdroid.receiver.KeepInternalDistributor"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<!--
|
||||||
|
This action is checked to track installed and uninstalled distributors.
|
||||||
|
We declare it to keep the background sync as an internal
|
||||||
|
unifiedpush distributor.
|
||||||
|
-->
|
||||||
|
<action android:name="org.unifiedpush.android.distributor.REGISTER" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".fdroid.service.GuardService"
|
android:name=".fdroid.service.GuardService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
@ -35,4 +49,4 @@
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -23,14 +23,14 @@ import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver
|
||||||
import im.vector.app.features.settings.BackgroundSyncMode
|
import im.vector.app.features.settings.BackgroundSyncMode
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
object BackgroundSyncStarter {
|
class BackgroundSyncStarter @Inject constructor(
|
||||||
fun start(
|
private val context: Context,
|
||||||
context: Context,
|
private val vectorPreferences: VectorPreferences,
|
||||||
vectorPreferences: VectorPreferences,
|
private val clock: Clock
|
||||||
activeSessionHolder: ActiveSessionHolder,
|
) {
|
||||||
clock: Clock
|
fun start(activeSessionHolder: ActiveSessionHolder) {
|
||||||
) {
|
|
||||||
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||||
val activeSession = activeSessionHolder.getSafeActiveSession() ?: return
|
val activeSession = activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
when (vectorPreferences.getFdroidSyncBackgroundMode()) {
|
when (vectorPreferences.getFdroidSyncBackgroundMode()) {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.fdroid.receiver
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UnifiedPush lib tracks an action to check installed and uninstalled distributors.
|
||||||
|
* We declare it to keep the background sync as an internal unifiedpush distributor.
|
||||||
|
* This class is used to declare this action.
|
||||||
|
*/
|
||||||
|
class KeepInternalDistributor : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {}
|
||||||
|
}
|
|
@ -20,20 +20,20 @@ package im.vector.app.fdroid.receiver
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import im.vector.app.core.extensions.singletonEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.fdroid.BackgroundSyncStarter
|
import im.vector.app.fdroid.BackgroundSyncStarter
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
|
class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
|
@Inject lateinit var backgroundSyncStarter: BackgroundSyncStarter
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
Timber.v("## onReceive() ${intent.action}")
|
Timber.v("## onReceive() ${intent.action}")
|
||||||
val singletonEntryPoint = context.singletonEntryPoint()
|
backgroundSyncStarter.start(activeSessionHolder)
|
||||||
BackgroundSyncStarter.start(
|
|
||||||
context,
|
|
||||||
singletonEntryPoint.vectorPreferences(),
|
|
||||||
singletonEntryPoint.activeSessionHolder(),
|
|
||||||
singletonEntryPoint.clock()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,34 +21,35 @@ import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.PushersManager
|
||||||
import im.vector.app.core.time.Clock
|
|
||||||
import im.vector.app.fdroid.BackgroundSyncStarter
|
import im.vector.app.fdroid.BackgroundSyncStarter
|
||||||
import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver
|
import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class has an alter ego in the gplay variant.
|
* This class has an alter ego in the gplay variant.
|
||||||
*/
|
*/
|
||||||
object FcmHelper {
|
class FcmHelper @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val backgroundSyncStarter: BackgroundSyncStarter,
|
||||||
|
) {
|
||||||
|
|
||||||
fun isPushSupported(): Boolean = false
|
fun isFirebaseAvailable(): Boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the FCM registration token.
|
* Retrieves the FCM registration token.
|
||||||
*
|
*
|
||||||
* @return the FCM token or null if not received from FCM
|
* @return the FCM token or null if not received from FCM
|
||||||
*/
|
*/
|
||||||
fun getFcmToken(context: Context): String? {
|
fun getFcmToken(): String? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store FCM token to the SharedPrefs
|
* Store FCM token to the SharedPrefs
|
||||||
*
|
*
|
||||||
* @param context android context
|
|
||||||
* @param token the token to store
|
* @param token the token to store
|
||||||
*/
|
*/
|
||||||
fun storeFcmToken(context: Context, token: String?) {
|
fun storeFcmToken(token: String?) {
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,18 +62,13 @@ object FcmHelper {
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
|
||||||
// try to stop all regardless of background mode
|
// try to stop all regardless of background mode
|
||||||
activeSessionHolder.getSafeActiveSession()?.syncService()?.stopAnyBackgroundSync()
|
activeSessionHolder.getSafeActiveSession()?.syncService()?.stopAnyBackgroundSync()
|
||||||
AlarmSyncBroadcastReceiver.cancelAlarm(context)
|
AlarmSyncBroadcastReceiver.cancelAlarm(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEnterBackground(
|
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
|
||||||
context: Context,
|
backgroundSyncStarter.start(activeSessionHolder)
|
||||||
vectorPreferences: VectorPreferences,
|
|
||||||
activeSessionHolder: ActiveSessionHolder,
|
|
||||||
clock: Clock
|
|
||||||
) {
|
|
||||||
BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder, clock)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,26 +16,42 @@
|
||||||
package im.vector.app.push.fcm
|
package im.vector.app.push.fcm
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.fdroid.features.settings.troubleshoot.TestAutoStartBoot
|
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.TestBackgroundRestrictions
|
||||||
import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization
|
import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||||
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
|
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestCurrentUnifiedPushDistributor
|
||||||
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
|
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestEndpointAsTokenRegistration
|
||||||
import im.vector.app.features.settings.troubleshoot.TestNotification
|
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.TestPushRulesSettings
|
||||||
import im.vector.app.features.settings.troubleshoot.TestSystemSettings
|
import im.vector.app.features.settings.troubleshoot.TestSystemSettings
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestUnifiedPushEndpoint
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestUnifiedPushGateway
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
private val testSystemSettings: TestSystemSettings,
|
private val testSystemSettings: TestSystemSettings,
|
||||||
private val testAccountSettings: TestAccountSettings,
|
private val testAccountSettings: TestAccountSettings,
|
||||||
private val testDeviceSettings: TestDeviceSettings,
|
private val testDeviceSettings: TestDeviceSettings,
|
||||||
private val testPushRulesSettings: TestPushRulesSettings,
|
private val testPushRulesSettings: TestPushRulesSettings,
|
||||||
|
private val testCurrentUnifiedPushDistributor: TestCurrentUnifiedPushDistributor,
|
||||||
|
private val testUnifiedPushGateway: TestUnifiedPushGateway,
|
||||||
|
private val testUnifiedPushEndpoint: TestUnifiedPushEndpoint,
|
||||||
|
private val testAvailableUnifiedPushDistributors: TestAvailableUnifiedPushDistributors,
|
||||||
|
private val testEndpointAsTokenRegistration: TestEndpointAsTokenRegistration,
|
||||||
|
private val testPushFromPushGateway: TestPushFromPushGateway,
|
||||||
private val testAutoStartBoot: TestAutoStartBoot,
|
private val testAutoStartBoot: TestAutoStartBoot,
|
||||||
private val testBackgroundRestrictions: TestBackgroundRestrictions,
|
private val testBackgroundRestrictions: TestBackgroundRestrictions,
|
||||||
private val testBatteryOptimization: TestBatteryOptimization,
|
private val testBatteryOptimization: TestBatteryOptimization,
|
||||||
private val testNotification: TestNotification
|
private val testNotification: TestNotification,
|
||||||
|
private val vectorFeatures: VectorFeatures,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
||||||
|
@ -44,9 +60,20 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||||
mgr.addTest(testAccountSettings)
|
mgr.addTest(testAccountSettings)
|
||||||
mgr.addTest(testDeviceSettings)
|
mgr.addTest(testDeviceSettings)
|
||||||
mgr.addTest(testPushRulesSettings)
|
mgr.addTest(testPushRulesSettings)
|
||||||
mgr.addTest(testAutoStartBoot)
|
if (vectorFeatures.allowExternalUnifiedPushDistributors()) {
|
||||||
mgr.addTest(testBackgroundRestrictions)
|
mgr.addTest(testAvailableUnifiedPushDistributors)
|
||||||
mgr.addTest(testBatteryOptimization)
|
mgr.addTest(testCurrentUnifiedPushDistributor)
|
||||||
|
}
|
||||||
|
if (unifiedPushHelper.isBackgroundSync()) {
|
||||||
|
mgr.addTest(testAutoStartBoot)
|
||||||
|
mgr.addTest(testBackgroundRestrictions)
|
||||||
|
mgr.addTest(testBatteryOptimization)
|
||||||
|
} else {
|
||||||
|
mgr.addTest(testUnifiedPushGateway)
|
||||||
|
mgr.addTest(testUnifiedPushEndpoint)
|
||||||
|
mgr.addTest(testEndpointAsTokenRegistration)
|
||||||
|
mgr.addTest(testPushFromPushGateway)
|
||||||
|
}
|
||||||
mgr.addTest(testNotification)
|
mgr.addTest(testNotification)
|
||||||
return mgr
|
return mgr
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,17 @@
|
||||||
android:name="firebase_analytics_collection_deactivated"
|
android:name="firebase_analytics_collection_deactivated"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".gplay.push.fcm.VectorFirebaseMessagingService"
|
android:enabled="true"
|
||||||
|
android:name=".push.fcm.EmbeddedFCMDistributor"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
<action android:name="org.unifiedpush.android.distributor.REGISTER" />
|
||||||
|
<action android:name="org.unifiedpush.android.distributor.UNREGISTER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,8 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class TestFirebaseToken @Inject constructor(
|
class TestFirebaseToken @Inject constructor(
|
||||||
private val context: FragmentActivity,
|
private val context: FragmentActivity,
|
||||||
private val stringProvider: StringProvider
|
private val stringProvider: StringProvider,
|
||||||
|
private val fcmHelper: FcmHelper,
|
||||||
) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {
|
) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {
|
||||||
|
|
||||||
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
|
@ -68,7 +69,7 @@ class TestFirebaseToken @Inject constructor(
|
||||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok)
|
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok)
|
||||||
Timber.e("Retrieved FCM token success [$tok].")
|
Timber.e("Retrieved FCM token success [$tok].")
|
||||||
// Ensure it is well store in our local storage
|
// Ensure it is well store in our local storage
|
||||||
FcmHelper.storeFcmToken(context, token)
|
fcmHelper.storeFcmToken(token)
|
||||||
}
|
}
|
||||||
status = TestStatus.SUCCESS
|
status = TestStatus.SUCCESS
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,14 @@ class TestTokenRegistration @Inject constructor(
|
||||||
private val context: FragmentActivity,
|
private val context: FragmentActivity,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val pushersManager: PushersManager,
|
private val pushersManager: PushersManager,
|
||||||
private val activeSessionHolder: ActiveSessionHolder
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val fcmHelper: FcmHelper,
|
||||||
) :
|
) :
|
||||||
TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
|
TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
|
||||||
|
|
||||||
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
// Check if we have a registered pusher for this token
|
// Check if we have a registered pusher for this token
|
||||||
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
|
val fcmToken = fcmHelper.getFcmToken() ?: run {
|
||||||
status = TestStatus.FAILED
|
status = TestStatus.FAILED
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.push.fcm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.unifiedpush.android.embedded_fcm_distributor.EmbeddedDistributorReceiver
|
||||||
|
|
||||||
|
class EmbeddedFCMDistributor : EmbeddedDistributorReceiver() {
|
||||||
|
override fun getEndpoint(context: Context, token: String, instance: String): String {
|
||||||
|
// Here token is the FCM Token, used by the gateway (sygnal)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,40 +26,41 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.DefaultSharedPreferences
|
import im.vector.app.core.di.DefaultSharedPreferences
|
||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.PushersManager
|
||||||
import im.vector.app.core.time.Clock
|
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class store the FCM token in SharedPrefs and ensure this token is retrieved.
|
* This class store the FCM token in SharedPrefs and ensure this token is retrieved.
|
||||||
* It has an alter ego in the fdroid variant.
|
* It has an alter ego in the fdroid variant.
|
||||||
*/
|
*/
|
||||||
object FcmHelper {
|
class FcmHelper @Inject constructor(
|
||||||
private val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
|
context: Context,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
|
||||||
|
}
|
||||||
|
|
||||||
fun isPushSupported(): Boolean = true
|
private val sharedPrefs = DefaultSharedPreferences.getInstance(context)
|
||||||
|
|
||||||
|
fun isFirebaseAvailable(): Boolean = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the FCM registration token.
|
* Retrieves the FCM registration token.
|
||||||
*
|
*
|
||||||
* @return the FCM token or null if not received from FCM
|
* @return the FCM token or null if not received from FCM
|
||||||
*/
|
*/
|
||||||
fun getFcmToken(context: Context): String? {
|
fun getFcmToken(): String? {
|
||||||
return DefaultSharedPreferences.getInstance(context).getString(PREFS_KEY_FCM_TOKEN, null)
|
return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store FCM token to the SharedPrefs
|
* Store FCM token to the SharedPrefs
|
||||||
* TODO Store in realm
|
* TODO Store in realm
|
||||||
*
|
*
|
||||||
* @param context android context
|
|
||||||
* @param token the token to store
|
* @param token the token to store
|
||||||
*/
|
*/
|
||||||
fun storeFcmToken(
|
fun storeFcmToken(token: String?) {
|
||||||
context: Context,
|
sharedPrefs.edit {
|
||||||
token: String?
|
|
||||||
) {
|
|
||||||
DefaultSharedPreferences.getInstance(context).edit {
|
|
||||||
putString(PREFS_KEY_FCM_TOKEN, token)
|
putString(PREFS_KEY_FCM_TOKEN, token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +77,7 @@ object FcmHelper {
|
||||||
try {
|
try {
|
||||||
FirebaseMessaging.getInstance().token
|
FirebaseMessaging.getInstance().token
|
||||||
.addOnSuccessListener { token ->
|
.addOnSuccessListener { token ->
|
||||||
storeFcmToken(activity, token)
|
storeFcmToken(token)
|
||||||
if (registerPusher) {
|
if (registerPusher) {
|
||||||
pushersManager.enqueueRegisterPusherWithFcmKey(token)
|
pushersManager.enqueueRegisterPusherWithFcmKey(token)
|
||||||
}
|
}
|
||||||
|
@ -98,24 +99,19 @@ object FcmHelper {
|
||||||
* it doesn't, display a dialog that allows users to download the APK from
|
* 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.
|
* the Google Play Store or enable it in the device's system settings.
|
||||||
*/
|
*/
|
||||||
private fun checkPlayServices(activity: Activity): Boolean {
|
private fun checkPlayServices(context: Context): Boolean {
|
||||||
val apiAvailability = GoogleApiAvailability.getInstance()
|
val apiAvailability = GoogleApiAvailability.getInstance()
|
||||||
val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
|
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
|
||||||
return resultCode == ConnectionResult.SUCCESS
|
return resultCode == ConnectionResult.SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun onEnterBackground(
|
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
|
||||||
context: Context,
|
|
||||||
vectorPreferences: VectorPreferences,
|
|
||||||
activeSessionHolder: ActiveSessionHolder,
|
|
||||||
clock: Clock
|
|
||||||
) {
|
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,28 +16,42 @@
|
||||||
package im.vector.app.push.fcm
|
package im.vector.app.push.fcm
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||||
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
|
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestCurrentUnifiedPushDistributor
|
||||||
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
|
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestEndpointAsTokenRegistration
|
||||||
import im.vector.app.features.settings.troubleshoot.TestNotification
|
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.TestPushRulesSettings
|
||||||
import im.vector.app.features.settings.troubleshoot.TestSystemSettings
|
import im.vector.app.features.settings.troubleshoot.TestSystemSettings
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestUnifiedPushEndpoint
|
||||||
|
import im.vector.app.features.settings.troubleshoot.TestUnifiedPushGateway
|
||||||
import im.vector.app.gplay.features.settings.troubleshoot.TestFirebaseToken
|
import im.vector.app.gplay.features.settings.troubleshoot.TestFirebaseToken
|
||||||
import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices
|
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.gplay.features.settings.troubleshoot.TestTokenRegistration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
private val testSystemSettings: TestSystemSettings,
|
private val testSystemSettings: TestSystemSettings,
|
||||||
private val testAccountSettings: TestAccountSettings,
|
private val testAccountSettings: TestAccountSettings,
|
||||||
private val testDeviceSettings: TestDeviceSettings,
|
private val testDeviceSettings: TestDeviceSettings,
|
||||||
private val testBingRulesSettings: TestPushRulesSettings,
|
private val testPushRulesSettings: TestPushRulesSettings,
|
||||||
private val testPlayServices: TestPlayServices,
|
private val testPlayServices: TestPlayServices,
|
||||||
private val testFirebaseToken: TestFirebaseToken,
|
private val testFirebaseToken: TestFirebaseToken,
|
||||||
private val testTokenRegistration: TestTokenRegistration,
|
private val testTokenRegistration: TestTokenRegistration,
|
||||||
|
private val testCurrentUnifiedPushDistributor: TestCurrentUnifiedPushDistributor,
|
||||||
|
private val testUnifiedPushGateway: TestUnifiedPushGateway,
|
||||||
|
private val testUnifiedPushEndpoint: TestUnifiedPushEndpoint,
|
||||||
|
private val testAvailableUnifiedPushDistributors: TestAvailableUnifiedPushDistributors,
|
||||||
|
private val testEndpointAsTokenRegistration: TestEndpointAsTokenRegistration,
|
||||||
private val testPushFromPushGateway: TestPushFromPushGateway,
|
private val testPushFromPushGateway: TestPushFromPushGateway,
|
||||||
private val testNotification: TestNotification
|
private val testNotification: TestNotification,
|
||||||
|
private val vectorFeatures: VectorFeatures,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
||||||
|
@ -45,10 +59,20 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||||
mgr.addTest(testSystemSettings)
|
mgr.addTest(testSystemSettings)
|
||||||
mgr.addTest(testAccountSettings)
|
mgr.addTest(testAccountSettings)
|
||||||
mgr.addTest(testDeviceSettings)
|
mgr.addTest(testDeviceSettings)
|
||||||
mgr.addTest(testBingRulesSettings)
|
mgr.addTest(testPushRulesSettings)
|
||||||
mgr.addTest(testPlayServices)
|
if (vectorFeatures.allowExternalUnifiedPushDistributors()) {
|
||||||
mgr.addTest(testFirebaseToken)
|
mgr.addTest(testAvailableUnifiedPushDistributors)
|
||||||
mgr.addTest(testTokenRegistration)
|
mgr.addTest(testCurrentUnifiedPushDistributor)
|
||||||
|
}
|
||||||
|
if (unifiedPushHelper.isEmbeddedDistributor()) {
|
||||||
|
mgr.addTest(testPlayServices)
|
||||||
|
mgr.addTest(testFirebaseToken)
|
||||||
|
mgr.addTest(testTokenRegistration)
|
||||||
|
} else {
|
||||||
|
mgr.addTest(testUnifiedPushGateway)
|
||||||
|
mgr.addTest(testUnifiedPushEndpoint)
|
||||||
|
mgr.addTest(testEndpointAsTokenRegistration)
|
||||||
|
}
|
||||||
mgr.addTest(testPushFromPushGateway)
|
mgr.addTest(testPushFromPushGateway)
|
||||||
mgr.addTest(testNotification)
|
mgr.addTest(testNotification)
|
||||||
return mgr
|
return mgr
|
||||||
|
|
|
@ -304,7 +304,8 @@
|
||||||
android:supportsPictureInPicture="true" />
|
android:supportsPictureInPicture="true" />
|
||||||
|
|
||||||
<activity android:name=".features.terms.ReviewTermsActivity" />
|
<activity android:name=".features.terms.ReviewTermsActivity" />
|
||||||
<activity android:name=".features.widgets.WidgetActivity"
|
<activity
|
||||||
|
android:name=".features.widgets.WidgetActivity"
|
||||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />
|
||||||
|
|
||||||
<activity android:name=".features.pin.PinActivity" />
|
<activity android:name=".features.pin.PinActivity" />
|
||||||
|
@ -410,6 +411,20 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<!-- UnifiedPush -->
|
||||||
|
<receiver
|
||||||
|
android:exported="true"
|
||||||
|
android:enabled="true"
|
||||||
|
android:name=".core.pushers.VectorMessagingReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
|
||||||
|
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
|
||||||
|
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
|
||||||
|
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED" />
|
||||||
|
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<!-- Providers -->
|
<!-- Providers -->
|
||||||
|
|
||||||
<!-- Remove WorkManagerInitializer Provider because we are using on-demand initialization of WorkManager-->
|
<!-- Remove WorkManagerInitializer Provider because we are using on-demand initialization of WorkManager-->
|
||||||
|
|
|
@ -43,7 +43,6 @@ import dagger.hilt.android.HiltAndroidApp
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.extensions.configureAndStart
|
import im.vector.app.core.extensions.configureAndStart
|
||||||
import im.vector.app.core.extensions.startSyncing
|
import im.vector.app.core.extensions.startSyncing
|
||||||
import im.vector.app.core.time.Clock
|
|
||||||
import im.vector.app.features.analytics.VectorAnalytics
|
import im.vector.app.features.analytics.VectorAnalytics
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
import im.vector.app.features.configuration.VectorConfiguration
|
import im.vector.app.features.configuration.VectorConfiguration
|
||||||
|
@ -86,7 +85,6 @@ class VectorApplication :
|
||||||
@Inject lateinit var emojiCompatWrapper: EmojiCompatWrapper
|
@Inject lateinit var emojiCompatWrapper: EmojiCompatWrapper
|
||||||
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
||||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
@Inject lateinit var clock: Clock
|
|
||||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||||
@Inject lateinit var versionProvider: VersionProvider
|
@Inject lateinit var versionProvider: VersionProvider
|
||||||
|
@ -100,6 +98,7 @@ class VectorApplication :
|
||||||
@Inject lateinit var vectorFileLogger: VectorFileLogger
|
@Inject lateinit var vectorFileLogger: VectorFileLogger
|
||||||
@Inject lateinit var vectorAnalytics: VectorAnalytics
|
@Inject lateinit var vectorAnalytics: VectorAnalytics
|
||||||
@Inject lateinit var matrix: Matrix
|
@Inject lateinit var matrix: Matrix
|
||||||
|
@Inject lateinit var fcmHelper: FcmHelper
|
||||||
|
|
||||||
// font thread handler
|
// font thread handler
|
||||||
private var fontThreadHandler: Handler? = null
|
private var fontThreadHandler: Handler? = null
|
||||||
|
@ -174,7 +173,7 @@ class VectorApplication :
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
|
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
Timber.i("App entered foreground")
|
Timber.i("App entered foreground")
|
||||||
FcmHelper.onEnterForeground(appContext, activeSessionHolder)
|
fcmHelper.onEnterForeground(activeSessionHolder)
|
||||||
activeSessionHolder.getSafeActiveSession()?.also {
|
activeSessionHolder.getSafeActiveSession()?.also {
|
||||||
it.syncService().stopAnyBackgroundSync()
|
it.syncService().stopAnyBackgroundSync()
|
||||||
}
|
}
|
||||||
|
@ -182,7 +181,7 @@ class VectorApplication :
|
||||||
|
|
||||||
override fun onPause(owner: LifecycleOwner) {
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
Timber.i("App entered background")
|
Timber.i("App entered background")
|
||||||
FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder, clock)
|
fcmHelper.onEnterBackground(activeSessionHolder)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.core.di
|
||||||
|
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import im.vector.app.ActiveSessionDataSource
|
import im.vector.app.ActiveSessionDataSource
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.core.services.GuardServiceStarter
|
import im.vector.app.core.services.GuardServiceStarter
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
||||||
|
@ -39,6 +40,7 @@ class ActiveSessionHolder @Inject constructor(
|
||||||
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
||||||
private val sessionListener: SessionListener,
|
private val sessionListener: SessionListener,
|
||||||
private val imageManager: ImageManager,
|
private val imageManager: ImageManager,
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
private val guardServiceStarter: GuardServiceStarter
|
private val guardServiceStarter: GuardServiceStarter
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -58,7 +60,7 @@ class ActiveSessionHolder @Inject constructor(
|
||||||
guardServiceStarter.start()
|
guardServiceStarter.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearActiveSession() {
|
suspend fun clearActiveSession() {
|
||||||
// Do some cleanup first
|
// Do some cleanup first
|
||||||
getSafeActiveSession()?.let {
|
getSafeActiveSession()?.let {
|
||||||
Timber.w("clearActiveSession of ${it.myUserId}")
|
Timber.w("clearActiveSession of ${it.myUserId}")
|
||||||
|
@ -72,6 +74,8 @@ class ActiveSessionHolder @Inject constructor(
|
||||||
keyRequestHandler.stop()
|
keyRequestHandler.stop()
|
||||||
incomingVerificationRequestHandler.stop()
|
incomingVerificationRequestHandler.stop()
|
||||||
pushRuleTriggerListener.stop()
|
pushRuleTriggerListener.stop()
|
||||||
|
// No need to unregister the pusher, the sign out will (should?) do it server side.
|
||||||
|
unifiedPushHelper.unregister(pushersManager = null)
|
||||||
guardServiceStarter.stop()
|
guardServiceStarter.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.core.pushers.model.PushData
|
||||||
|
import im.vector.app.core.pushers.model.PushDataFcm
|
||||||
|
import im.vector.app.core.pushers.model.PushDataUnifiedPush
|
||||||
|
import im.vector.app.core.pushers.model.toPushData
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PushParser @Inject constructor() {
|
||||||
|
/**
|
||||||
|
* Parse the received data from Push. Json format are different depending on the source.
|
||||||
|
*
|
||||||
|
* Notifications received by FCM are formatted by the matrix gateway [1]. The data send to FCM is the content
|
||||||
|
* of the "notification" attribute of the json sent to the gateway [2][3].
|
||||||
|
* On the other side, with UnifiedPush, the content of the message received is the content posted to the push
|
||||||
|
* gateway endpoint [3].
|
||||||
|
*
|
||||||
|
* *Note*: If we want to get the same content with FCM and unifiedpush, we can do a new sygnal pusher [4].
|
||||||
|
*
|
||||||
|
* [1] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py
|
||||||
|
* [2] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py#L366
|
||||||
|
* [3] https://spec.matrix.org/latest/push-gateway-api/
|
||||||
|
* [4] https://github.com/p1gp1g/sygnal/blob/unifiedpush/sygnal/upfcmpushkin.py (Not tested for a while)
|
||||||
|
*/
|
||||||
|
fun parseData(message: String, firebaseFormat: Boolean): PushData? {
|
||||||
|
val moshi = MatrixJsonParser.getMoshi()
|
||||||
|
return if (firebaseFormat) {
|
||||||
|
tryOrNull { moshi.adapter(PushDataFcm::class.java).fromJson(message) }?.toPushData()
|
||||||
|
} else {
|
||||||
|
tryOrNull { moshi.adapter(PushDataUnifiedPush::class.java).fromJson(message) }?.toPushData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,40 +29,47 @@ import kotlin.math.abs
|
||||||
private const val DEFAULT_PUSHER_FILE_TAG = "mobile"
|
private const val DEFAULT_PUSHER_FILE_TAG = "mobile"
|
||||||
|
|
||||||
class PushersManager @Inject constructor(
|
class PushersManager @Inject constructor(
|
||||||
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val localeProvider: LocaleProvider,
|
private val localeProvider: LocaleProvider,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val appNameProvider: AppNameProvider
|
private val appNameProvider: AppNameProvider,
|
||||||
) {
|
) {
|
||||||
suspend fun testPush(pushKey: String) {
|
suspend fun testPush() {
|
||||||
val currentSession = activeSessionHolder.getActiveSession()
|
val currentSession = activeSessionHolder.getActiveSession()
|
||||||
|
|
||||||
currentSession.pushersService().testPush(
|
currentSession.pushersService().testPush(
|
||||||
stringProvider.getString(R.string.pusher_http_url),
|
unifiedPushStore.getPushGateway()!!,
|
||||||
stringProvider.getString(R.string.pusher_app_id),
|
stringProvider.getString(R.string.pusher_app_id),
|
||||||
pushKey,
|
unifiedPushStore.getEndpointOrToken().orEmpty(),
|
||||||
TEST_EVENT_ID
|
TEST_EVENT_ID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID {
|
fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID {
|
||||||
val currentSession = activeSessionHolder.getActiveSession()
|
return enqueueRegisterPusher(pushKey, stringProvider.getString(R.string.pusher_http_url))
|
||||||
return currentSession.pushersService().enqueueAddHttpPusher(createHttpPusher(pushKey))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun registerPusherWithFcmKey(pushKey: String) {
|
fun enqueueRegisterPusher(
|
||||||
|
pushKey: String,
|
||||||
|
gateway: String
|
||||||
|
): UUID {
|
||||||
val currentSession = activeSessionHolder.getActiveSession()
|
val currentSession = activeSessionHolder.getActiveSession()
|
||||||
currentSession.pushersService().addHttpPusher(createHttpPusher(pushKey))
|
val pusher = createHttpPusher(pushKey, gateway)
|
||||||
|
return currentSession.pushersService().enqueueAddHttpPusher(pusher)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createHttpPusher(pushKey: String) = HttpPusher(
|
private fun createHttpPusher(
|
||||||
|
pushKey: String,
|
||||||
|
gateway: String
|
||||||
|
) = HttpPusher(
|
||||||
pushKey,
|
pushKey,
|
||||||
stringProvider.getString(R.string.pusher_app_id),
|
stringProvider.getString(R.string.pusher_app_id),
|
||||||
profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()),
|
profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()),
|
||||||
localeProvider.current().language,
|
localeProvider.current().language,
|
||||||
appNameProvider.getAppName(),
|
appNameProvider.getAppName(),
|
||||||
activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE",
|
activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE",
|
||||||
stringProvider.getString(R.string.pusher_http_url),
|
gateway,
|
||||||
append = false,
|
append = false,
|
||||||
withEventIdOnly = true
|
withEventIdOnly = true
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.utils.getApplicationLabel
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
|
import im.vector.app.features.settings.BackgroundSyncMode
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import im.vector.app.push.fcm.FcmHelper
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.Matrix
|
||||||
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||||
|
import org.unifiedpush.android.connector.UnifiedPush
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.net.URL
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UnifiedPushHelper @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val matrix: Matrix,
|
||||||
|
private val vectorFeatures: VectorFeatures,
|
||||||
|
private val fcmHelper: FcmHelper,
|
||||||
|
) {
|
||||||
|
fun register(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
onDoneRunnable: Runnable? = null,
|
||||||
|
) {
|
||||||
|
registerInternal(
|
||||||
|
activity,
|
||||||
|
onDoneRunnable = onDoneRunnable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reRegister(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
pushersManager: PushersManager,
|
||||||
|
onDoneRunnable: Runnable? = null
|
||||||
|
) {
|
||||||
|
registerInternal(
|
||||||
|
activity,
|
||||||
|
force = true,
|
||||||
|
pushersManager = pushersManager,
|
||||||
|
onDoneRunnable = onDoneRunnable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerInternal(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
force: Boolean = false,
|
||||||
|
pushersManager: PushersManager? = null,
|
||||||
|
onDoneRunnable: Runnable? = null
|
||||||
|
) {
|
||||||
|
activity.lifecycleScope.launch {
|
||||||
|
if (!vectorFeatures.allowExternalUnifiedPushDistributors()) {
|
||||||
|
UnifiedPush.saveDistributor(context, context.packageName)
|
||||||
|
UnifiedPush.registerApp(context)
|
||||||
|
onDoneRunnable?.run()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (force) {
|
||||||
|
// Un-register first
|
||||||
|
unregister(pushersManager)
|
||||||
|
}
|
||||||
|
if (UnifiedPush.getDistributor(context).isNotEmpty()) {
|
||||||
|
UnifiedPush.registerApp(context)
|
||||||
|
onDoneRunnable?.run()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, use internal solution (fcm/background sync)
|
||||||
|
UnifiedPush.saveDistributor(context, context.packageName)
|
||||||
|
val distributors = UnifiedPush.getDistributors(context)
|
||||||
|
|
||||||
|
if (distributors.size == 1 && !force) {
|
||||||
|
UnifiedPush.saveDistributor(context, distributors.first())
|
||||||
|
UnifiedPush.registerApp(context)
|
||||||
|
onDoneRunnable?.run()
|
||||||
|
} else {
|
||||||
|
openDistributorDialogInternal(activity, pushersManager, onDoneRunnable, distributors, !force, !force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openDistributorDialog(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
pushersManager: PushersManager,
|
||||||
|
onDoneRunnable: Runnable,
|
||||||
|
) {
|
||||||
|
val distributors = UnifiedPush.getDistributors(activity)
|
||||||
|
openDistributorDialogInternal(
|
||||||
|
activity,
|
||||||
|
pushersManager,
|
||||||
|
onDoneRunnable, distributors,
|
||||||
|
unregisterFirst = true,
|
||||||
|
cancellable = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openDistributorDialogInternal(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
pushersManager: PushersManager?,
|
||||||
|
onDoneRunnable: Runnable?,
|
||||||
|
distributors: List<String>,
|
||||||
|
unregisterFirst: Boolean,
|
||||||
|
cancellable: Boolean,
|
||||||
|
) {
|
||||||
|
val internalDistributorName = stringProvider.getString(
|
||||||
|
if (fcmHelper.isFirebaseAvailable()) {
|
||||||
|
R.string.unifiedpush_distributor_fcm_fallback
|
||||||
|
} else {
|
||||||
|
R.string.unifiedpush_distributor_background_sync
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val distributorsName = distributors.map {
|
||||||
|
if (it == context.packageName) {
|
||||||
|
internalDistributorName
|
||||||
|
} else {
|
||||||
|
context.getApplicationLabel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
|
||||||
|
.setItems(distributorsName.toTypedArray()) { _, which ->
|
||||||
|
val distributor = distributors[which]
|
||||||
|
if (distributor == UnifiedPush.getDistributor(context)) {
|
||||||
|
Timber.d("Same distributor selected again, no action")
|
||||||
|
return@setItems
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.lifecycleScope.launch {
|
||||||
|
if (unregisterFirst) {
|
||||||
|
// Un-register first
|
||||||
|
unregister(pushersManager)
|
||||||
|
}
|
||||||
|
UnifiedPush.saveDistributor(context, distributor)
|
||||||
|
Timber.i("Saving distributor: $distributor")
|
||||||
|
UnifiedPush.registerApp(context)
|
||||||
|
onDoneRunnable?.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setCancelable(cancellable)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun unregister(pushersManager: PushersManager? = null) {
|
||||||
|
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||||
|
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
||||||
|
try {
|
||||||
|
pushersManager?.unregisterPusher(unifiedPushStore.getEndpointOrToken().orEmpty())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.d(e, "Probably unregistering a non existing pusher")
|
||||||
|
}
|
||||||
|
unifiedPushStore.storeUpEndpoint(null)
|
||||||
|
unifiedPushStore.storePushGateway(null)
|
||||||
|
UnifiedPush.unregisterApp(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class DiscoveryResponse(
|
||||||
|
@Json(name = "unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush()
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class DiscoveryUnifiedPush(
|
||||||
|
@Json(name = "gateway") val gateway: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun storeCustomOrDefaultGateway(
|
||||||
|
endpoint: String,
|
||||||
|
onDoneRunnable: Runnable? = null
|
||||||
|
) {
|
||||||
|
// if we use the embedded distributor,
|
||||||
|
// register app_id type upfcm on sygnal
|
||||||
|
// the pushkey if FCM key
|
||||||
|
if (UnifiedPush.getDistributor(context) == context.packageName) {
|
||||||
|
unifiedPushStore.storePushGateway(stringProvider.getString(R.string.pusher_http_url))
|
||||||
|
onDoneRunnable?.run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// else, unifiedpush, and pushkey is an endpoint
|
||||||
|
val gateway = stringProvider.getString(R.string.default_push_gateway_http_url)
|
||||||
|
val parsed = URL(endpoint)
|
||||||
|
val custom = "${parsed.protocol}://${parsed.host}/_matrix/push/v1/notify"
|
||||||
|
Timber.i("Testing $custom")
|
||||||
|
try {
|
||||||
|
val response = matrix.rawService().getUrl(custom, CacheStrategy.NoCache)
|
||||||
|
val moshi = MatrixJsonParser.getMoshi()
|
||||||
|
moshi.adapter(DiscoveryResponse::class.java).fromJson(response)
|
||||||
|
?.let { discoveryResponse ->
|
||||||
|
if (discoveryResponse.unifiedpush.gateway == "matrix") {
|
||||||
|
Timber.d("Using custom gateway")
|
||||||
|
unifiedPushStore.storePushGateway(custom)
|
||||||
|
onDoneRunnable?.run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.d(e, "Cannot try custom gateway")
|
||||||
|
}
|
||||||
|
unifiedPushStore.storePushGateway(gateway)
|
||||||
|
onDoneRunnable?.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getExternalDistributors(): List<String> {
|
||||||
|
return UnifiedPush.getDistributors(context)
|
||||||
|
.filterNot { it == context.packageName }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentDistributorName(): String {
|
||||||
|
return when {
|
||||||
|
isEmbeddedDistributor() -> stringProvider.getString(R.string.unifiedpush_distributor_fcm_fallback)
|
||||||
|
isBackgroundSync() -> stringProvider.getString(R.string.unifiedpush_distributor_background_sync)
|
||||||
|
else -> context.getApplicationLabel(UnifiedPush.getDistributor(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEmbeddedDistributor(): Boolean {
|
||||||
|
return UnifiedPush.getDistributor(context) == context.packageName && fcmHelper.isFirebaseAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isBackgroundSync(): Boolean {
|
||||||
|
return UnifiedPush.getDistributor(context) == context.packageName && !fcmHelper.isFirebaseAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPrivacyFriendlyUpEndpoint(): String? {
|
||||||
|
val endpoint = unifiedPushStore.getEndpointOrToken()
|
||||||
|
if (endpoint.isNullOrEmpty()) return null
|
||||||
|
if (isEmbeddedDistributor()) {
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val parsed = URL(endpoint)
|
||||||
|
"${parsed.protocol}://${parsed.host}/***"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Error parsing unifiedpush endpoint")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 androidx.core.content.edit
|
||||||
|
import im.vector.app.core.di.DefaultSharedPreferences
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UnifiedPushStore @Inject constructor(
|
||||||
|
context: Context,
|
||||||
|
) {
|
||||||
|
private val defaultPrefs = DefaultSharedPreferences.getInstance(context)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the UnifiedPush Endpoint.
|
||||||
|
*
|
||||||
|
* @return the UnifiedPush Endpoint or null if not received
|
||||||
|
*/
|
||||||
|
fun getEndpointOrToken(): String? {
|
||||||
|
return defaultPrefs.getString(PREFS_ENDPOINT_OR_TOKEN, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store UnifiedPush Endpoint to the SharedPrefs.
|
||||||
|
*
|
||||||
|
* @param endpoint the endpoint to store
|
||||||
|
*/
|
||||||
|
fun storeUpEndpoint(endpoint: String?) {
|
||||||
|
defaultPrefs.edit {
|
||||||
|
putString(PREFS_ENDPOINT_OR_TOKEN, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the Push Gateway.
|
||||||
|
*
|
||||||
|
* @return the Push Gateway or null if not defined
|
||||||
|
*/
|
||||||
|
fun getPushGateway(): String? {
|
||||||
|
return defaultPrefs.getString(PREFS_PUSH_GATEWAY, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store Push Gateway to the SharedPrefs.
|
||||||
|
*
|
||||||
|
* @param gateway the push gateway to store
|
||||||
|
*/
|
||||||
|
fun storePushGateway(gateway: String?) {
|
||||||
|
defaultPrefs.edit {
|
||||||
|
putString(PREFS_PUSH_GATEWAY, gateway)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREFS_ENDPOINT_OR_TOKEN = "UP_ENDPOINT_OR_TOKEN"
|
||||||
|
private const val PREFS_PUSH_GATEWAY = "PUSH_GATEWAY"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,27 +14,28 @@
|
||||||
* limitations under the License.
|
* 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.content.Intent
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService
|
|
||||||
import com.google.firebase.messaging.RemoteMessage
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.network.WifiDetector
|
import im.vector.app.core.network.WifiDetector
|
||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.model.PushData
|
||||||
|
import im.vector.app.core.services.GuardServiceStarter
|
||||||
import im.vector.app.features.notifications.NotifiableEventResolver
|
import im.vector.app.features.notifications.NotifiableEventResolver
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
|
import im.vector.app.features.settings.BackgroundSyncMode
|
||||||
import im.vector.app.features.settings.VectorDataStore
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.push.fcm.FcmHelper
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -44,24 +45,28 @@ import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
|
import org.unifiedpush.android.connector.MessagingReceiver
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
|
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class extending FirebaseMessagingService.
|
* Hilt injection happen at super.onReceive().
|
||||||
*/
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
class VectorMessagingReceiver : MessagingReceiver() {
|
||||||
|
|
||||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver
|
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver
|
||||||
@Inject lateinit var pusherManager: PushersManager
|
@Inject lateinit var pushersManager: PushersManager
|
||||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||||
@Inject lateinit var vectorDataStore: VectorDataStore
|
@Inject lateinit var vectorDataStore: VectorDataStore
|
||||||
@Inject lateinit var wifiDetector: WifiDetector
|
@Inject lateinit var wifiDetector: WifiDetector
|
||||||
|
@Inject lateinit var guardServiceStarter: GuardServiceStarter
|
||||||
|
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||||
|
@Inject lateinit var unifiedPushStore: UnifiedPushStore
|
||||||
|
@Inject lateinit var pushParser: PushParser
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
|
@ -73,22 +78,29 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
/**
|
/**
|
||||||
* Called when message is received.
|
* Called when message is received.
|
||||||
*
|
*
|
||||||
|
* @param context the Android context
|
||||||
* @param message the message
|
* @param message the message
|
||||||
|
* @param instance connection, for multi-account
|
||||||
*/
|
*/
|
||||||
override fun onMessageReceived(message: RemoteMessage) {
|
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
||||||
|
Timber.tag(loggerTag.value).d("## onMessage() received")
|
||||||
|
|
||||||
|
val sMessage = String(message)
|
||||||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
||||||
Timber.tag(loggerTag.value).d("## onMessageReceived() %s", message.data.toString())
|
Timber.tag(loggerTag.value).d("## onMessage() $sMessage")
|
||||||
}
|
}
|
||||||
Timber.tag(loggerTag.value).d("## onMessageReceived() from FCM with priority %s", message.priority)
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
vectorDataStore.incrementPushCounter()
|
vectorDataStore.incrementPushCounter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val pushData = pushParser.parseData(sMessage, unifiedPushHelper.isEmbeddedDistributor())
|
||||||
|
?: return Unit.also { Timber.tag(loggerTag.value).w("Invalid received data Json format") }
|
||||||
|
|
||||||
// Diagnostic Push
|
// Diagnostic Push
|
||||||
if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) {
|
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
|
||||||
val intent = Intent(NotificationUtils.PUSH_ACTION)
|
val intent = Intent(NotificationUtils.PUSH_ACTION)
|
||||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,48 +114,64 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
// we are in foreground, let the sync do the things?
|
// we are in foreground, let the sync do the things?
|
||||||
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
|
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
|
||||||
} else {
|
} else {
|
||||||
onMessageReceivedInternal(message.data)
|
onMessageReceivedInternal(pushData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
||||||
|
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
|
||||||
|
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||||
|
// If the endpoint has changed
|
||||||
|
// or the gateway has changed
|
||||||
|
if (unifiedPushStore.getEndpointOrToken() != endpoint) {
|
||||||
|
unifiedPushStore.storeUpEndpoint(endpoint)
|
||||||
|
coroutineScope.launch {
|
||||||
|
unifiedPushHelper.storeCustomOrDefaultGateway(endpoint) {
|
||||||
|
unifiedPushStore.getPushGateway()?.let {
|
||||||
|
pushersManager.enqueueRegisterPusher(endpoint, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED
|
||||||
|
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
||||||
|
guardServiceStarter.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRegistrationFailed(context: Context, instance: String) {
|
||||||
|
Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
|
||||||
|
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||||
|
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
||||||
|
guardServiceStarter.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnregistered(context: Context, instance: String) {
|
||||||
|
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
|
||||||
|
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||||
|
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
||||||
|
guardServiceStarter.start()
|
||||||
|
runBlocking {
|
||||||
|
try {
|
||||||
|
pushersManager.unregisterPusher(unifiedPushStore.getEndpointOrToken().orEmpty())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.tag(loggerTag.value).d("Probably unregistering a non existing pusher")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called if InstanceID token is updated. This may occur if the security of
|
* Internal receive method.
|
||||||
* the previous token had been compromised. Note that this is also called
|
|
||||||
* when the InstanceID token is initially generated, so this is where
|
|
||||||
* you retrieve the token.
|
|
||||||
*/
|
|
||||||
override fun onNewToken(refreshedToken: String) {
|
|
||||||
Timber.tag(loggerTag.value).i("onNewToken: FCM Token has been updated")
|
|
||||||
FcmHelper.storeFcmToken(this, refreshedToken)
|
|
||||||
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
|
||||||
pusherManager.enqueueRegisterPusherWithFcmKey(refreshedToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* @param pushData Object containing message data.
|
||||||
*/
|
*/
|
||||||
override fun onDeletedMessages() {
|
private fun onMessageReceivedInternal(pushData: PushData) {
|
||||||
Timber.tag(loggerTag.value).v("## onDeletedMessages()")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal receive method
|
|
||||||
*
|
|
||||||
* @param data Data map containing message data as key/value pairs.
|
|
||||||
* For Set of keys use data.keySet().
|
|
||||||
*/
|
|
||||||
private fun onMessageReceivedInternal(data: Map<String, String>) {
|
|
||||||
try {
|
try {
|
||||||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
||||||
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $data")
|
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
|
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
|
||||||
}
|
}
|
||||||
|
@ -153,15 +181,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
|
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
|
||||||
} else {
|
} else {
|
||||||
val eventId = data["event_id"]
|
if (isEventAlreadyKnown(pushData)) {
|
||||||
val roomId = data["room_id"]
|
|
||||||
|
|
||||||
if (isEventAlreadyKnown(eventId, roomId)) {
|
|
||||||
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
|
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
|
||||||
} else {
|
} else {
|
||||||
// Try to get the Event content faster
|
// Try to get the Event content faster
|
||||||
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
|
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
|
||||||
getEventFastLane(session, roomId, eventId)
|
getEventFastLane(session, pushData)
|
||||||
|
|
||||||
Timber.tag(loggerTag.value).d("Requesting background sync")
|
Timber.tag(loggerTag.value).d("Requesting background sync")
|
||||||
session.syncService().requireBackgroundSync()
|
session.syncService().requireBackgroundSync()
|
||||||
|
@ -172,12 +197,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEventFastLane(session: Session, roomId: String?, eventId: String?) {
|
private fun getEventFastLane(session: Session, pushData: PushData) {
|
||||||
roomId?.takeIf { it.isNotEmpty() } ?: return
|
pushData.roomId ?: return
|
||||||
eventId?.takeIf { it.isNotEmpty() } ?: return
|
pushData.eventId ?: return
|
||||||
|
|
||||||
// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
|
// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
|
||||||
if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(roomId)) {
|
if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(pushData.roomId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +213,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
Timber.tag(loggerTag.value).d("Fast lane: start request")
|
Timber.tag(loggerTag.value).d("Fast lane: start request")
|
||||||
val event = tryOrNull { session.eventService().getEvent(roomId, eventId) } ?: return@launch
|
val event = tryOrNull { session.eventService().getEvent(pushData.roomId, pushData.eventId) } ?: return@launch
|
||||||
|
|
||||||
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true)
|
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true)
|
||||||
|
|
||||||
|
@ -202,12 +227,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
|
|
||||||
// check if the event was not yet received
|
// check if the event was not yet received
|
||||||
// a previous catchup might have already retrieved the notified event
|
// a previous catchup might have already retrieved the notified event
|
||||||
private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean {
|
private fun isEventAlreadyKnown(pushData: PushData): Boolean {
|
||||||
if (null != eventId && null != roomId) {
|
if (pushData.eventId != null && pushData.roomId != null) {
|
||||||
try {
|
try {
|
||||||
val session = activeSessionHolder.getSafeActiveSession() ?: return false
|
val session = activeSessionHolder.getSafeActiveSession() ?: return false
|
||||||
val room = session.getRoom(roomId) ?: return false
|
val room = session.getRoom(pushData.roomId) ?: return false
|
||||||
return room.getTimelineEvent(eventId) != null
|
return room.getTimelineEvent(pushData.eventId) != null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
|
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
|
||||||
}
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent parsed data that the app has received from a Push content.
|
||||||
|
*
|
||||||
|
* @property eventId The Event ID. If not null, it will not be empty, and will have a valid format.
|
||||||
|
* @property roomId The Room ID. If not null, it will not be empty, and will have a valid format.
|
||||||
|
* @property unread Number of unread message.
|
||||||
|
*/
|
||||||
|
data class PushData(
|
||||||
|
val eventId: String?,
|
||||||
|
val roomId: String?,
|
||||||
|
val unread: Int?,
|
||||||
|
)
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this case, the format is:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "event_id":"$anEventId",
|
||||||
|
* "room_id":"!aRoomId",
|
||||||
|
* "unread":"1",
|
||||||
|
* "prio":"high"
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* .
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PushDataFcm(
|
||||||
|
@Json(name = "event_id") val eventId: String?,
|
||||||
|
@Json(name = "room_id") val roomId: String?,
|
||||||
|
@Json(name = "unread") var unread: Int?,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun PushDataFcm.toPushData() = PushData(
|
||||||
|
eventId = eventId?.takeIf { MatrixPatterns.isEventId(it) },
|
||||||
|
roomId = roomId?.takeIf { MatrixPatterns.isRoomId(it) },
|
||||||
|
unread = unread
|
||||||
|
)
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this case, the format is:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "notification":{
|
||||||
|
* "event_id":"$anEventId",
|
||||||
|
* "room_id":"!aRoomId",
|
||||||
|
* "counts":{
|
||||||
|
* "unread":1
|
||||||
|
* },
|
||||||
|
* "prio":"high"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* .
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PushDataUnifiedPush(
|
||||||
|
@Json(name = "notification") val notification: PushDataUnifiedPushNotification?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PushDataUnifiedPushNotification(
|
||||||
|
@Json(name = "event_id") val eventId: String?,
|
||||||
|
@Json(name = "room_id") val roomId: String?,
|
||||||
|
@Json(name = "counts") var counts: PushDataUnifiedPushCounts?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PushDataUnifiedPushCounts(
|
||||||
|
@Json(name = "unread") val unread: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
fun PushDataUnifiedPush.toPushData() = PushData(
|
||||||
|
eventId = notification?.eventId?.takeIf { MatrixPatterns.isEventId(it) },
|
||||||
|
roomId = notification?.roomId?.takeIf { MatrixPatterns.isRoomId(it) },
|
||||||
|
unread = notification?.counts?.unread
|
||||||
|
)
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.core.resources
|
package im.vector.app.core.resources
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import im.vector.app.core.utils.getApplicationLabel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -25,9 +26,7 @@ class AppNameProvider @Inject constructor(private val context: Context) {
|
||||||
fun getAppName(): String {
|
fun getAppName(): String {
|
||||||
return try {
|
return try {
|
||||||
val appPackageName = context.applicationContext.packageName
|
val appPackageName = context.applicationContext.packageName
|
||||||
val pm = context.packageManager
|
var appName = context.getApplicationLabel(appPackageName)
|
||||||
val appInfo = pm.getApplicationInfo(appPackageName, 0)
|
|
||||||
var appName = pm.getApplicationLabel(appInfo).toString()
|
|
||||||
|
|
||||||
// Use appPackageName instead of appName if appName contains any non-ASCII character
|
// Use appPackageName instead of appName if appName contains any non-ASCII character
|
||||||
if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) {
|
if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
@ -59,6 +60,18 @@ fun Context.isAnimationEnabled(): Boolean {
|
||||||
return Settings.Global.getFloat(contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) != 0f
|
return Settings.Global.getFloat(contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) != 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the application label of the provided package. If not found, the package is returned.
|
||||||
|
*/
|
||||||
|
fun Context.getApplicationLabel(packageName: String): String {
|
||||||
|
return try {
|
||||||
|
val ai = packageManager.getApplicationInfo(packageName, 0)
|
||||||
|
packageManager.getApplicationLabel(ai).toString()
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
packageName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* display the system dialog for granting this permission. If previously granted, the
|
* display the system dialog for granting this permission. If previously granted, the
|
||||||
* system will not show it (so you should call this method).
|
* system will not show it (so you should call this method).
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features
|
package im.vector.app.features
|
||||||
|
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
|
import im.vector.app.config.Config
|
||||||
|
|
||||||
interface VectorFeatures {
|
interface VectorFeatures {
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ interface VectorFeatures {
|
||||||
fun isOnboardingPersonalizeEnabled(): Boolean
|
fun isOnboardingPersonalizeEnabled(): Boolean
|
||||||
fun isOnboardingCombinedRegisterEnabled(): Boolean
|
fun isOnboardingCombinedRegisterEnabled(): Boolean
|
||||||
fun isOnboardingCombinedLoginEnabled(): Boolean
|
fun isOnboardingCombinedLoginEnabled(): Boolean
|
||||||
|
fun allowExternalUnifiedPushDistributors(): Boolean
|
||||||
fun isScreenSharingEnabled(): Boolean
|
fun isScreenSharingEnabled(): Boolean
|
||||||
|
|
||||||
enum class OnboardingVariant {
|
enum class OnboardingVariant {
|
||||||
|
@ -44,5 +46,6 @@ class DefaultVectorFeatures : VectorFeatures {
|
||||||
override fun isOnboardingPersonalizeEnabled() = false
|
override fun isOnboardingPersonalizeEnabled() = false
|
||||||
override fun isOnboardingCombinedRegisterEnabled() = false
|
override fun isOnboardingCombinedRegisterEnabled() = false
|
||||||
override fun isOnboardingCombinedLoginEnabled() = false
|
override fun isOnboardingCombinedLoginEnabled() = false
|
||||||
|
override fun allowExternalUnifiedPushDistributors(): Boolean = Config.ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS
|
||||||
override fun isScreenSharingEnabled(): Boolean = true
|
override fun isScreenSharingEnabled(): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import im.vector.app.ActiveSessionDataSource
|
import im.vector.app.ActiveSessionDataSource
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.core.services.CallService
|
import im.vector.app.core.services.CallService
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.plan.CallEnded
|
import im.vector.app.features.analytics.plan.CallEnded
|
||||||
|
@ -32,7 +33,6 @@ import im.vector.app.features.call.lookup.CallUserMapper
|
||||||
import im.vector.app.features.call.utils.EglUtils
|
import im.vector.app.features.call.utils.EglUtils
|
||||||
import im.vector.app.features.call.vectorCallService
|
import im.vector.app.features.call.vectorCallService
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.push.fcm.FcmHelper
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
@ -72,7 +72,8 @@ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP)
|
||||||
class WebRtcCallManager @Inject constructor(
|
class WebRtcCallManager @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val activeSessionDataSource: ActiveSessionDataSource,
|
private val activeSessionDataSource: ActiveSessionDataSource,
|
||||||
private val analyticsTracker: AnalyticsTracker
|
private val analyticsTracker: AnalyticsTracker,
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
) : CallListener,
|
) : CallListener,
|
||||||
DefaultLifecycleObserver {
|
DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
@ -272,7 +273,7 @@ class WebRtcCallManager @Inject constructor(
|
||||||
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
|
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
|
||||||
// did we start background sync? so we should stop it
|
// did we start background sync? so we should stop it
|
||||||
if (isInBackground) {
|
if (isInBackground) {
|
||||||
if (FcmHelper.isPushSupported()) {
|
if (!unifiedPushHelper.isBackgroundSync()) {
|
||||||
currentSession?.syncService()?.stopAnyBackgroundSync()
|
currentSession?.syncService()?.stopAnyBackgroundSync()
|
||||||
} else {
|
} else {
|
||||||
// for fdroid we should not stop, it should continue syncing
|
// for fdroid we should not stop, it should continue syncing
|
||||||
|
@ -378,7 +379,7 @@ class WebRtcCallManager @Inject constructor(
|
||||||
// and thus won't be able to received events. For example if the call is
|
// 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
|
// accepted on an other session this device will continue ringing
|
||||||
if (isInBackground) {
|
if (isInBackground) {
|
||||||
if (FcmHelper.isPushSupported()) {
|
if (!unifiedPushHelper.isBackgroundSync()) {
|
||||||
// only for push version as fdroid version is already doing it?
|
// only for push version as fdroid version is already doing it?
|
||||||
currentSession?.syncService()?.startAutomaticBackgroundSync(30, 0)
|
currentSession?.syncService()?.startAutomaticBackgroundSync(30, 0)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -44,6 +44,7 @@ import im.vector.app.core.extensions.replaceFragment
|
||||||
import im.vector.app.core.extensions.validateBackPressed
|
import im.vector.app.core.extensions.validateBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.PushersManager
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.databinding.ActivityHomeBinding
|
import im.vector.app.databinding.ActivityHomeBinding
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
import im.vector.app.features.MainActivityArgs
|
import im.vector.app.features.MainActivityArgs
|
||||||
|
@ -127,6 +128,8 @@ class HomeActivity :
|
||||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
||||||
@Inject lateinit var appStateHandler: AppStateHandler
|
@Inject lateinit var appStateHandler: AppStateHandler
|
||||||
|
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||||
|
@Inject lateinit var fcmHelper: FcmHelper
|
||||||
|
|
||||||
private val createSpaceResultLauncher = registerStartForActivityResult { activityResult ->
|
private val createSpaceResultLauncher = registerStartForActivityResult { activityResult ->
|
||||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
@ -187,7 +190,15 @@ class HomeActivity :
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
analyticsScreenName = MobileScreen.ScreenName.Home
|
analyticsScreenName = MobileScreen.ScreenName.Home
|
||||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
|
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
|
||||||
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
|
unifiedPushHelper.register(this) {
|
||||||
|
if (unifiedPushHelper.isEmbeddedDistributor()) {
|
||||||
|
fcmHelper.ensureFcmTokenIsRetrieved(
|
||||||
|
this,
|
||||||
|
pushManager,
|
||||||
|
vectorPreferences.areNotificationEnabledForDevice()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
|
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
|
||||||
views.drawerLayout.addDrawerListener(drawerListener)
|
views.drawerLayout.addDrawerListener(drawerListener)
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
|
|
|
@ -141,6 +141,9 @@ class VectorPreferences @Inject constructor(
|
||||||
const val SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY = "SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY"
|
const val SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY = "SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY"
|
||||||
const val SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY = "SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY"
|
const val SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY = "SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY"
|
||||||
|
|
||||||
|
// notification method
|
||||||
|
const val SETTINGS_NOTIFICATION_METHOD_KEY = "SETTINGS_NOTIFICATION_METHOD_KEY"
|
||||||
|
|
||||||
// Calls
|
// Calls
|
||||||
const val SETTINGS_CALL_PREVENT_ACCIDENTAL_CALL_KEY = "SETTINGS_CALL_PREVENT_ACCIDENTAL_CALL_KEY"
|
const val SETTINGS_CALL_PREVENT_ACCIDENTAL_CALL_KEY = "SETTINGS_CALL_PREVENT_ACCIDENTAL_CALL_KEY"
|
||||||
const val SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY = "SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY"
|
const val SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY = "SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY"
|
||||||
|
|
|
@ -38,10 +38,12 @@ import im.vector.app.core.preference.VectorPreference
|
||||||
import im.vector.app.core.preference.VectorPreferenceCategory
|
import im.vector.app.core.preference.VectorPreferenceCategory
|
||||||
import im.vector.app.core.preference.VectorSwitchPreference
|
import im.vector.app.core.preference.VectorSwitchPreference
|
||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.PushersManager
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.core.services.GuardServiceStarter
|
import im.vector.app.core.services.GuardServiceStarter
|
||||||
import im.vector.app.core.utils.combineLatest
|
import im.vector.app.core.utils.combineLatest
|
||||||
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
|
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
|
||||||
import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.features.settings.BackgroundSyncMode
|
import im.vector.app.features.settings.BackgroundSyncMode
|
||||||
|
@ -49,7 +51,6 @@ import im.vector.app.features.settings.BackgroundSyncModeChooserDialog
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.settings.VectorSettingsBaseFragment
|
import im.vector.app.features.settings.VectorSettingsBaseFragment
|
||||||
import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener
|
import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener
|
||||||
import im.vector.app.push.fcm.FcmHelper
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
@ -62,10 +63,12 @@ import javax.inject.Inject
|
||||||
|
|
||||||
// Referenced in vector_settings_preferences_root.xml
|
// Referenced in vector_settings_preferences_root.xml
|
||||||
class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
private val pushManager: PushersManager,
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
|
private val pushersManager: PushersManager,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val guardServiceStarter: GuardServiceStarter
|
private val guardServiceStarter: GuardServiceStarter,
|
||||||
|
private val vectorFeatures: VectorFeatures,
|
||||||
) : VectorSettingsBaseFragment(),
|
) : VectorSettingsBaseFragment(),
|
||||||
BackgroundSyncModeChooserDialog.InteractionListener {
|
BackgroundSyncModeChooserDialog.InteractionListener {
|
||||||
|
|
||||||
|
@ -98,14 +101,14 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)?.let {
|
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)?.let {
|
||||||
it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
|
it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
FcmHelper.getFcmToken(requireContext())?.let {
|
unifiedPushHelper.register(requireActivity()) {
|
||||||
pushManager.registerPusherWithFcmKey(it)
|
// Update the summary
|
||||||
|
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
|
||||||
|
?.summary = unifiedPushHelper.getCurrentDistributorName()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FcmHelper.getFcmToken(requireContext())?.let {
|
unifiedPushHelper.unregister(pushersManager)
|
||||||
pushManager.unregisterPusher(it)
|
session.pushersService().refreshPushers()
|
||||||
session.pushersService().refreshPushers()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +151,22 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)?.let {
|
||||||
|
if (vectorFeatures.allowExternalUnifiedPushDistributors()) {
|
||||||
|
it.summary = unifiedPushHelper.getCurrentDistributorName()
|
||||||
|
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
unifiedPushHelper.openDistributorDialog(requireActivity(), pushersManager) {
|
||||||
|
it.summary = unifiedPushHelper.getCurrentDistributorName()
|
||||||
|
session.pushersService().refreshPushers()
|
||||||
|
refreshBackgroundSyncPrefs()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bindEmailNotifications()
|
bindEmailNotifications()
|
||||||
refreshBackgroundSyncPrefs()
|
refreshBackgroundSyncPrefs()
|
||||||
|
|
||||||
|
@ -182,9 +201,9 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
pref.isChecked = isEnabled
|
pref.isChecked = isEnabled
|
||||||
pref.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
|
pref.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
pushManager.registerEmailForPush(emailPid.email)
|
pushersManager.registerEmailForPush(emailPid.email)
|
||||||
} else {
|
} else {
|
||||||
pushManager.unregisterEmailPusher(emailPid.email)
|
pushersManager.unregisterEmailPusher(emailPid.email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
category.addPreference(pref)
|
category.addPreference(pref)
|
||||||
|
@ -222,7 +241,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<VectorPreferenceCategory>(VectorPreferences.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY)?.let {
|
findPreference<VectorPreferenceCategory>(VectorPreferences.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY)?.let {
|
||||||
it.isVisible = !FcmHelper.isPushSupported()
|
it.isVisible = unifiedPushHelper.isBackgroundSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
val backgroundSyncEnabled = vectorPreferences.isBackgroundSyncEnabled()
|
val backgroundSyncEnabled = vectorPreferences.isBackgroundSyncEnabled()
|
||||||
|
@ -331,7 +350,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
|
|
||||||
private fun refreshPref() {
|
private fun refreshPref() {
|
||||||
// This pref may have change from troubleshoot pref fragment
|
// This pref may have change from troubleshoot pref fragment
|
||||||
if (!FcmHelper.isPushSupported()) {
|
if (unifiedPushHelper.isBackgroundSync()) {
|
||||||
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_START_ON_BOOT_PREFERENCE_KEY)
|
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_START_ON_BOOT_PREFERENCE_KEY)
|
||||||
?.isChecked = vectorPreferences.autoStartOnBoot()
|
?.isChecked = vectorPreferences.autoStartOnBoot()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.R
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.push.fcm.FcmHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TestAvailableUnifiedPushDistributors @Inject constructor(
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val fcmHelper: FcmHelper,
|
||||||
|
) : TroubleshootTest(R.string.settings_troubleshoot_test_distributors_title) {
|
||||||
|
|
||||||
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
|
val distributors = unifiedPushHelper.getExternalDistributors()
|
||||||
|
description = if (distributors.isEmpty()) {
|
||||||
|
stringProvider.getString(
|
||||||
|
if (fcmHelper.isFirebaseAvailable()) {
|
||||||
|
R.string.settings_troubleshoot_test_distributors_gplay
|
||||||
|
} else {
|
||||||
|
R.string.settings_troubleshoot_test_distributors_fdroid
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val quantity = distributors.size + 1
|
||||||
|
stringProvider.getQuantityString(R.plurals.settings_troubleshoot_test_distributors_many, quantity, quantity)
|
||||||
|
}
|
||||||
|
status = TestStatus.SUCCESS
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.R
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TestCurrentUnifiedPushDistributor @Inject constructor(
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_distributor_title) {
|
||||||
|
|
||||||
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
|
description = stringProvider.getString(
|
||||||
|
R.string.settings_troubleshoot_test_current_distributor,
|
||||||
|
unifiedPushHelper.getCurrentDistributorName()
|
||||||
|
)
|
||||||
|
status = TestStatus.SUCCESS
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.work.WorkInfo
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.core.pushers.PushersManager
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushStore
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import org.matrix.android.sdk.api.session.pushers.PusherState
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TestEndpointAsTokenRegistration @Inject constructor(
|
||||||
|
private val context: FragmentActivity,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val pushersManager: PushersManager,
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
|
) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) {
|
||||||
|
|
||||||
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
|
// Check if we have a registered pusher for this token
|
||||||
|
val endpoint = unifiedPushStore.getEndpointOrToken() ?: run {
|
||||||
|
status = TestStatus.FAILED
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val session = activeSessionHolder.getSafeActiveSession() ?: run {
|
||||||
|
status = TestStatus.FAILED
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val pushers = session.pushersService().getPushers().filter {
|
||||||
|
it.pushKey == endpoint && it.state == PusherState.REGISTERED
|
||||||
|
}
|
||||||
|
if (pushers.isEmpty()) {
|
||||||
|
description = stringProvider.getString(
|
||||||
|
R.string.settings_troubleshoot_test_endpoint_registration_failed,
|
||||||
|
stringProvider.getString(R.string.sas_error_unknown)
|
||||||
|
)
|
||||||
|
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) {
|
||||||
|
override fun doFix() {
|
||||||
|
unifiedPushHelper.reRegister(
|
||||||
|
context,
|
||||||
|
pushersManager
|
||||||
|
)
|
||||||
|
val workId = pushersManager.enqueueRegisterPusherWithFcmKey(endpoint)
|
||||||
|
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
|
||||||
|
if (workInfo != null) {
|
||||||
|
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
|
||||||
|
manager?.retry(activityResultLauncher)
|
||||||
|
} else if (workInfo.state == WorkInfo.State.FAILED) {
|
||||||
|
manager?.retry(activityResultLauncher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = TestStatus.FAILED
|
||||||
|
} else {
|
||||||
|
description = stringProvider.getString(R.string.settings_troubleshoot_test_endpoint_registration_success)
|
||||||
|
status = TestStatus.SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,19 +13,16 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.app.gplay.features.settings.troubleshoot
|
package im.vector.app.features.settings.troubleshoot
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.PushersManager
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
|
|
||||||
import im.vector.app.push.fcm.FcmHelper
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -34,28 +31,22 @@ import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Push by asking the Push Gateway to send a Push back
|
* Test Push by asking the Push Gateway to send a Push back.
|
||||||
*/
|
*/
|
||||||
class TestPushFromPushGateway @Inject constructor(
|
class TestPushFromPushGateway @Inject constructor(
|
||||||
private val context: FragmentActivity,
|
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val errorFormatter: ErrorFormatter,
|
private val errorFormatter: ErrorFormatter,
|
||||||
private val pushersManager: PushersManager,
|
private val pushersManager: PushersManager,
|
||||||
private val activeSessionHolder: ActiveSessionHolder
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
) :
|
) : TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
|
||||||
TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
|
|
||||||
|
|
||||||
private var action: Job? = null
|
private var action: Job? = null
|
||||||
private var pushReceived: Boolean = false
|
private var pushReceived: Boolean = false
|
||||||
|
|
||||||
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
pushReceived = false
|
pushReceived = false
|
||||||
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
|
|
||||||
status = TestStatus.FAILED
|
|
||||||
return
|
|
||||||
}
|
|
||||||
action = activeSessionHolder.getActiveSession().coroutineScope.launch {
|
action = activeSessionHolder.getActiveSession().coroutineScope.launch {
|
||||||
val result = runCatching { pushersManager.testPush(fcmToken) }
|
val result = runCatching { pushersManager.testPush() }
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
status = result
|
status = result
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.R
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TestUnifiedPushEndpoint @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
|
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_endpoint_title) {
|
||||||
|
|
||||||
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
|
val endpoint = unifiedPushHelper.getPrivacyFriendlyUpEndpoint()
|
||||||
|
if (endpoint != null) {
|
||||||
|
description = stringProvider.getString(R.string.settings_troubleshoot_test_current_endpoint_success, endpoint)
|
||||||
|
status = TestStatus.SUCCESS
|
||||||
|
} else {
|
||||||
|
description = stringProvider.getString(R.string.settings_troubleshoot_test_current_endpoint_failed)
|
||||||
|
status = TestStatus.FAILED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.R
|
||||||
|
import im.vector.app.core.pushers.UnifiedPushStore
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TestUnifiedPushGateway @Inject constructor(
|
||||||
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
|
private val stringProvider: StringProvider
|
||||||
|
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_gateway_title) {
|
||||||
|
|
||||||
|
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||||
|
description = stringProvider.getString(
|
||||||
|
R.string.settings_troubleshoot_test_current_gateway,
|
||||||
|
unifiedPushStore.getPushGateway()
|
||||||
|
)
|
||||||
|
status = TestStatus.SUCCESS
|
||||||
|
}
|
||||||
|
}
|
|
@ -855,6 +855,10 @@
|
||||||
<string name="settings_troubleshoot_test_token_registration_success">FCM token successfully registered to homeserver.</string>
|
<string name="settings_troubleshoot_test_token_registration_success">FCM token successfully registered to homeserver.</string>
|
||||||
<string name="settings_troubleshoot_test_token_registration_failed">Failed to register FCM token to homeserver:\n%1$s</string>
|
<string name="settings_troubleshoot_test_token_registration_failed">Failed to register FCM token to homeserver:\n%1$s</string>
|
||||||
|
|
||||||
|
<string name="settings_troubleshoot_test_endpoint_registration_title">Endpoint Registration</string>
|
||||||
|
<string name="settings_troubleshoot_test_endpoint_registration_success">Endpoint successfully registered to homeserver.</string>
|
||||||
|
<string name="settings_troubleshoot_test_endpoint_registration_failed">Failed to register endpoint token to homeserver:\n%1$s</string>
|
||||||
|
|
||||||
<string name="settings_troubleshoot_test_push_loop_title">Test Push</string>
|
<string name="settings_troubleshoot_test_push_loop_title">Test Push</string>
|
||||||
<string name="settings_troubleshoot_test_push_loop_waiting_for_push">The application is waiting for the PUSH</string>
|
<string name="settings_troubleshoot_test_push_loop_waiting_for_push">The application is waiting for the PUSH</string>
|
||||||
<string name="settings_troubleshoot_test_push_loop_success">The application is receiving PUSH</string>
|
<string name="settings_troubleshoot_test_push_loop_success">The application is receiving PUSH</string>
|
||||||
|
@ -1665,6 +1669,8 @@
|
||||||
|
|
||||||
<string name="settings_troubleshoot_test_token_registration_quick_fix">Register token</string>
|
<string name="settings_troubleshoot_test_token_registration_quick_fix">Register token</string>
|
||||||
|
|
||||||
|
<string name="settings_troubleshoot_test_endpoint_registration_quick_fix">Reset notification method</string>
|
||||||
|
|
||||||
<string name="send_suggestion">Make a suggestion</string>
|
<string name="send_suggestion">Make a suggestion</string>
|
||||||
<string name="send_suggestion_content">Please write your suggestion below.</string>
|
<string name="send_suggestion_content">Please write your suggestion below.</string>
|
||||||
<string name="send_suggestion_report_placeholder">Describe your suggestion here</string>
|
<string name="send_suggestion_report_placeholder">Describe your suggestion here</string>
|
||||||
|
@ -3063,4 +3069,23 @@
|
||||||
<!-- Screen sharing -->
|
<!-- Screen sharing -->
|
||||||
<string name="screen_sharing_notification_title">${app_name} Screen Sharing</string>
|
<string name="screen_sharing_notification_title">${app_name} Screen Sharing</string>
|
||||||
<string name="screen_sharing_notification_description">Screen sharing is in progress</string>
|
<string name="screen_sharing_notification_description">Screen sharing is in progress</string>
|
||||||
|
|
||||||
|
<string name="unifiedpush_getdistributors_dialog_title">Choose how to receive notifications</string>
|
||||||
|
<string name="unifiedpush_distributor_fcm_fallback">Google Services</string>
|
||||||
|
<string name="unifiedpush_distributor_background_sync">Background synchronization</string>
|
||||||
|
<string name="settings_notification_method">Notification method</string>
|
||||||
|
<string name="settings_troubleshoot_test_distributors_title">Available methods</string>
|
||||||
|
<string name="settings_troubleshoot_test_distributors_gplay">No other method than Google Play Service found.</string>
|
||||||
|
<string name="settings_troubleshoot_test_distributors_fdroid">No other method than background synchronization found.</string>
|
||||||
|
<plurals name="settings_troubleshoot_test_distributors_many">
|
||||||
|
<item quantity="one">Found %d method.</item>
|
||||||
|
<item quantity="other">Found %d methods.</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="settings_troubleshoot_test_current_distributor_title">Method</string>
|
||||||
|
<string name="settings_troubleshoot_test_current_distributor">Currently using %s.</string>
|
||||||
|
<string name="settings_troubleshoot_test_current_endpoint_title">Endpoint</string>
|
||||||
|
<string name="settings_troubleshoot_test_current_endpoint_success">Current endpoint: %s</string>
|
||||||
|
<string name="settings_troubleshoot_test_current_endpoint_failed">Cannot find the endpoint.</string>
|
||||||
|
<string name="settings_troubleshoot_test_current_gateway_title">Gateway</string>
|
||||||
|
<string name="settings_troubleshoot_test_current_gateway">Current gateway: %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -51,6 +51,12 @@
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/settings_notification_configuration">
|
android:title="@string/settings_notification_configuration">
|
||||||
|
|
||||||
|
<im.vector.app.core.preference.VectorPreference
|
||||||
|
android:dependency="SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"
|
||||||
|
android:key="SETTINGS_NOTIFICATION_METHOD_KEY"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/settings_notification_method" />
|
||||||
|
|
||||||
<!-- For API < 26 -->
|
<!-- For API < 26 -->
|
||||||
<im.vector.app.core.preference.VectorPreference
|
<im.vector.app.core.preference.VectorPreference
|
||||||
android:dialogTitle="@string/settings_notification_ringtone"
|
android:dialogTitle="@string/settings_notification_ringtone"
|
||||||
|
@ -121,4 +127,4 @@
|
||||||
android:key="SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY"
|
android:key="SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY"
|
||||||
android:title="@string/settings_notifications_targets" /-->
|
android:title="@string/settings_notifications_targets" /-->
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.core.pushers.model.PushData
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class PushParserTest {
|
||||||
|
private val validData = PushData(
|
||||||
|
eventId = "\$anEventId",
|
||||||
|
roomId = "!aRoomId:domain",
|
||||||
|
unread = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
private val emptyData = PushData(
|
||||||
|
eventId = null,
|
||||||
|
roomId = null,
|
||||||
|
unread = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test edge cases`() {
|
||||||
|
doAllEdgeTests(true)
|
||||||
|
doAllEdgeTests(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doAllEdgeTests(firebaseFormat: Boolean) {
|
||||||
|
val pushParser = PushParser()
|
||||||
|
// Empty string
|
||||||
|
pushParser.parseData("", firebaseFormat) shouldBe null
|
||||||
|
// Empty Json
|
||||||
|
pushParser.parseData("{}", firebaseFormat) shouldBeEqualTo emptyData
|
||||||
|
// Bad Json
|
||||||
|
pushParser.parseData("ABC", firebaseFormat) shouldBe null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test unified push format`() {
|
||||||
|
val pushParser = PushParser()
|
||||||
|
|
||||||
|
pushParser.parseData(UNIFIED_PUSH_DATA, false) shouldBeEqualTo validData
|
||||||
|
pushParser.parseData(UNIFIED_PUSH_DATA, true) shouldBeEqualTo emptyData
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test firebase push format`() {
|
||||||
|
val pushParser = PushParser()
|
||||||
|
|
||||||
|
pushParser.parseData(FIREBASE_PUSH_DATA, true) shouldBeEqualTo validData
|
||||||
|
pushParser.parseData(FIREBASE_PUSH_DATA, false) shouldBeEqualTo emptyData
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test empty roomId`() {
|
||||||
|
val pushParser = PushParser()
|
||||||
|
|
||||||
|
pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", ""), true) shouldBeEqualTo validData.copy(roomId = null)
|
||||||
|
pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", ""), false) shouldBeEqualTo validData.copy(roomId = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test invalid roomId`() {
|
||||||
|
val pushParser = PushParser()
|
||||||
|
|
||||||
|
pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), true) shouldBeEqualTo validData.copy(roomId = null)
|
||||||
|
pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), false) shouldBeEqualTo validData.copy(roomId = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test empty eventId`() {
|
||||||
|
val pushParser = PushParser()
|
||||||
|
|
||||||
|
pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", ""), true) shouldBeEqualTo validData.copy(eventId = null)
|
||||||
|
pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", ""), false) shouldBeEqualTo validData.copy(eventId = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test invalid eventId`() {
|
||||||
|
val pushParser = PushParser()
|
||||||
|
|
||||||
|
pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", "anEventId"), true) shouldBeEqualTo validData.copy(eventId = null)
|
||||||
|
pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", "anEventId"), false) shouldBeEqualTo validData.copy(eventId = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val UNIFIED_PUSH_DATA =
|
||||||
|
"{\"notification\":{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"counts\":{\"unread\":1},\"prio\":\"high\"}}"
|
||||||
|
private const val FIREBASE_PUSH_DATA =
|
||||||
|
"{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"unread\":\"1\",\"prio\":\"high\"}"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue