1
0
mirror of https://github.com/vector-im/element-android.git synced 2025-08-07 14:42:55 +03:00

Merge pull request #7217 from vector-im/feature/eric/msc3881

Implements MSC3881 (enabled and device_id fields for Pusher API)
This commit is contained in:
Eric Decanini
2022-10-10 12:37:47 -04:00
committed by GitHub
33 changed files with 635 additions and 39 deletions

1
changelog.d/7217.wip Normal file
View File

@@ -0,0 +1 @@
Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API

View File

@@ -1702,13 +1702,15 @@
<string name="settings_push_rules_no_rules">No push rules defined</string> <string name="settings_push_rules_no_rules">No push rules defined</string>
<string name="settings_push_gateway_no_pushers">No registered push gateways</string> <string name="settings_push_gateway_no_pushers">No registered push gateways</string>
<string name="push_gateway_item_app_id">app_id:</string> <string name="push_gateway_item_app_id">App ID:</string>
<string name="push_gateway_item_push_key">push_key:</string> <string name="push_gateway_item_push_key">Push Key:</string>
<string name="push_gateway_item_app_display_name">app_display_name:</string> <string name="push_gateway_item_app_display_name">App Display Name:</string>
<string name="push_gateway_item_device_name">session_name:</string> <string name="push_gateway_item_device_name">Session Display Name:</string>
<string name="push_gateway_item_device_id">Session ID:</string>
<string name="push_gateway_item_url">Url:</string> <string name="push_gateway_item_url">Url:</string>
<string name="push_gateway_item_format">Format:</string> <string name="push_gateway_item_format">Format:</string>
<string name="push_gateway_item_profile_tag">Profile tag:</string> <string name="push_gateway_item_profile_tag">Profile tag:</string>
<string name="push_gateway_item_enabled">Enabled:</string>
<string name="preference_voice_and_video">Voice &amp; Video</string> <string name="preference_voice_and_video">Voice &amp; Video</string>
<string name="preference_root_help_about">Help &amp; About</string> <string name="preference_root_help_about">Help &amp; About</string>

View File

