Get real push rules from server and evaluate them

This commit is contained in:
Valere
2019-06-21 18:26:35 +02:00
committed by Benoit Marty
parent 2e417a9143
commit 0584fc3666
49 changed files with 1535 additions and 274 deletions

View File

@ -43,51 +43,53 @@ class Action(val type: Type) {
var stringValue: String? = null
var boolValue: Boolean? = null
}
fun PushRule.domainActions(): List<Action>? {
val actions = ArrayList<Action>()
this.actions.forEach { actionStrOrObj ->
if (actionStrOrObj is String) {
val action = when (actionStrOrObj) {
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
else -> {
Timber.w("Unsupported action type ${actionStrOrObj}")
null
}
}?.let {
actions.add(it)
}
} else if (actionStrOrObj is Map<*, *>) {
val tweakAction = actionStrOrObj["set_tweak"] as? String
when (tweakAction) {
"sound" -> {
(actionStrOrObj["value"] as? String)?.let { stringValue ->
Action(Action.Type.SET_TWEAK).also {
it.tweak_action = "sound"
it.stringValue = stringValue
actions.add(it)
companion object {
fun mapFrom(pushRule: PushRule): List<Action>? {
val actions = ArrayList<Action>()
pushRule.actions.forEach { actionStrOrObj ->
if (actionStrOrObj is String) {
when (actionStrOrObj) {
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
else -> {
Timber.w("Unsupported action type ${actionStrOrObj}")
null
}
}?.let {
actions.add(it)
}
} else if (actionStrOrObj is Map<*, *>) {
val tweakAction = actionStrOrObj["set_tweak"] as? String
when (tweakAction) {
"sound" -> {
(actionStrOrObj["value"] as? String)?.let { stringValue ->
Action(Action.Type.SET_TWEAK).also {
it.tweak_action = "sound"
it.stringValue = stringValue
actions.add(it)
}
}
}
"highlight" -> {
(actionStrOrObj["value"] as? Boolean)?.let { boolValue ->
Action(Action.Type.SET_TWEAK).also {
it.tweak_action = "highlight"
it.boolValue = boolValue
actions.add(it)
}
}
}
else -> {
Timber.w("Unsupported action type ${actionStrOrObj}")
}
}
}
"highlight" -> {
(actionStrOrObj["value"] as? Boolean)?.let { boolValue ->
Action(Action.Type.SET_TWEAK).also {
it.tweak_action = "highlight"
it.boolValue = boolValue
actions.add(it)
}
}
}
else -> {
} else {
Timber.w("Unsupported action type ${actionStrOrObj}")
return null
}
}
} else {
Timber.w("Unsupported action type ${actionStrOrObj}")
return null
return if (actions.isEmpty()) null else actions
}
}
return if (actions.isEmpty()) null else actions
}
}

View File

@ -15,25 +15,23 @@
*/
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
abstract class Condition(val kind: Kind) {
enum class Kind(val value: String) {
EVENT_MATCH("event_match"),
CONTAINS_DISPLAY_NAME("contains_display_name"),
ROOM_MEMBER_COUNT("room_member_count"),
SENDER_NOTIFICATION_PERMISSION("sender_notification_permission"),
event_match("event_match"),
contains_display_name("contains_display_name"),
room_member_count("room_member_count"),
sender_notification_permission("sender_notification_permission"),
UNRECOGNIZE("");
companion object {
fun fromString(value: String): Kind {
return when (value) {
"event_match" -> EVENT_MATCH
"contains_display_name" -> CONTAINS_DISPLAY_NAME
"room_member_count" -> ROOM_MEMBER_COUNT
"sender_notification_permission" -> SENDER_NOTIFICATION_PERMISSION
"event_match" -> event_match
"contains_display_name" -> contains_display_name
"room_member_count" -> room_member_count
"sender_notification_permission" -> sender_notification_permission
else -> UNRECOGNIZE
}
}
@ -42,10 +40,9 @@ abstract class Condition(val kind: Kind) {
}
abstract fun isSatisfied(event: Event): Boolean
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
companion object {
//TODO factory methods?
open fun technicalDescription(): String {
return "Kind: $kind"
}
}

View File

@ -15,11 +15,14 @@
*/
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.pushrules.rest.PushRule
/**
* Acts like a visitor on Conditions.
* This class as all required context needed to evaluate rules
*/
interface ConditionResolver {
interface PushRulesProvider {
fun getOrderedPushrules(): List<PushRule>
fun onRulesUpdate(newRules: List<PushRule>)
fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2019 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.matrix.android.api.pushrules
import android.text.TextUtils
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import timber.log.Timber
import java.util.regex.Pattern
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveContainsDisplayNameCondition(this)
}
override fun technicalDescription(): String {
return "User is mentioned"
}
fun isSatisfied(event: Event, displayName: String): Boolean {
//TODO the spec says:
// Matches any message whose content is unencrypted and contains the user's current display name
var message = when (event.type) {
EventType.MESSAGE -> {
event.content.toModel<MessageContent>()
}
// EventType.ENCRYPTED -> {
// event.root.getClearContent()?.toModel<MessageContent>()
// }
else -> null
} ?: return false
return caseInsensitiveFind(displayName, message.body)
}
companion object {
/**
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
*
* @param subString the string to search for
* @param longString the string to search in
* @return whether a match was found
*/
fun caseInsensitiveFind(subString: String, longString: String): Boolean {
// add sanity checks
if (TextUtils.isEmpty(subString) || TextUtils.isEmpty(longString)) {
return false
}
var res = false
try {
val pattern = Pattern.compile("(\\W|^)" + Pattern.quote(subString) + "(\\W|$)", Pattern.CASE_INSENSITIVE)
res = pattern.matcher(longString).find()
} catch (e: Exception) {
Timber.e(e, "## caseInsensitiveFind() : failed")
}
return res
}
}
}

View File

@ -19,9 +19,18 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.di.MoshiProvider
import timber.log.Timber
class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.EVENT_MATCH) {
class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.event_match) {
override fun isSatisfied(event: Event): Boolean {
override fun isSatisfied(conditionResolver: ConditionResolver) : Boolean {
return conditionResolver.resolveEventMatchCondition(this)
}
override fun technicalDescription(): String {
return "'$key' Matches '$pattern'"
}
fun isSatisfied(event: Event): Boolean {
//TODO encrypted events?
val rawJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJsonValue(event) as? Map<*, *>
?: return false

View File

@ -15,12 +15,19 @@
*/
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event
interface PushRuleService {
/**
* Fetch the push rules from the server
*/
fun fetchPushRules(scope: String = "global")
//TODO get push rule set
fun getPushrules(scope: String = "global"): List<PushRule>
//TODO update rule
@ -28,6 +35,8 @@ interface PushRuleService {
fun removePushRuleListener(listener: PushRuleListener)
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
interface PushRuleListener {
fun onMatchRule(event: Event, actions: List<Action>)
fun batchFinish()

View File

@ -0,0 +1,71 @@
/*
* Copyright 2019 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.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.RoomService
import timber.log.Timber
import java.util.regex.Pattern
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$")
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) {
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveRoomMemberCountCondition(this)
}
override fun technicalDescription(): String {
return "Room member count is $`is`"
}
fun isSatisfied(event: Event, session: RoomService?): Boolean {
// sanity check^
val roomId = event.roomId ?: return false
val room = session?.getRoom(roomId) ?: return false
// Parse the is field into prefix and number the first time
val (prefix, count) = parseIsField() ?: return false
val numMembers = room.getNumberOfJoinedMembers()
return when (prefix) {
"<" -> numMembers < count
">" -> numMembers > count
"<=" -> numMembers <= count
">=" -> numMembers >= count
else -> numMembers == count
}
}
/**
* Parse the is field to extract meaningful information.
*/
private fun parseIsField(): Pair<String?, Int>? {
try {
val match = regex.matcher(`is`)
if (match.find()) {
val prefix = match.group(1)
val count = match.group(2).toInt()
return prefix to count
}
} catch (t: Throwable) {
Timber.d(t)
}
return null
}
}

View File

@ -0,0 +1,36 @@
package im.vector.matrix.android.api.pushrules
/*
* Copyright 2019 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.
*/
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.PowerLevels
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveSenderNotificationPermissionCondition(this)
}
override fun technicalDescription(): String {
return "User power level <$key>"
}
fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean {
return event.sender != null && powerLevels.getUserPowerLevel(event.sender) >= powerLevels.notificationLevel(key)
}
}

View File

@ -17,8 +17,7 @@ package im.vector.matrix.android.api.pushrules.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.pushrules.Condition
import im.vector.matrix.android.api.pushrules.EventMatchCondition
import im.vector.matrix.android.api.pushrules.*
import timber.log.Timber
@JsonClass(generateAdapter = true)
@ -37,7 +36,7 @@ data class PushCondition(
fun asExecutableCondition(): Condition? {
return when (Condition.Kind.fromString(this.kind)) {
Condition.Kind.EVENT_MATCH -> {
Condition.Kind.event_match -> {
if (this.key != null && this.pattern != null) {
EventMatchCondition(key, pattern)
} else {
@ -45,10 +44,24 @@ data class PushCondition(
null
}
}
Condition.Kind.CONTAINS_DISPLAY_NAME -> TODO()
Condition.Kind.ROOM_MEMBER_COUNT -> TODO()
Condition.Kind.SENDER_NOTIFICATION_PERMISSION -> TODO()
Condition.Kind.UNRECOGNIZE -> null
Condition.Kind.contains_display_name -> {
ContainsDisplayNameCondition()
}
Condition.Kind.room_member_count -> {
if (this.iz.isNullOrBlank()) {
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
null
} else {
RoomMemberCountCondition(this.iz)
}
}
Condition.Kind.sender_notification_permission -> {
this.key?.let { SenderNotificationPermissionCondition(it) }
}
Condition.Kind.UNRECOGNIZE -> {
Timber.e("Unknwon kind $kind")
null
}
}
}
}

View File

@ -25,7 +25,7 @@ data class PushRule(
//Required. The domainActions to perform when this rule is matched.
val actions: List<Any>,
//Required. Whether this is a default rule, or has been set explicitly.
val default: Boolean,
val default: Boolean? = false,
//Required. Whether the push rule is enabled or not.
val enabled: Boolean,
//Required. The ID of this rule.

View File

@ -22,7 +22,7 @@ import im.vector.matrix.android.api.pushrules.rest.Ruleset
* All push rulesets for a user.
*/
@JsonClass(generateAdapter = true)
data class PushruleResponse(
data class PushrulesResponse(
//Global rules, account level applying to all devices
val global: Ruleset,
//Device specific rules, apply only to current device

View File

@ -49,6 +49,8 @@ interface MembershipService {
*/
fun getRoomMemberIdsLive(): LiveData<List<String>>
fun getNumberOfJoinedMembers() : Int
/**
* Invite a user in the room
*/

View File

@ -0,0 +1,26 @@
package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.pushrules.rest.PushCondition
import im.vector.matrix.android.internal.database.model.PushConditionEntity
internal object PushConditionMapper {
fun map(entity: PushConditionEntity): PushCondition {
return PushCondition(
kind = entity.kind,
iz = entity.iz,
key = entity.key,
pattern = entity.pattern
)
}
fun map(domain: PushCondition): PushConditionEntity {
return PushConditionEntity(
kind = domain.kind,
iz = domain.iz,
key = domain.key,
pattern = domain.pattern
)
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright 2019 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.matrix.android.internal.database.mapper
import com.squareup.moshi.Types
import im.vector.matrix.android.api.pushrules.Condition
import im.vector.matrix.android.api.pushrules.rest.PushCondition
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.di.MoshiProvider
import io.realm.RealmList
import timber.log.Timber
internal object PushRulesMapper {
private val moshiActionsAdapter = MoshiProvider.providesMoshi().adapter<List<Any>>(Types.newParameterizedType(List::class.java, Any::class.java))
// private val listOfAnyAdapter: JsonAdapter<List<Any>> =
// moshi.adapter<List<Any>>(Types.newParameterizedType(List::class.java, Any::class.java), kotlin.collections.emptySet(), "actions")
fun mapContentRule(pushrule: PushRuleEntity): PushRule {
return PushRule(
actions = fromActionStr(pushrule.actionsStr),
default = pushrule.default,
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.event_match.name, "content.body", pushrule.pattern)
)
)
}
private fun fromActionStr(actionsStr: String?): List<Any> {
try {
return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList()
} catch (e: Throwable) {
Timber.e(e, "## failed to map push rule actions <$actionsStr>")
return emptyList()
}
}
fun mapRoomRule(pushrule: PushRuleEntity): PushRule {
return PushRule(
actions = fromActionStr(pushrule.actionsStr),
default = pushrule.default,
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.event_match.name, "room_id", pushrule.ruleId)
)
)
}
fun mapSenderRule(pushrule: PushRuleEntity): PushRule {
return PushRule(
actions = fromActionStr(pushrule.actionsStr),
default = pushrule.default,
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.event_match.name, "user_id", pushrule.ruleId)
)
)
}
fun map(pushrule: PushRuleEntity): PushRule {
return PushRule(
actions = fromActionStr(pushrule.actionsStr),
default = pushrule.default,
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = pushrule.conditions?.map { PushConditionMapper.map(it) }
)
}
fun map(pushRule: PushRule): PushRuleEntity {
return PushRuleEntity(
actionsStr = moshiActionsAdapter.toJson(pushRule.actions),
default = pushRule.default ?: false,
enabled = pushRule.enabled,
ruleId = pushRule.ruleId,
pattern = pushRule.pattern,
conditions = pushRule.conditions?.let {
RealmList(*pushRule.conditions.map { PushConditionMapper.map(it) }.toTypedArray())
} ?: RealmList()
)
}
}

View File

@ -24,7 +24,7 @@ internal open class PushRulesEntity(
@Index var userId: String = "",
var scope: String = "",
var rulesetKey: String = "",
var pushRules: RealmList<PushRulesEntity> = RealmList()
var pushRules: RealmList<PushRuleEntity> = RealmList()
) : RealmObject() {
companion object
}

View File

@ -15,8 +15,10 @@
*/
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
@ -29,5 +31,11 @@ internal fun PusherEntity.Companion.where(realm: Realm, userId: String, pushKey:
equalTo(PusherEntityFields.PUSH_KEY, pushKey)
}
}
}
internal fun PushRulesEntity.Companion.where(realm: Realm, userId: String, scope: String, rulesetKey: String) : RealmQuery<PushRulesEntity> {
return realm.where<PushRulesEntity>()
.equalTo(PushRulesEntityFields.USER_ID,userId)
.equalTo(PushRulesEntityFields.SCOPE,scope)
.equalTo(PushRulesEntityFields.RULESET_KEY,rulesetKey)
}

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
@ -82,6 +83,7 @@ import java.util.*
internal class DefaultSession(override val sessionParams: SessionParams) : Session, MatrixKoinComponent {
companion object {
const val SCOPE: String = "session"
}
@ -497,4 +499,13 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
return pushersService.livePushers()
}
override fun getPushrules(scope: String): List<PushRule> {
return pushRuleService.getPushrules(scope)
}
override fun fetchPushRules(scope: String) {
pushRuleService.fetchPushRules(scope)
}
}

View File

@ -20,7 +20,6 @@ import android.content.Context
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.PushRulesProvider
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.pushers.PushersService
@ -38,11 +37,11 @@ import im.vector.matrix.android.internal.session.filter.*
import im.vector.matrix.android.internal.session.group.DefaultGroupService
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.notification.BingRuleWatcher
import im.vector.matrix.android.internal.session.notification.MockPushRuleProvider
import im.vector.matrix.android.internal.session.notification.PushRulesManager
import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService
import im.vector.matrix.android.internal.session.pushers.*
import im.vector.matrix.android.internal.session.pushers.DefaultGetPusherTask
import im.vector.matrix.android.internal.session.pushers.DefaultPusherService
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
import im.vector.matrix.android.internal.session.pushers.GetPushersTask
import im.vector.matrix.android.internal.session.pushers.PushersAPI
import im.vector.matrix.android.internal.session.room.*
@ -175,18 +174,25 @@ internal class SessionModule(private val sessionParams: SessionParams) {
}
scope(DefaultSession.SCOPE) {
MockPushRuleProvider() as PushRulesProvider
val retrofit: Retrofit = get()
retrofit.create(PushrulesApi::class.java)
}
scope(DefaultSession.SCOPE) {
get<PushRulesManager>() as PushRuleService
}
scope(DefaultSession.SCOPE) {
PushRulesManager(get(), get())
get<DefaultPushRuleService>() as PushRuleService
}
scope(DefaultSession.SCOPE) {
BingRuleWatcher(get(), get(), get(), get())
DefaultPushRuleService(get(), get(), get(), get())
}
scope(DefaultSession.SCOPE) {
DefaultGetPushrulesTask(get()) as GetPushRulesTask
}
scope(DefaultSession.SCOPE) {
BingRuleWatcher(get(), get())
}
scope(DefaultSession.SCOPE) {

View File

@ -16,19 +16,19 @@
package im.vector.matrix.android.internal.session.notification
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver
import timber.log.Timber
internal class BingRuleWatcher(monarchy: Monarchy,
private val credentials: Credentials,
private val taskExecutor: TaskExecutor,
private val pushRulesManager: PushRulesManager) :
private val defaultPushRuleService: DefaultPushRuleService) :
RealmLiveEntityObserver<EventEntity>(monarchy) {
override val query = Monarchy.Query<EventEntity> {
@ -41,9 +41,33 @@ internal class BingRuleWatcher(monarchy: Monarchy,
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
//TODO task
inserted.map { it.asDomain() }.let {
pushRulesManager.processEvents(it)
val rules = defaultPushRuleService.getPushrules("global")
inserted.map { it.asDomain() }.let { events ->
events.forEach { event ->
fulfilledBingRule(event, rules)?.let {
Timber.v("Rule $it match for event ${event.eventId}")
defaultPushRuleService.dispatchBing(event, it)
}
}
}
defaultPushRuleService.dispatchFinish()
}
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
val conditionResolver = DefaultConditionResolver(event)
rules.filter { it.enabled }.forEach { rule ->
val isFullfilled = rule.conditions?.map {
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false
}?.fold(true/*A rule with no conditions always matches*/, { acc, next ->
//All conditions must hold true for an event in order to apply the action for the event.
acc && next
}) ?: false
if (isFullfilled) {
return rule
}
}
return null
}

View File

@ -0,0 +1,188 @@
/*
* Copyright 2019 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.matrix.android.internal.session.notification
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
internal class DefaultPushRuleService(
private val sessionParams: SessionParams,
private val pushRulesTask: GetPushRulesTask,
private val taskExecutor: TaskExecutor,
private val monarchy: Monarchy
) : PushRuleService {
private var listeners = ArrayList<PushRuleService.PushRuleListener>()
override fun fetchPushRules(scope: String) {
pushRulesTask
.configureWith(Unit)
.dispatchTo(object : MatrixCallback<PushrulesResponse> {
override fun onSuccess(data: PushrulesResponse) {
monarchy.runTransactionSync { realm ->
//clear existings?
//TODO
realm.where(PushRulesEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm()
var content = PushRulesEntity(sessionParams.credentials.userId, scope, "content")
data.global.content?.forEach { rule ->
PushRulesMapper.map(rule).also {
content.pushRules.add(it)
}
}
realm.insertOrUpdate(content)
var override = PushRulesEntity(sessionParams.credentials.userId, scope, "override")
data.global.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)
var rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room")
data.global.room?.forEach { rule ->
PushRulesMapper.map(rule).also {
rooms.pushRules.add(it)
}
}
realm.insertOrUpdate(rooms)
var senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender")
data.global.sender?.forEach { rule ->
PushRulesMapper.map(rule).also {
senders.pushRules.add(it)
}
}
realm.insertOrUpdate(senders)
var underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride")
data.global.underride?.forEach { rule ->
PushRulesMapper.map(rule).also {
underrides.pushRules.add(it)
}
}
realm.insertOrUpdate(underrides)
}
}
})
.executeBy(taskExecutor)
}
override fun getPushrules(scope: String): List<PushRule> {
var contentRules: List<PushRule> = emptyList()
monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "content").findFirst()?.let { re ->
contentRules = re.pushRules.map { PushRulesMapper.mapContentRule(it) }
}
}
var overrideRules: List<PushRule> = emptyList()
monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "override").findFirst()?.let { re ->
overrideRules = re.pushRules.map { PushRulesMapper.map(it) }
}
}
var roomRules: List<PushRule> = emptyList()
monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "room").findFirst()?.let { re ->
roomRules = re.pushRules.map { PushRulesMapper.mapRoomRule(it) }
}
}
var senderRules: List<PushRule> = emptyList()
monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "sender").findFirst()?.let { re ->
senderRules = re.pushRules.map { PushRulesMapper.mapSenderRule(it) }
}
}
var underrideRules: List<PushRule> = emptyList()
monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "underride").findFirst()?.let { re ->
underrideRules = re.pushRules.map { PushRulesMapper.map(it) }
}
}
return contentRules + overrideRules + roomRules + senderRules + underrideRules
}
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
listeners.remove(listener)
}
override fun addPushRuleListener(listener: PushRuleService.PushRuleListener) {
if (!listeners.contains(listener))
listeners.add(listener)
}
// fun processEvents(events: List<Event>) {
// var hasDoneSomething = false
// events.forEach { event ->
// fulfilledBingRule(event)?.let {
// hasDoneSomething = true
// dispatchBing(event, it)
// }
// }
// if (hasDoneSomething)
// dispatchFinish()
// }
fun dispatchBing(event: Event, rule: PushRule) {
try {
listeners.forEach {
it.onMatchRule(event, Action.mapFrom(rule) ?: emptyList())
}
} catch (e: Throwable) {
}
}
fun dispatchFinish() {
try {
listeners.forEach {
it.batchFinish()
}
} catch (e: Throwable) {
}
}
}

View File

@ -1,41 +0,0 @@
package im.vector.matrix.android.internal.session.notification
import im.vector.matrix.android.api.pushrules.PushRulesProvider
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.di.MoshiProvider
class MockPushRuleProvider : PushRulesProvider {
override fun getOrderedPushrules(): List<PushRule> {
val raw = """
{
"actions": [
"notify",
{
"set_tweak": "highlight",
"value": false
}
],
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.message"
}
],
"default": true,
"enabled": true,
"rule_id": ".m.rule.message"
}
""".trimIndent()
val pushRule = MoshiProvider.providesMoshi().adapter<PushRule>(PushRule::class.java).fromJson(raw)
return listOf<PushRule>(
pushRule!!
)
}
override fun onRulesUpdate(newRules: List<PushRule>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright 2019 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.matrix.android.internal.session.notification
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.PushRulesProvider
import im.vector.matrix.android.api.pushrules.domainActions
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event
internal class PushRulesManager(
private val sessionParams: SessionParams,
private val pushRulesProvider: PushRulesProvider) : PushRuleService {
private var listeners = ArrayList<PushRuleService.PushRuleListener>()
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
listeners.remove(listener)
}
override fun addPushRuleListener(listener: PushRuleService.PushRuleListener) {
if (!listeners.contains(listener))
listeners.add(listener)
}
fun processEvents(events: List<Event>) {
var hasDoneSomething = false
events.forEach { event ->
fulfilledBingRule(event)?.let {
hasDoneSomething = true
dispatchBing(event, it)
}
}
if (hasDoneSomething)
dispatchFinish()
}
fun dispatchBing(event: Event, rule: PushRule) {
try {
listeners.forEach {
it.onMatchRule(event, rule.domainActions() ?: emptyList())
}
} catch (e: Throwable) {
}
}
fun dispatchFinish() {
try {
listeners.forEach {
it.batchFinish()
}
} catch (e: Throwable) {
}
}
fun fulfilledBingRule(event: Event): PushRule? {
pushRulesProvider.getOrderedPushrules().forEach { rule ->
rule.conditions?.mapNotNull { it.asExecutableCondition() }?.forEach {
if (it.isSatisfied(event)) return rule
}
}
return null
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2019 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.matrix.android.internal.session.pushers
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.*
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import org.koin.standalone.inject
import timber.log.Timber
internal class DefaultConditionResolver(val event: Event) : ConditionResolver, MatrixKoinComponent {
private val roomService by inject<RoomService>()
private val sessionParams by inject<SessionParams>()
override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean {
return eventMatchCondition.isSatisfied(event)
}
override fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean {
return roomMemberCountCondition.isSatisfied(event, roomService)
}
override fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean {
// val roomId = event.roomId ?: return false
// val room = roomService.getRoom(roomId) ?: return false
//TODO RoomState not yet managed
Timber.e("POWER LEVELS STATE NOT YET MANAGED BY RIOTX")
return false //senderNotificationPermissionCondition.isSatisfied(event, )
}
override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean {
val roomId = event.roomId ?: return false
val room = roomService.getRoom(roomId) ?: return false
val myDisplayName = room.getRoomMember(sessionParams.credentials.userId)?.displayName
?: return false
return containsDisplayNameCondition.isSatisfied(event, myDisplayName)
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019 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.matrix.android.internal.session.pushers
import arrow.core.Try
import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
internal interface GetPushRulesTask : Task<Unit, PushrulesResponse>
internal class DefaultGetPushrulesTask(private val pushrulesApi: PushrulesApi) : GetPushRulesTask {
override suspend fun execute(params: Unit): Try<PushrulesResponse> {
return executeRequest {
apiCall = pushrulesApi.getAllRules()
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2019 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.matrix.android.internal.session.pushers
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.*
internal interface PushrulesApi {
/**
* Get all push rules
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/")
fun getAllRules(): Call<PushrulesResponse>
/**
* Update the ruleID enable status
*
* @param kind the notification kind (sender, room...)
* @param ruleId the ruleId
* @param enable the new enable status
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled")
fun updateEnableRuleStatus(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body enable: Boolean?): Call<Unit>
/**
* Update the ruleID enable status
*
* @param kind the notification kind (sender, room...)
* @param ruleId the ruleId
* @param actions the actions
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions")
fun updateRuleActions(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body actions: Any): Call<Unit>
/**
* Update the ruleID enable status
*
* @param kind the notification kind (sender, room...)
* @param ruleId the ruleId
*/
@DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}")
fun deleteRule(@Path("kind") kind: String, @Path("ruleId") ruleId: String): Call<Unit>
/**
* Add the ruleID enable status
*
* @param kind the notification kind (sender, room...)
* @param ruleId the ruleId.
* @param rule the rule to add.
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}")
fun addRule(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body rule: PushRule): Call<Unit>
}

View File

@ -66,6 +66,14 @@ internal class DefaultMembershipService(private val roomId: String,
)
}
override fun getNumberOfJoinedMembers(): Int {
var result = 0
monarchy.runTransactionSync {
result = RoomMembers(it, roomId).getNumberOfJoinedMembers()
}
return result
}
override fun invite(userId: String, callback: MatrixCallback<Unit>) {
val params = InviteTask.Params(roomId, userId)
inviteTask.configureWith(params)

View File

@ -1,10 +1,23 @@
package im.vector.matrix.android.api.pushrules
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable
import org.junit.Assert
import org.junit.Test
class PushrulesConditionTest {
@ -100,4 +113,179 @@ class PushrulesConditionTest {
assert(condition.isSatisfied(simpleTextEvent))
}
@Test
fun test_roommember_condition() {
val conditionEqual3 = RoomMemberCountCondition("3")
val conditionEqual3Bis = RoomMemberCountCondition("==3")
val conditionLessThan3 = RoomMemberCountCondition("<3")
val session = MockRoomService()
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = "2joined").also {
Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, session))
Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, session))
Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, session))
}
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = "3joined").also {
Assert.assertTrue("This room has 3 members",conditionEqual3.isSatisfied(it, session))
Assert.assertTrue("This room has 3 members",conditionEqual3Bis.isSatisfied(it, session))
Assert.assertFalse("This room has more than 3 members",conditionLessThan3.isSatisfied(it, session))
}
}
class MockRoomService() : RoomService {
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>) {
}
override fun getRoom(roomId: String): Room? {
return when (roomId) {
"2joined" -> MockRoom(roomId, 2)
"3joined" -> MockRoom(roomId, 3)
else -> null
}
}
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
return MutableLiveData()
}
}
class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room {
override fun getNumberOfJoinedMembers(): Int {
return _numberOfJoinedMembers
}
override val roomSummary: LiveData<RoomSummary>
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun redactEvent(event: Event, reason: String?): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isEventRead(eventId: String): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun loadRoomMembersIfNeeded(): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getRoomMember(userId: String): RoomMember? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getRoomMemberIdsLive(): LiveData<List<String>> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun invite(userId: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun join(callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun leave(callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun undoReaction(reaction: String, targetEventId: String, myUserId: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun editTextMessage(targetEventId: String, newBodyText: String, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String): Cancelable {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isEncrypted(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun encryptionAlgorithm(): String? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun shouldEncryptForInvitedMembers(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
}