diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt index a7789aaa..797830f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt @@ -43,51 +43,53 @@ class Action(val type: Type) { var stringValue: String? = null var boolValue: Boolean? = null -} - -fun PushRule.domainActions(): List? { - val actions = ArrayList() - 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? { + val actions = ArrayList() + 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 -} \ No newline at end of file +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt index d20f9692..c0bb4f16 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt @@ -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" } - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt similarity index 52% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt index 75520f9a..4d15d5de 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt @@ -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 - - fun onRulesUpdate(newRules: List) + fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean + fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean + fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean + fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ContainsDisplayNameCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ContainsDisplayNameCondition.kt new file mode 100644 index 00000000..ce9f88e3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ContainsDisplayNameCondition.kt @@ -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() + } +// EventType.ENCRYPTED -> { +// event.root.getClearContent()?.toModel() +// } + 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 + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt index 0c729799..7325abba 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt index 47328d17..fc71d16d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt @@ -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 //TODO update rule @@ -28,6 +35,8 @@ interface PushRuleService { fun removePushRuleListener(listener: PushRuleListener) +// fun fulfilledBingRule(event: Event, rules: List): PushRule? + interface PushRuleListener { fun onMatchRule(event: Event, actions: List) fun batchFinish() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt new file mode 100644 index 00000000..b2bed4c2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt @@ -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? { + 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 + + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt new file mode 100644 index 00000000..3a67ff93 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt @@ -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) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt index 72fc2b24..fac0fc51 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt @@ -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 + } } } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt index 58d36cfc..2e090a24 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt @@ -25,7 +25,7 @@ data class PushRule( //Required. The domainActions to perform when this rule is matched. val actions: List, //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. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt similarity index 97% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt index 5642bf82..75ac3aee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index 59054dc1..7c21695e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -49,6 +49,8 @@ interface MembershipService { */ fun getRoomMemberIdsLive(): LiveData> + fun getNumberOfJoinedMembers() : Int + /** * Invite a user in the room */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushConditionMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushConditionMapper.kt new file mode 100644 index 00000000..eef122a5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushConditionMapper.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt new file mode 100644 index 00000000..1ed2151e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt @@ -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>(Types.newParameterizedType(List::class.java, Any::class.java)) + +// private val listOfAnyAdapter: JsonAdapter> = +// moshi.adapter>(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 { + 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() + ) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt index fc8d66b5..1f1681d8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt @@ -24,7 +24,7 @@ internal open class PushRulesEntity( @Index var userId: String = "", var scope: String = "", var rulesetKey: String = "", - var pushRules: RealmList = RealmList() + var pushRules: RealmList = RealmList() ) : RealmObject() { companion object } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt index f5e9a6d0..ff91665f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt @@ -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 { + return realm.where() + .equalTo(PushRulesEntityFields.USER_ID,userId) + .equalTo(PushRulesEntityFields.SCOPE,scope) + .equalTo(PushRulesEntityFields.RULESET_KEY,rulesetKey) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index cef7e275..49318096 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -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 { + return pushRuleService.getPushrules(scope) + } + + override fun fetchPushRules(scope: String) { + pushRuleService.fetchPushRules(scope) + } + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 9d24ae54..97b7bc37 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -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() as PushRuleService - } - scope(DefaultSession.SCOPE) { - PushRulesManager(get(), get()) + get() 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) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt index 371c7f2c..0c12b3e5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -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(monarchy) { override val query = Monarchy.Query { @@ -41,9 +41,33 @@ internal class BingRuleWatcher(monarchy: Monarchy, override fun processChanges(inserted: List, updated: List, deleted: List) { //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? { + 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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt new file mode 100644 index 00000000..b2a6b9b1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -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() + + + override fun fetchPushRules(scope: String) { + pushRulesTask + .configureWith(Unit) + .dispatchTo(object : MatrixCallback { + 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 { + + var contentRules: List = 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 = 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 = 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 = 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 = 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) { +// 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) { + + } + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt deleted file mode 100644 index e4329c65..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt +++ /dev/null @@ -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 { - 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::class.java).fromJson(raw) - - return listOf( - pushRule!! - ) - } - - override fun onRulesUpdate(newRules: List) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt deleted file mode 100644 index 5c5a7dab..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt +++ /dev/null @@ -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() - - - 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) { - 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 - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt new file mode 100644 index 00000000..e85f4347 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt @@ -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() + + private val sessionParams by inject() + + 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) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt new file mode 100644 index 00000000..1ce8c21e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt @@ -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 + +internal class DefaultGetPushrulesTask(private val pushrulesApi: PushrulesApi) : GetPushRulesTask { + + override suspend fun execute(params: Unit): Try { + return executeRequest { + apiCall = pushrulesApi.getAllRules() + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt new file mode 100644 index 00000000..cdec3543 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt @@ -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 + + /** + * 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 + + + /** + * 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 + + + /** + * 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 + + /** + * 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 +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt index a9b48ea6..a3c2fc13 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt @@ -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) { val params = InviteTask.Params(roomId, userId) inviteTask.configureWith(params) diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index 2c7e29b2..d9b81917 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -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) { + + } + + override fun getRoom(roomId: String): Room? { + return when (roomId) { + "2joined" -> MockRoom(roomId, 2) + "3joined" -> MockRoom(roomId, 3) + else -> null + } + } + + override fun liveRoomSummaries(): LiveData> { + return MutableLiveData() + } + + } + + class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room { + + + override fun getNumberOfJoinedMembers(): Int { + return _numberOfJoinedMembers + } + + override val roomSummary: LiveData + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + + override fun createTimeline(eventId: String?, allowedTypes: List?): 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): 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) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun setReadReceipt(eventId: String, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) { + 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> { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun invite(userId: String, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun join(callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun leave(callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun updateTopic(topic: String, callback: MatrixCallback) { + 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> { + 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. + } + + } + } diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index ed98d3df..93e34a78 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -137,8 +137,10 @@ class VectorApplication : Application() { } else { //TODO check if notifications are enabled for this device //We need to use alarm in this mode - AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext,4_000L) - Timber.i("Alarm scheduled to restart service") + if (Matrix.getInstance().currentSession != null) { + AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext, 4_000L) + Timber.i("Alarm scheduled to restart service") + } } } @@ -150,6 +152,7 @@ class VectorApplication : Application() { it.refreshPushers() //bind to the sync service get().startWithSession(it) + it.fetchPushRules() } } diff --git a/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt b/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt index bd10f709..9a17390b 100755 --- a/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt @@ -25,7 +25,7 @@ import android.widget.TextView import androidx.preference.PreferenceViewHolder import im.vector.riotredesign.R -// TODO Replace by real Bingrule class +// TODO Replace by real Bingrule class, then delete class BingRule(rule: BingRule) { fun shouldNotNotify() = false fun shouldNotify() = false diff --git a/vector/src/main/java/im/vector/riotredesign/core/ui/list/GenericFooterItem.kt b/vector/src/main/java/im/vector/riotredesign/core/ui/list/GenericFooterItem.kt new file mode 100644 index 00000000..30cd69a4 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/ui/list/GenericFooterItem.kt @@ -0,0 +1,62 @@ +/* + * 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.riotredesign.core.ui.list + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder +import im.vector.riotredesign.core.epoxy.VectorEpoxyModel +import im.vector.riotredesign.core.extensions.setTextOrHide + +/** + * A generic list item. + * Displays an item with a title, and optional description. + * Can display an accessory on the right, that can be an image or an indeterminate progress. + * If provided with an action, will display a button at the bottom of the list item. + */ +@EpoxyModelClass(layout = R.layout.item_generic_footer) +abstract class GenericFooterItem : VectorEpoxyModel() { + + + @EpoxyAttribute + var text: String? = null + + @EpoxyAttribute + var style: GenericItem.STYLE = GenericItem.STYLE.NORMAL_TEXT + + @EpoxyAttribute + var itemClickAction: GenericItem.Action? = null + + override fun bind(holder: Holder) { + + holder.text.setTextOrHide(text) + when (style) { + GenericItem.STYLE.BIG_TEXT -> holder.text.textSize = 18f + GenericItem.STYLE.NORMAL_TEXT -> holder.text.textSize = 14f + } + + + holder.view.setOnClickListener { + itemClickAction?.perform?.run() + } + } + + class Holder : VectorEpoxyHolder() { + val text by bind(R.id.itemGenericFooterText) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt index 8c0be582..a3c4d426 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt @@ -78,6 +78,7 @@ class LoginActivity : VectorBaseActivity() { data.setFilter(FilterService.FilterPreset.RiotFilter) data.startSync() get().startWithSession(data) + data.fetchPushRules() goToHome() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt index befff24a..1854ccd0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt @@ -43,7 +43,7 @@ class NotifiableEventResolver(val context: Context) { //private val eventDisplay = RiotEventDisplay(context) - fun resolveEvent(event: Event/*, roomState: RoomState?*/, bingRule: PushRule?, session: Session): NotifiableEvent? { + fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session): NotifiableEvent? { // val store = session.dataHandler.store @@ -55,7 +55,7 @@ class NotifiableEventResolver(val context: Context) { when (event.getClearType()) { EventType.MESSAGE -> { - return resolveMessageEvent(event, bingRule, session) + return resolveMessageEvent(event, session) } // EventType.ENCRYPTED -> { // val messageEvent = resolveMessageEvent(event, bingRule, session, store) @@ -88,12 +88,10 @@ class NotifiableEventResolver(val context: Context) { } - private fun resolveMessageEvent(event: Event, pushRule: PushRule?, session: Session): NotifiableEvent? { + private fun resolveMessageEvent(event: Event, session: Session): NotifiableEvent? { //If we are here, that means that the event should be notified to the user, we check now how it should be presented (sound) // val soundName = pushRule?.notificationSound - val noisy = true//pushRule?.notificationSound != null - //The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...) val room = session.getRoom(event.roomId!! /*roomID cannot be null (see Matrix SDK code)*/) @@ -109,7 +107,7 @@ class NotifiableEventResolver(val context: Context) { val notifiableEvent = NotifiableMessageEvent( eventId = event.eventId ?: "", timestamp = event.originServerTs ?: 0, - noisy = noisy, + noisy = false,//will be updated senderName = senderDisplayName, senderId = event.senderId, body = body, @@ -130,7 +128,7 @@ class NotifiableEventResolver(val context: Context) { val notifiableEvent = NotifiableMessageEvent( eventId = event.eventId!!, timestamp = event.originServerTs ?: 0, - noisy = noisy, + noisy = false,//will be updated senderName = senderDisplayName, senderId = event.senderId, body = body, diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationAction.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationAction.kt new file mode 100644 index 00000000..a1eac946 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationAction.kt @@ -0,0 +1,41 @@ +/* + * 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.riotredesign.features.notifications + +import im.vector.matrix.android.api.pushrules.Action + +data class NotificationAction( + val shouldNotify: Boolean, + val highlight: Boolean = false, + val soundName: String? = null +) { + companion object { + fun extractFrom(ruleActions: List): NotificationAction { + var shouldNotify = false + var highlight = false + var sound: String? = null + ruleActions.forEach { + if (it.type == Action.Type.NOTIFY) shouldNotify = true + if (it.type == Action.Type.DONT_NOTIFY) shouldNotify = false + if (it.type == Action.Type.SET_TWEAK) { + if (it.tweak_action == "highlight") highlight = it.boolValue ?: false + if (it.tweak_action == "sound") sound = it.stringValue + } + } + return NotificationAction(shouldNotify, highlight, sound) + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index 1a352864..5bf7caa7 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -57,28 +57,6 @@ class NotificationDrawerManager(val context: Context, private val outdatedDetect } }) - /** - * No multi session support for now - */ - private fun initWithSession(session: Session?) { - session?.let { - /* - myUserDisplayName = it.myUser?.displayname ?: it.myUserId - - // User Avatar - it.myUser?.avatarUrl?.let { avatarUrl -> - val userAvatarUrlPath = it.mediaCache?.thumbnailCacheFile(avatarUrl, avatarSize) - if (userAvatarUrlPath != null) { - myUserAvatarUrl = userAvatarUrlPath.path - } else { - // prepare for the next time - session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), avatarUrl, avatarSize) - } - } - */ - } - } - /** Should be called as soon as a new event is ready to be displayed. The notification corresponding to this event will not be displayed until diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt index 26089917..458db906 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt @@ -16,12 +16,24 @@ class PushRuleTriggerListener( var session: Session? = null override fun onMatchRule(event: Event, actions: List) { + Timber.v("Push rule match for event ${event.eventId}") if (session == null) { Timber.e("Called without active session") return } - resolver.resolveEvent(event,null,session!!)?.let { - drawerManager.onNotifiableEventReceived(it) + val notificationAction = NotificationAction.extractFrom(actions) + if (notificationAction.shouldNotify) { + val resolveEvent = resolver.resolveEvent(event, session!!) + if (resolveEvent == null) { + Timber.v("## Failed to resolve event") + //TODO + } else { + resolveEvent.noisy = !notificationAction.soundName.isNullOrBlank() + Timber.v("New event to notify $resolveEvent tweaks:$notificationAction") + drawerManager.onNotifiableEventReceived(resolveEvent) + } + } else { + Timber.v("Matched push rule is set to not notify") } } @@ -43,4 +55,5 @@ class PushRuleTriggerListener( drawerManager.clearAllEvents() drawerManager.refreshNotificationDrawer() } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt index d073d560..6ce235f6 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt @@ -1671,15 +1671,15 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref var index = 0 - for (pusher in mDisplayedPushers) { - if (null != pusher.lang) { - val isThisDeviceTarget = TextUtils.equals(pushManager.currentRegistrationToken, pusher.pushkey) + for (pushRule in mDisplayedPushers) { + if (null != pushRule.lang) { + val isThisDeviceTarget = TextUtils.equals(pushManager.currentRegistrationToken, pushRule.pushkey) val preference = VectorPreference(activity).apply { mTypeface = if (isThisDeviceTarget) Typeface.BOLD else Typeface.NORMAL } - preference.title = pusher.deviceDisplayName - preference.summary = pusher.appDisplayName + preference.title = pushRule.deviceDisplayName + preference.summary = pushRule.appDisplayName preference.key = PUSHER_PREFERENCE_KEY_BASE + index index++ mPushersSettingsCategory.addPreference(preference) @@ -1694,7 +1694,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref .setPositiveButton(R.string.remove) { _, _ -> displayLoadingView() - pushManager.unregister(session, pusher, object : MatrixCallback { + pushManager.unregister(session, pushRule, object : MatrixCallback { override fun onSuccess(info: Void?) { refreshPushersList() onCommonDone(null) diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt index bdb29e83..c468955f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt @@ -17,7 +17,6 @@ package im.vector.riotredesign.features.settings.push import android.os.Bundle -import android.widget.LinearLayout import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -27,19 +26,21 @@ import com.airbnb.mvrx.withState import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseFragment -import kotlinx.android.synthetic.main.fragment_settings_pushgateways.* +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.core.ui.list.genericFooterItem +import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* class PushGatewaysFragment : VectorBaseFragment() { - override fun getLayoutResId(): Int = R.layout.fragment_settings_pushgateways + override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy private val viewModel: PushGatewaysViewModel by fragmentViewModel(PushGatewaysViewModel::class) - private val epoxyController by lazy { PushGateWayController() } + private val epoxyController by lazy { PushGateWayController(StringProvider(requireContext().resources)) } override fun onResume() { super.onResume() - (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_gateways) + (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notifications_targets) } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -56,13 +57,26 @@ class PushGatewaysFragment : VectorBaseFragment() { epoxyController.setData(it) } - class PushGateWayController : TypedEpoxyController() { + class PushGateWayController(private val stringProvider: StringProvider) : TypedEpoxyController() { override fun buildModels(data: PushGatewayViewState?) { - val pushers = data?.pushgateways?.invoke() ?: return - pushers.forEach { - pushGatewayItem { - id("${it.pushKey}_${it.appId}") - pusher(it) + data?.pushgateways?.invoke()?.let { pushers -> + if (pushers.isEmpty()) { + genericFooterItem { + id("footer") + text(stringProvider.getString(R.string.settings_push_gateway_no_pushers)) + } + } else { + pushers.forEach { + pushGatewayItem { + id("${it.pushKey}_${it.appId}") + pusher(it) + } + } + } + } ?: run { + genericFooterItem { + id("footer") + text(stringProvider.getString(R.string.loading)) } } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt new file mode 100644 index 00000000..47f41e91 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt @@ -0,0 +1,71 @@ +package im.vector.riotredesign.features.settings.push + +import android.annotation.SuppressLint +import android.graphics.Color +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.airbnb.epoxy.EpoxyModelWithHolder +import im.vector.matrix.android.api.pushrules.Action +import im.vector.matrix.android.api.pushrules.rest.PushRule +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder +import im.vector.riotredesign.features.notifications.NotificationAction + + +@EpoxyModelClass(layout = R.layout.item_pushrule_raw) +abstract class PushRuleItem : EpoxyModelWithHolder() { + + @EpoxyAttribute + lateinit var pushRule: PushRule + + @SuppressLint("SetTextI18n") + override fun bind(holder: Holder) { + val context = holder.view.context + if (pushRule.enabled) { + holder.view.setBackgroundColor(Color.TRANSPARENT) + holder.ruleId.text = pushRule.ruleId + } else { + holder.view.setBackgroundColor(ContextCompat.getColor(context, R.color.vector_silver_color)) + holder.ruleId.text = "[Disabled] ${pushRule.ruleId}" + } + val actions = Action.mapFrom(pushRule) + if (actions.isNullOrEmpty()) { + holder.actionIcon.isInvisible = true + } else { + holder.actionIcon.isVisible = true + val notifAction = NotificationAction.extractFrom(actions) + + if (notifAction.shouldNotify && !notifAction.soundName.isNullOrBlank()) { + holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_notify_noisy)) + } else if (notifAction.shouldNotify) { + holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_notify_silent)) + } else { + holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_dont_notify)) + } + + var description = StringBuffer() + pushRule.conditions?.forEachIndexed { i, condition -> + if (i > 0) description.append("\n") + description.append(condition.asExecutableCondition()?.technicalDescription() + ?: "UNSUPPORTED") + } + if (description.isBlank()) { + holder.description.text = "No Conditions" + } else { + holder.description.text = description + } + + } + } + + class Holder : VectorEpoxyHolder() { + val ruleId by bind(R.id.pushRuleId) + val description by bind(R.id.pushRuleDescription) + val actionIcon by bind(R.id.pushRuleActionIcon) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt new file mode 100644 index 00000000..7f73f893 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt @@ -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.riotredesign.features.settings.push + +import android.os.Bundle +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotredesign.R +import im.vector.riotredesign.core.platform.VectorBaseActivity +import im.vector.riotredesign.core.platform.VectorBaseFragment +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.core.ui.list.genericFooterItem +import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* + + +class PushRulesFragment : VectorBaseFragment() { + + override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy + + private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class) + + private val epoxyController by lazy { PushRulesFragment.PushRulesController(StringProvider(requireContext().resources)) } + + + override fun onResume() { + super.onResume() + (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_rules) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + epoxyRecyclerView.layoutManager = lmgr + val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, + lmgr.orientation) + epoxyRecyclerView.addItemDecoration(dividerItemDecoration) + epoxyRecyclerView.adapter = epoxyController.adapter + } + + override fun invalidate() = withState(viewModel) { + epoxyController.setData(it) + } + + class PushRulesController(private val stringProvider: StringProvider) : TypedEpoxyController() { + + override fun buildModels(data: PushRulesViewState?) { + data?.let { + it.rules.forEach { + pushRuleItem { + id(it.ruleId) + pushRule(it) + } + } + } ?: run { + genericFooterItem { + id("footer") + text(stringProvider.getString(R.string.settings_push_rules_no_rules)) + } + } + } + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt new file mode 100644 index 00000000..05109d7e --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt @@ -0,0 +1,43 @@ +/* + * 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.riotredesign.features.settings.push + +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import im.vector.matrix.android.api.pushrules.rest.PushRule +import im.vector.matrix.android.api.session.Session +import im.vector.riotredesign.core.platform.VectorViewModel +import org.koin.android.ext.android.get + +data class PushRulesViewState( + val rules: List = emptyList()) + : MvRxState + + +class PushRulesViewModel(initialState: PushRulesViewState) : VectorViewModel(initialState) { + + companion object : MvRxViewModelFactory { + + override fun initialState(viewModelContext: ViewModelContext): PushRulesViewState? { + val session = viewModelContext.activity.get() + val rules = session.getPushrules() + + return PushRulesViewState(rules) + } + + } +} \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_action_dont_notify.xml b/vector/src/main/res/drawable/ic_action_dont_notify.xml new file mode 100644 index 00000000..b199ce04 --- /dev/null +++ b/vector/src/main/res/drawable/ic_action_dont_notify.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/vector/src/main/res/drawable/ic_action_notify_noisy.xml b/vector/src/main/res/drawable/ic_action_notify_noisy.xml new file mode 100644 index 00000000..fb078198 --- /dev/null +++ b/vector/src/main/res/drawable/ic_action_notify_noisy.xml @@ -0,0 +1,18 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_action_notify_silent.xml b/vector/src/main/res/drawable/ic_action_notify_silent.xml new file mode 100644 index 00000000..c2d325e1 --- /dev/null +++ b/vector/src/main/res/drawable/ic_action_notify_silent.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/vector/src/main/res/layout/fragment_settings_pushgateways.xml b/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml similarity index 100% rename from vector/src/main/res/layout/fragment_settings_pushgateways.xml rename to vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml diff --git a/vector/src/main/res/layout/item_generic_footer.xml b/vector/src/main/res/layout/item_generic_footer.xml new file mode 100644 index 00000000..c5c4e7fc --- /dev/null +++ b/vector/src/main/res/layout/item_generic_footer.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/debug/res/layout/item_pushgateway.xml b/vector/src/main/res/layout/item_pushgateway.xml similarity index 100% rename from vector/src/debug/res/layout/item_pushgateway.xml rename to vector/src/main/res/layout/item_pushgateway.xml diff --git a/vector/src/main/res/layout/item_pushrule_raw.xml b/vector/src/main/res/layout/item_pushrule_raw.xml new file mode 100644 index 00000000..0d2ba360 --- /dev/null +++ b/vector/src/main/res/layout/item_pushrule_raw.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 7583463f..987afcd8 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -17,6 +17,8 @@ Preferences Security & Privacy Expert - Push Gateways + Push Rules + No push rules defined + No registered push gateways \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml index 0c890068..4f110e8a 100644 --- a/vector/src/main/res/xml/vector_settings_notifications.xml +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -2,14 +2,15 @@ - + + + + @@ -27,6 +28,7 @@ android:persistent="false" android:summary="@string/settings_notification_advanced_summary" android:title="@string/settings_notification_advanced" + android:enabled="false" app:fragment="im.vector.fragments.VectorSettingsNotificationsAdvancedFragment" /> + + \ No newline at end of file