@@ -58,6 +58,16 @@ data class HttpPusher(
*/ */
val url: String, val url: String,
/**
* Whether the pusher should actively create push notifications.
*/
val enabled: Boolean,
/**
* The device ID of the session that registered the pusher.
*/
val deviceId: String,
/** /**
* If true, the homeserver should add another pusher with the given pushkey and App ID in addition * If true, the homeserver should add another pusher with the given pushkey and App ID in addition
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers

View File

@@ -24,8 +24,9 @@ data class Pusher(
val profileTag: String? = null, val profileTag: String? = null,
val lang: String?, val lang: String?,
val data: PusherData, val data: PusherData,
val enabled: Boolean,
val state: PusherState val deviceId: String?,
val state: PusherState,
) { ) {
companion object { companion object {

View File

@@ -33,7 +33,9 @@ internal object PushersMapper {
profileTag = pushEntity.profileTag, profileTag = pushEntity.profileTag,
lang = pushEntity.lang, lang = pushEntity.lang,
data = PusherData(pushEntity.data?.url, pushEntity.data?.format), data = PusherData(pushEntity.data?.url, pushEntity.data?.format),
state = pushEntity.state enabled = pushEntity.enabled,
deviceId = pushEntity.deviceId,
state = pushEntity.state,
) )
} }
@@ -46,7 +48,9 @@ internal object PushersMapper {
deviceDisplayName = pusher.deviceDisplayName, deviceDisplayName = pusher.deviceDisplayName,
profileTag = pusher.profileTag, profileTag = pusher.profileTag,
lang = pusher.lang, lang = pusher.lang,
data = PusherDataEntity(pusher.data?.url, pusher.data?.format) data = PusherDataEntity(pusher.data?.url, pusher.data?.format),
enabled = pusher.enabled,
deviceId = pusher.deviceId,
) )
} }
} }

View File

@@ -18,15 +18,6 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.RealmObject import io.realm.RealmObject
import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.api.session.pushers.PusherState
// TODO
// at java.lang.Thread.run(Thread.java:764)
// Caused by: java.lang.IllegalArgumentException: 'value' is not a valid managed object.
// at io.realm.ProxyState.checkValidObject(ProxyState.java:213)
// at io.realm.im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy
// .realmSet$data(im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.java:413)
// at org.matrix.android.sdk.internal.database.model.PusherEntity.setData(PusherEntity.kt:16)
// at org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70)
// at io.realm.Realm.executeTransaction(Realm.java:1493)
internal open class PusherEntity( internal open class PusherEntity(
var pushKey: String = "", var pushKey: String = "",
var kind: String? = null, var kind: String? = null,
@@ -35,7 +26,9 @@ internal open class PusherEntity(
var deviceDisplayName: String? = null, var deviceDisplayName: String? = null,
var profileTag: String? = null, var profileTag: String? = null,
var lang: String? = null, var lang: String? = null,
var data: PusherDataEntity? = null var data: PusherDataEntity? = null,
var enabled: Boolean = true,
var deviceId: String? = null,
) : RealmObject() { ) : RealmObject() {
private var stateStr: String = PusherState.UNREGISTERED.name private var stateStr: String = PusherState.UNREGISTERED.name

View File

@@ -38,6 +38,7 @@ internal class DefaultAddPusherTask @Inject constructor(
private val requestExecutor: RequestExecutor, private val requestExecutor: RequestExecutor,
private val globalErrorReceiver: GlobalErrorReceiver private val globalErrorReceiver: GlobalErrorReceiver
) : AddPusherTask { ) : AddPusherTask {
override suspend fun execute(params: AddPusherTask.Params) { override suspend fun execute(params: AddPusherTask.Params) {
val pusher = params.pusher val pusher = params.pusher
try { try {
@@ -71,6 +72,8 @@ internal class DefaultAddPusherTask @Inject constructor(
echo.profileTag = pusher.profileTag echo.profileTag = pusher.profileTag
echo.data?.format = pusher.data?.format echo.data?.format = pusher.data?.format
echo.data?.url = pusher.data?.url echo.data?.url = pusher.data?.url
echo.enabled = pusher.enabled
echo.deviceId = pusher.deviceId
echo.state = PusherState.REGISTERED echo.state = PusherState.REGISTERED
} }
} }

View File

@@ -78,7 +78,9 @@ internal class DefaultPushersService @Inject constructor(
appDisplayName = appDisplayName, appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName, deviceDisplayName = deviceDisplayName,
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
append = append append = append,
enabled = enabled,
deviceId = deviceId,
) )
override suspend fun addEmailPusher( override suspend fun addEmailPusher(

View File

@@ -33,6 +33,8 @@ import java.security.InvalidParameterException
* "device_display_name": "Alice's Phone", * "device_display_name": "Alice's Phone",
* "profile_tag": "xyz", * "profile_tag": "xyz",
* "lang": "en-US", * "lang": "en-US",
* "enabled": true,
* "device_id": "abc123",
* "data": { * "data": {
* "url": "https://example.com/_matrix/push/v1/notify" * "url": "https://example.com/_matrix/push/v1/notify"
* } * }
@@ -112,7 +114,19 @@ internal data class JsonPusher(
* The default is false. * The default is false.
*/ */
@Json(name = "append") @Json(name = "append")
val append: Boolean? = false val append: Boolean? = false,
/**
* Whether the pusher should actively create push notifications.
*/
@Json(name = "org.matrix.msc3881.enabled")
val enabled: Boolean = true,
/**
* The device_id of the session that registered the pusher.
*/
@Json(name = "org.matrix.msc3881.device_id")
val deviceId: String? = null,
) { ) {
init { init {
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.mapper
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.test.fixtures.JsonPusherFixture.aJsonPusher
import org.matrix.android.sdk.test.fixtures.PusherEntityFixture.aPusherEntity
class PushersMapperTest {
@Test
fun `when mapping PusherEntity, then it is mapped into Pusher successfully`() {
val pusherEntity = aPusherEntity()
val mappedPusher = PushersMapper.map(pusherEntity)
mappedPusher.pushKey shouldBeEqualTo pusherEntity.pushKey
mappedPusher.kind shouldBeEqualTo pusherEntity.kind.orEmpty()
mappedPusher.appId shouldBeEqualTo pusherEntity.appId
mappedPusher.appDisplayName shouldBeEqualTo pusherEntity.appDisplayName
mappedPusher.deviceDisplayName shouldBeEqualTo pusherEntity.deviceDisplayName
mappedPusher.profileTag shouldBeEqualTo pusherEntity.profileTag
mappedPusher.lang shouldBeEqualTo pusherEntity.lang
mappedPusher.data.url shouldBeEqualTo pusherEntity.data?.url
mappedPusher.data.format shouldBeEqualTo pusherEntity.data?.format
mappedPusher.enabled shouldBeEqualTo pusherEntity.enabled
mappedPusher.deviceId shouldBeEqualTo pusherEntity.deviceId
mappedPusher.state shouldBeEqualTo pusherEntity.state
}
@Test
fun `when mapping JsonPusher, then it is mapped into Pusher successfully`() {
val jsonPusher = aJsonPusher()
val mappedPusherEntity = PushersMapper.map(jsonPusher)
mappedPusherEntity.pushKey shouldBeEqualTo jsonPusher.pushKey
mappedPusherEntity.kind shouldBeEqualTo jsonPusher.kind
mappedPusherEntity.appId shouldBeEqualTo jsonPusher.appId
mappedPusherEntity.appDisplayName shouldBeEqualTo jsonPusher.appDisplayName
mappedPusherEntity.deviceDisplayName shouldBeEqualTo jsonPusher.deviceDisplayName
mappedPusherEntity.profileTag shouldBeEqualTo jsonPusher.profileTag
mappedPusherEntity.lang shouldBeEqualTo jsonPusher.lang
mappedPusherEntity.data?.url shouldBeEqualTo jsonPusher.data?.url
mappedPusherEntity.data?.format shouldBeEqualTo jsonPusher.data?.format
mappedPusherEntity.enabled shouldBeEqualTo jsonPusher.enabled
mappedPusherEntity.deviceId shouldBeEqualTo jsonPusher.deviceId
}
}

View File

@@ -71,7 +71,7 @@ class DefaultAddPusherTaskTest {
} }
@Test @Test
fun `given a persisted pusher when adding Pusher then updates api and mutates persisted result with Registered state`() { fun `given a persisted pusher, when adding Pusher, then updates api and mutates persisted result with Registered state`() {
val realmResult = PusherEntity(appDisplayName = null) val realmResult = PusherEntity(appDisplayName = null)
monarchy.givenWhereReturns(result = realmResult) monarchy.givenWhereReturns(result = realmResult)
.givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey)
@@ -85,7 +85,7 @@ class DefaultAddPusherTaskTest {
} }
@Test @Test
fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows`() { fun `given a persisted push entity and SetPush API fails, when adding Pusher, then mutates persisted result with Failed registration state and rethrows`() {
val realmResult = PusherEntity() val realmResult = PusherEntity()
monarchy.givenWhereReturns(result = realmResult) monarchy.givenWhereReturns(result = realmResult)
.givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey)
@@ -99,7 +99,7 @@ class DefaultAddPusherTaskTest {
} }
@Test @Test
fun `given no persisted push entity and SetPush API fails when adding Pusher then rethrows error`() { fun `given no persisted push entity and SetPush API fails, when adding Pusher, then rethrows error`() {
monarchy.givenWhereReturns<PusherEntity>(result = null) monarchy.givenWhereReturns<PusherEntity>(result = null)
.givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey)
pushersAPI.givenSetPusherErrors(SocketException()) pushersAPI.givenSetPusherErrors(SocketException())

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.internal.session.pushers.JsonPusher
import org.matrix.android.sdk.internal.session.pushers.JsonPusherData
internal object JsonPusherFixture {
fun aJsonPusher(
pushKey: String = "",
kind: String? = null,
appId: String = "",
appDisplayName: String? = null,
deviceDisplayName: String? = null,
profileTag: String? = null,
lang: String? = null,
data: JsonPusherData? = null,
append: Boolean? = false,
enabled: Boolean = true,
deviceId: String? = null,
) = JsonPusher(
pushKey,
kind,
appId,
appDisplayName,
deviceDisplayName,
profileTag,
lang,
data,
append,
enabled,
deviceId,
)
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.internal.database.model.PusherDataEntity
import org.matrix.android.sdk.internal.database.model.PusherEntity
internal object PusherEntityFixture {
fun aPusherEntity(
pushKey: String = "",
kind: String? = null,
appId: String = "",
appDisplayName: String? = null,
deviceDisplayName: String? = null,
profileTag: String? = null,
lang: String? = null,
data: PusherDataEntity? = null,
enabled: Boolean = true,
deviceId: String? = null,
) = PusherEntity(
pushKey,
kind,
appId,
appDisplayName,
deviceDisplayName,
profileTag,
lang,
data,
enabled,
deviceId,
)
}

View File

@@ -23,6 +23,10 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.DefaultAppNameProvider
import im.vector.app.core.resources.DefaultLocaleProvider
import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.fdroid.service.FDroidGuardServiceStarter import im.vector.app.fdroid.service.FDroidGuardServiceStarter
import im.vector.app.features.home.NightlyProxy import im.vector.app.features.home.NightlyProxy
@@ -59,4 +63,10 @@ abstract class FlavorModule {
@Binds @Binds
abstract fun bindsFcmHelper(fcmHelper: FdroidFcmHelper): FcmHelper abstract fun bindsFcmHelper(fcmHelper: FdroidFcmHelper): FcmHelper
@Binds
abstract fun bindsLocaleProvider(localeProvider: DefaultLocaleProvider): LocaleProvider
@Binds
abstract fun bindsAppNameProvider(appNameProvider: DefaultAppNameProvider): AppNameProvider
} }

View File

@@ -23,6 +23,10 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import im.vector.app.GoogleFlavorLegals import im.vector.app.GoogleFlavorLegals
import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.DefaultAppNameProvider
import im.vector.app.core.resources.DefaultLocaleProvider
import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.features.home.NightlyProxy import im.vector.app.features.home.NightlyProxy
import im.vector.app.features.settings.legals.FlavorLegals import im.vector.app.features.settings.legals.FlavorLegals
@@ -46,6 +50,12 @@ abstract class FlavorModule {
@Binds @Binds
abstract fun bindsFcmHelper(fcmHelper: GoogleFcmHelper): FcmHelper abstract fun bindsFcmHelper(fcmHelper: GoogleFcmHelper): FcmHelper
@Binds
abstract fun bindsLocaleProvider(localeProvider: DefaultLocaleProvider): LocaleProvider
@Binds
abstract fun bindsAppNameProvider(appNameProvider: DefaultAppNameProvider): AppNameProvider
@Binds @Binds
abstract fun bindsFlavorLegals(legals: GoogleFlavorLegals): FlavorLegals abstract fun bindsFlavorLegals(legals: GoogleFlavorLegals): FlavorLegals
} }

View File

@@ -34,6 +34,8 @@ import im.vector.app.SpaceStateHandler
import im.vector.app.SpaceStateHandlerImpl import im.vector.app.SpaceStateHandlerImpl
import im.vector.app.config.Config import im.vector.app.config.Config
import im.vector.app.core.debug.FlipperProxy import im.vector.app.core.debug.FlipperProxy
import im.vector.app.core.device.DefaultGetDeviceInfoUseCase
import im.vector.app.core.device.GetDeviceInfoUseCase
import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
@@ -112,6 +114,9 @@ abstract class VectorBindModule {
@Binds @Binds
abstract fun bindSpaceStateHandler(spaceStateHandlerImpl: SpaceStateHandlerImpl): SpaceStateHandler abstract fun bindSpaceStateHandler(spaceStateHandlerImpl: SpaceStateHandlerImpl): SpaceStateHandler
@Binds
abstract fun bindGetDeviceInfoUseCase(getDeviceInfoUseCase: DefaultGetDeviceInfoUseCase): GetDeviceInfoUseCase
} }
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)

View File

@@ -0,0 +1,35 @@
/*
* 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.device
import im.vector.app.core.di.ActiveSessionHolder
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import javax.inject.Inject
interface GetDeviceInfoUseCase {
fun execute(): CryptoDeviceInfo
}
class DefaultGetDeviceInfoUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder
) : GetDeviceInfoUseCase {
override fun execute(): CryptoDeviceInfo {
return activeSessionHolder.getActiveSession().cryptoService().getMyDevice()
}
}

View File

@@ -17,6 +17,7 @@
package im.vector.app.core.pushers package im.vector.app.core.pushers
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.device.GetDeviceInfoUseCase
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.AppNameProvider import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.LocaleProvider
@@ -26,7 +27,7 @@ import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
private const val DEFAULT_PUSHER_FILE_TAG = "mobile" internal const val DEFAULT_PUSHER_FILE_TAG = "mobile"
class PushersManager @Inject constructor( class PushersManager @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper, private val unifiedPushHelper: UnifiedPushHelper,
@@ -34,6 +35,7 @@ class PushersManager @Inject constructor(
private val localeProvider: LocaleProvider, private val localeProvider: LocaleProvider,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider, private val appNameProvider: AppNameProvider,
private val getDeviceInfoUseCase: GetDeviceInfoUseCase,
) { ) {
suspend fun testPush() { suspend fun testPush() {
val currentSession = activeSessionHolder.getActiveSession() val currentSession = activeSessionHolder.getActiveSession()
@@ -63,15 +65,17 @@ class PushersManager @Inject constructor(
pushKey: String, pushKey: String,
gateway: String gateway: String
) = HttpPusher( ) = HttpPusher(
pushKey, pushkey = pushKey,
stringProvider.getString(R.string.pusher_app_id), appId = 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, lang = localeProvider.current().language,
appNameProvider.getAppName(), appDisplayName = appNameProvider.getAppName(),
activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", deviceDisplayName = getDeviceInfoUseCase.execute().displayName().orEmpty(),
gateway, url = gateway,
enabled = true,
deviceId = activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE",
append = false, append = false,
withEventIdOnly = true withEventIdOnly = true,
) )
suspend fun registerEmailForPush(email: String) { suspend fun registerEmailForPush(email: String) {

View File

@@ -21,9 +21,14 @@ import im.vector.app.core.utils.getApplicationLabel
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class AppNameProvider @Inject constructor(private val context: Context) { interface AppNameProvider {
fun getAppName(): String { fun getAppName(): String
}
class DefaultAppNameProvider @Inject constructor(private val context: Context) : AppNameProvider {
override fun getAppName(): String {
return try { return try {
val appPackageName = context.applicationContext.packageName val appPackageName = context.applicationContext.packageName
var appName = context.getApplicationLabel(appPackageName) var appName = context.getApplicationLabel(appPackageName)

View File

@@ -23,9 +23,14 @@ import androidx.core.os.ConfigurationCompat
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
class LocaleProvider @Inject constructor(private val resources: Resources) { interface LocaleProvider {
fun current(): Locale { fun current(): Locale
}
class DefaultLocaleProvider @Inject constructor(private val resources: Resources) : LocaleProvider {
override fun current(): Locale {
return ConfigurationCompat.getLocales(resources.configuration).get(0) ?: Locale.getDefault() return ConfigurationCompat.getLocales(resources.configuration).get(0) ?: Locale.getDefault()
} }
} }

View File

@@ -27,7 +27,7 @@ import androidx.core.view.marginEnd
import androidx.core.view.marginStart import androidx.core.view.marginStart
import androidx.core.view.marginTop import androidx.core.view.marginTop
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.DefaultLocaleProvider
import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale
class MessageBubbleContentLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : class MessageBubbleContentLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
@@ -53,7 +53,7 @@ class MessageBubbleContentLayout @JvmOverloads constructor(context: Context, att
textViewStub.setOnInflateListener(null) textViewStub.setOnInflateListener(null)
messageTextView = inflated.findViewById(R.id.messageTextView) messageTextView = inflated.findViewById(R.id.messageTextView)
} }
localeLayoutDirection = LocaleProvider(resources).getLayoutDirectionFromCurrentLocale() localeLayoutDirection = DefaultLocaleProvider(resources).getLayoutDirectionFromCurrentLocale()
} }
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

View File

@@ -33,7 +33,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.DefaultLocaleProvider
import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ViewMessageBubbleBinding import im.vector.app.databinding.ViewMessageBubbleBinding
@@ -67,7 +67,7 @@ class MessageBubbleView @JvmOverloads constructor(
override fun onFinishInflate() { override fun onFinishInflate() {
super.onFinishInflate() super.onFinishInflate()
views = ViewMessageBubbleBinding.bind(this) views = ViewMessageBubbleBinding.bind(this)
val currentLayoutDirection = LocaleProvider(resources).getLayoutDirectionFromCurrentLocale() val currentLayoutDirection = DefaultLocaleProvider(resources).getLayoutDirectionFromCurrentLocale()
val layoutDirectionToSet = if (isIncoming) { val layoutDirectionToSet = if (isIncoming) {
currentLayoutDirection currentLayoutDirection
} else { } else {

View File

@@ -50,6 +50,8 @@ abstract class PushGatewayItem : VectorEpoxyModel<PushGatewayItem.Holder>(R.layo
holder.format.setTextOrHide(pusher.data.format, hideWhenBlank = true, holder.formatTitle) holder.format.setTextOrHide(pusher.data.format, hideWhenBlank = true, holder.formatTitle)
holder.profileTag.setTextOrHide(pusher.profileTag, hideWhenBlank = true, holder.profileTagTitle) holder.profileTag.setTextOrHide(pusher.profileTag, hideWhenBlank = true, holder.profileTagTitle)
holder.deviceName.text = pusher.deviceDisplayName holder.deviceName.text = pusher.deviceDisplayName
holder.deviceId.text = pusher.deviceId ?: "null"
holder.enabled.text = pusher.enabled.toString()
holder.removeButton.setOnClickListener { holder.removeButton.setOnClickListener {
interactions.onRemovePushTapped(pusher) interactions.onRemovePushTapped(pusher)
} }
@@ -59,10 +61,12 @@ abstract class PushGatewayItem : VectorEpoxyModel<PushGatewayItem.Holder>(R.layo
val kind by bind<TextView>(R.id.pushGatewayKind) val kind by bind<TextView>(R.id.pushGatewayKind)
val pushKey by bind<TextView>(R.id.pushGatewayKeyValue) val pushKey by bind<TextView>(R.id.pushGatewayKeyValue)
val deviceName by bind<TextView>(R.id.pushGatewayDeviceNameValue) val deviceName by bind<TextView>(R.id.pushGatewayDeviceNameValue)
val deviceId by bind<TextView>(R.id.pushGatewayDeviceIdValue)
val formatTitle by bind<View>(R.id.pushGatewayFormat) val formatTitle by bind<View>(R.id.pushGatewayFormat)
val format by bind<TextView>(R.id.pushGatewayFormatValue) val format by bind<TextView>(R.id.pushGatewayFormatValue)
val profileTagTitle by bind<TextView>(R.id.pushGatewayProfileTag) val profileTagTitle by bind<TextView>(R.id.pushGatewayProfileTag)
val profileTag by bind<TextView>(R.id.pushGatewayProfileTagValue) val profileTag by bind<TextView>(R.id.pushGatewayProfileTagValue)
val enabled by bind<TextView>(R.id.pushGatewayEnabledValue)
val urlTitle by bind<View>(R.id.pushGatewayURL) val urlTitle by bind<View>(R.id.pushGatewayURL)
val url by bind<TextView>(R.id.pushGatewayURLValue) val url by bind<TextView>(R.id.pushGatewayURLValue)
val appName by bind<TextView>(R.id.pushGatewayAppNameValue) val appName by bind<TextView>(R.id.pushGatewayAppNameValue)

View File

@@ -83,6 +83,23 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
tools:text="Pixel 6" />
<TextView
android:id="@+id/pushGatewayDeviceId"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="@string/push_gateway_item_device_id"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayDeviceIdValue"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
tools:text="EBMDOLFJD" /> tools:text="EBMDOLFJD" />
<TextView <TextView
@@ -135,6 +152,23 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" /> android:layout_marginBottom="16dp" />
<TextView
android:id="@+id/pushGatewayEnabled"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="@string/push_gateway_item_enabled"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayEnabledValue"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
tools:text="true" />
<Button <Button
android:id="@+id/pushGatewayDeleteButton" android:id="@+id/pushGatewayDeleteButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View 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.core.device
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeCryptoService
import im.vector.app.test.fakes.FakeSession
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
class DefaultGetDeviceInfoUseCaseTest {
private val cryptoService = FakeCryptoService()
private val session = FakeSession(fakeCryptoService = cryptoService)
private val activeSessionHolder = FakeActiveSessionHolder(session)
private val getDeviceInfoUseCase = DefaultGetDeviceInfoUseCase(activeSessionHolder.instance)
@Test
fun `when execute, then get crypto device info`() {
val result = getDeviceInfoUseCase.execute()
result shouldBeEqualTo cryptoService.cryptoDeviceInfo
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.R
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeAppNameProvider
import im.vector.app.test.fakes.FakeGetDeviceInfoUseCase
import im.vector.app.test.fakes.FakeLocaleProvider
import im.vector.app.test.fakes.FakePushersService
import im.vector.app.test.fakes.FakeSession
import im.vector.app.test.fakes.FakeStringProvider
import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo
import io.mockk.mockk
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
import org.matrix.android.sdk.api.session.pushers.HttpPusher
import java.util.Locale
import kotlin.math.abs
class PushersManagerTest {
private val pushersService = FakePushersService()
private val session = FakeSession(fakePushersService = pushersService)
private val activeSessionHolder = FakeActiveSessionHolder(session)
private val stringProvider = FakeStringProvider()
private val localeProvider = FakeLocaleProvider()
private val appNameProvider = FakeAppNameProvider()
private val getDeviceInfoUseCase = FakeGetDeviceInfoUseCase()
private val pushersManager = PushersManager(
mockk(),
activeSessionHolder.instance,
localeProvider,
stringProvider.instance,
appNameProvider,
getDeviceInfoUseCase,
)
@Test
fun `when enqueueRegisterPusher, then HttpPusher created and enqueued`() {
val pushKey = "abc"
val gateway = "123"
val pusherAppId = "app-id"
val appName = "element"
val deviceDisplayName = "iPhone Lollipop"
stringProvider.given(R.string.pusher_app_id, pusherAppId)
localeProvider.givenCurrent(Locale.UK)
appNameProvider.givenAppName(appName)
getDeviceInfoUseCase.givenDeviceInfo(aCryptoDeviceInfo(unsigned = UnsignedDeviceInfo(deviceDisplayName)))
val expectedHttpPusher = HttpPusher(
pushkey = pushKey,
appId = pusherAppId,
profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(session.myUserId.hashCode()),
lang = Locale.UK.language,
appDisplayName = appName,
deviceDisplayName = deviceDisplayName,
url = gateway,
enabled = true,
deviceId = session.sessionParams.deviceId!!,
append = false,
withEventIdOnly = true,
)
pushersManager.enqueueRegisterPusher(pushKey, gateway)
val httpPusher = pushersService.verifyEnqueueAddHttpPusher()
httpPusher shouldBeEqualTo expectedHttpPusher
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.test.fakes
import im.vector.app.core.resources.AppNameProvider
import io.mockk.every
import io.mockk.mockk
class FakeAppNameProvider : AppNameProvider by mockk() {
fun givenAppName(appName: String) {
every { getAppName() } returns appName
}
}

View File

@@ -17,6 +17,7 @@
package im.vector.app.test.fakes package im.vector.app.test.fakes
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.slot import io.mockk.slot
@@ -35,6 +36,7 @@ class FakeCryptoService(
var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>() var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>()
var cryptoDeviceInfoWithIdLiveData: MutableLiveData<Optional<CryptoDeviceInfo>> = MutableLiveData() var cryptoDeviceInfoWithIdLiveData: MutableLiveData<Optional<CryptoDeviceInfo>> = MutableLiveData()
var myDevicesInfoWithIdLiveData: MutableLiveData<Optional<DeviceInfo>> = MutableLiveData() var myDevicesInfoWithIdLiveData: MutableLiveData<Optional<DeviceInfo>> = MutableLiveData()
var cryptoDeviceInfo = aCryptoDeviceInfo()
override fun crossSigningService() = fakeCrossSigningService override fun crossSigningService() = fakeCrossSigningService
@@ -81,4 +83,6 @@ class FakeCryptoService(
thirdArg<MatrixCallback<Unit>>().onFailure(error) thirdArg<MatrixCallback<Unit>>().onFailure(error)
} }
} }
override fun getMyDevice() = cryptoDeviceInfo
} }

View File

@@ -0,0 +1,29 @@
/*
* 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.test.fakes
import im.vector.app.core.device.GetDeviceInfoUseCase
import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
class FakeGetDeviceInfoUseCase : GetDeviceInfoUseCase by mockk() {
fun givenDeviceInfo(cryptoDeviceInfo: CryptoDeviceInfo) {
every { execute() } returns cryptoDeviceInfo
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.test.fakes
import im.vector.app.core.resources.LocaleProvider
import io.mockk.every
import io.mockk.mockk
import java.util.Locale
class FakeLocaleProvider : LocaleProvider by mockk() {
fun givenCurrent(locale: Locale) {
every { current() } returns locale
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.test.fakes
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import org.matrix.android.sdk.api.session.pushers.HttpPusher
import org.matrix.android.sdk.api.session.pushers.PushersService
class FakePushersService : PushersService by mockk(relaxed = true) {
fun verifyEnqueueAddHttpPusher(): HttpPusher {
val httpPusherSlot = slot<HttpPusher>()
verify { enqueueAddHttpPusher(capture(httpPusherSlot)) }
return httpPusherSlot.captured
}
}

View File

@@ -41,6 +41,7 @@ class FakeSession(
val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(),
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(),
val fakeRoomService: FakeRoomService = FakeRoomService(), val fakeRoomService: FakeRoomService = FakeRoomService(),
val fakePushersService: FakePushersService = FakePushersService(),
private val fakeEventService: FakeEventService = FakeEventService(), private val fakeEventService: FakeEventService = FakeEventService(),
) : Session by mockk(relaxed = true) { ) : Session by mockk(relaxed = true) {
@@ -58,6 +59,7 @@ class FakeSession(
override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun sharedSecretStorageService() = fakeSharedSecretStorageService
override fun roomService() = fakeRoomService override fun roomService() = fakeRoomService
override fun eventService() = fakeEventService override fun eventService() = fakeEventService
override fun pushersService() = fakePushersService
fun givenVectorStore(vectorSessionStore: VectorSessionStore) { fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
coEvery { coEvery {

View File

@@ -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.test.fixtures
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
object CryptoDeviceInfoFixture {
fun aCryptoDeviceInfo(
deviceId: String = "",
userId: String = "",
algorithms: List<String>? = null,
keys: Map<String, String>? = null,
signatures: Map<String, Map<String, String>>? = null,
unsigned: UnsignedDeviceInfo? = null,
trustLevel: DeviceTrustLevel? = null,
isBlocked: Boolean = false,
firstTimeSeenLocalTs: Long? = null,
) = CryptoDeviceInfo(
deviceId,
userId,
algorithms,
keys,
signatures,
unsigned,
trustLevel,
isBlocked,
firstTimeSeenLocalTs,
)
}