Merge pull request #2001 from vector-im/feature/mention_display_name

Fix mention display name
This commit is contained in:
Benoit Marty 2020-08-26 17:35:23 +02:00 committed by GitHub
commit 95e80f0263
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 266 additions and 267 deletions

View file

@ -9,6 +9,7 @@ Improvements 🙌:
Bugfix 🐛:
- Display name not shown under Settings/General (#1926)
- Words containing my name should not trigger notifications (#1781)
- Fix changing language issue
- Fix FontSize issue (#1483, #1787)
- Fix bad color for settings icon on Android < 24 (#1786)

View file

@ -24,21 +24,24 @@ sealed class Action {
object DoNotNotify : Action()
data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action()
data class Highlight(val highlight: Boolean) : Action()
companion object {
const val ACTION_NOTIFY = "notify"
const val ACTION_DONT_NOTIFY = "dont_notify"
const val ACTION_COALESCE = "coalesce"
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
const val ACTION_OBJECT_VALUE_KEY = "value"
const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
const val ACTION_OBJECT_VALUE_VALUE_RING = "ring"
}
}
private const val ACTION_NOTIFY = "notify"
private const val ACTION_DONT_NOTIFY = "dont_notify"
private const val ACTION_COALESCE = "coalesce"
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
private const val ACTION_OBJECT_VALUE_KEY = "value"
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#actions
*
@ -69,18 +72,18 @@ private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
fun List<Action>.toJson(): List<Any> {
return map { action ->
when (action) {
is Action.Notify -> ACTION_NOTIFY
is Action.DoNotNotify -> ACTION_DONT_NOTIFY
is Action.Sound -> {
is Action.Notify -> Action.ACTION_NOTIFY
is Action.DoNotNotify -> Action.ACTION_DONT_NOTIFY
is Action.Sound -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
ACTION_OBJECT_VALUE_KEY to action.sound
Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
Action.ACTION_OBJECT_VALUE_KEY to action.sound
)
}
is Action.Highlight -> {
is Action.Highlight -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
ACTION_OBJECT_VALUE_KEY to action.highlight
Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
Action.ACTION_OBJECT_VALUE_KEY to action.highlight
)
}
}
@ -92,26 +95,26 @@ fun PushRule.getActions(): List<Action> {
actions.forEach { actionStrOrObj ->
when (actionStrOrObj) {
ACTION_NOTIFY -> Action.Notify
ACTION_DONT_NOTIFY -> Action.DoNotNotify
is Map<*, *> -> {
when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) {
ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
Action.ACTION_NOTIFY -> Action.Notify
Action.ACTION_DONT_NOTIFY -> Action.DoNotNotify
is Map<*, *> -> {
when (actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]) {
Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
(actionStrOrObj[Action.ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
Action.Sound(stringValue)
}
// When the value is not there, default sound (not specified by the spec)
?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT)
?: Action.Sound(Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT)
}
ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
Action.ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
(actionStrOrObj[Action.ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
Action.Highlight(boolValue)
}
// When the value is not there, default is true, says the spec
?: Action.Highlight(true)
}
else -> {
Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}")
Timber.w("Unsupported set_tweak value ${actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]}")
null
}
}

View file

@ -18,32 +18,8 @@ package org.matrix.android.sdk.api.pushrules
import org.matrix.android.sdk.api.session.events.model.Event
abstract class Condition(val kind: Kind) {
interface Condition {
fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
enum class Kind(val value: String) {
EventMatch("event_match"),
ContainsDisplayName("contains_display_name"),
RoomMemberCount("room_member_count"),
SenderNotificationPermission("sender_notification_permission"),
Unrecognised("");
companion object {
fun fromString(value: String): Kind {
return when (value) {
"event_match" -> EventMatch
"contains_display_name" -> ContainsDisplayName
"room_member_count" -> RoomMemberCount
"sender_notification_permission" -> SenderNotificationPermission
else -> Unrecognised
}
}
}
}
abstract fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
open fun technicalDescription(): String {
return "Kind: $kind"
}
fun technicalDescription(): String
}

View file

@ -20,17 +20,15 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import timber.log.Timber
import org.matrix.android.sdk.internal.util.caseInsensitiveFind
class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) {
class ContainsDisplayNameCondition : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveContainsDisplayNameCondition(event, this)
}
override fun technicalDescription(): String {
return "User is mentioned"
}
override fun technicalDescription() = "User is mentioned"
fun isSatisfied(event: Event, displayName: String): Boolean {
val message = when (event.type) {
@ -45,31 +43,6 @@ class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) {
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 (subString.isEmpty() || longString.isEmpty()) {
return false
}
try {
val regex = Regex("(\\W|^)" + Regex.escape(subString) + "(\\W|$)", RegexOption.IGNORE_CASE)
return regex.containsMatchIn(longString)
} catch (e: Exception) {
Timber.e(e, "## caseInsensitiveFind() : failed")
}
return false
}
return message.body.caseInsensitiveFind(displayName)
}
}

View file

@ -18,6 +18,9 @@ package org.matrix.android.sdk.api.pushrules
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.caseInsensitiveFind
import org.matrix.android.sdk.internal.util.hasSpecialGlobChar
import org.matrix.android.sdk.internal.util.simpleGlobToRegExp
import timber.log.Timber
class EventMatchCondition(
@ -29,16 +32,18 @@ class EventMatchCondition(
* The glob-style pattern to match against. Patterns with no special glob characters should
* be treated as having asterisks prepended and appended when testing the condition.
*/
val pattern: String
) : Condition(Kind.EventMatch) {
val pattern: String,
/**
* true to match only words. In this case pattern will not be considered as a glob
*/
val wordsOnly: Boolean
) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveEventMatchCondition(event, this)
}
override fun technicalDescription(): String {
return "'$key' Matches '$pattern'"
}
override fun technicalDescription() = "'$key' matches '$pattern', words only '$wordsOnly'"
fun isSatisfied(event: Event): Boolean {
// TODO encrypted events?
@ -48,14 +53,18 @@ class EventMatchCondition(
// Patterns with no special glob characters should be treated as having asterisks prepended
// and appended when testing the condition.
try {
val modPattern = if (hasSpecialGlobChar(pattern)) simpleGlobToRegExp(pattern) else simpleGlobToRegExp("*$pattern*")
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL)
return regex.containsMatchIn(value)
return try {
if (wordsOnly) {
value.caseInsensitiveFind(pattern)
} else {
val modPattern = if (pattern.hasSpecialGlobChar()) pattern.simpleGlobToRegExp() else "*$pattern*".simpleGlobToRegExp()
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL)
regex.containsMatchIn(value)
}
} catch (e: Throwable) {
// e.g PatternSyntaxException
Timber.e(e, "Failed to evaluate push condition")
return false
false
}
}
@ -78,27 +87,4 @@ class EventMatchCondition(
}
return null
}
companion object {
private fun hasSpecialGlobChar(glob: String): Boolean {
return glob.contains("*") || glob.contains("?")
}
// Very simple glob to regexp converter
private fun simpleGlobToRegExp(glob: String): String {
var out = "" // "^"
for (element in glob) {
when (element) {
'*' -> out += ".*"
'?' -> out += '.'.toString()
'.' -> out += "\\."
'\\' -> out += "\\\\"
else -> out += element
}
}
out += "" // '$'.toString()
return out
}
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.pushrules
enum class Kind(val value: String) {
EventMatch("event_match"),
ContainsDisplayName("contains_display_name"),
RoomMemberCount("room_member_count"),
SenderNotificationPermission("sender_notification_permission"),
Unrecognised("");
companion object {
fun fromString(value: String): Kind {
return when (value) {
"event_match" -> EventMatch
"contains_display_name" -> ContainsDisplayName
"room_member_count" -> RoomMemberCount
"sender_notification_permission" -> SenderNotificationPermission
else -> Unrecognised
}
}
}
}

View file

@ -29,15 +29,13 @@ class RoomMemberCountCondition(
* If no prefix is present, this parameter defaults to ==.
*/
val iz: String
) : Condition(Kind.RoomMemberCount) {
) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveRoomMemberCountCondition(event, this)
}
override fun technicalDescription(): String {
return "Room member count is $iz"
}
override fun technicalDescription() = "Room member count is $iz"
internal fun isSatisfied(event: Event, roomGetter: RoomGetter): Boolean {
// sanity checks

View file

@ -45,4 +45,6 @@ object RuleIds {
// Not documented
const val RULE_ID_FALLBACK = ".m.rule.fallback"
const val RULE_ID_REACTION = ".m.rule.reaction"
}

View file

@ -28,15 +28,13 @@ class SenderNotificationPermissionCondition(
* type from the notifications object in the power level event content.
*/
val key: String
) : Condition(Kind.SenderNotificationPermission) {
) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveSenderNotificationPermissionCondition(event, this)
}
override fun technicalDescription(): String {
return "User power level <$key>"
}
override fun technicalDescription() = "User power level <$key>"
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
val powerLevelsHelper = PowerLevelsHelper(powerLevels)

View file

@ -21,7 +21,9 @@ import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.pushrules.Condition
import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
import org.matrix.android.sdk.api.pushrules.EventMatchCondition
import org.matrix.android.sdk.api.pushrules.Kind
import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
import timber.log.Timber
@ -58,20 +60,20 @@ data class PushCondition(
val iz: String? = null
) {
fun asExecutableCondition(): Condition? {
return when (Condition.Kind.fromString(kind)) {
Condition.Kind.EventMatch -> {
fun asExecutableCondition(rule: PushRule): Condition? {
return when (Kind.fromString(kind)) {
Kind.EventMatch -> {
if (key != null && pattern != null) {
EventMatchCondition(key, pattern)
EventMatchCondition(key, pattern, rule.ruleId == RuleIds.RULE_ID_CONTAIN_USER_NAME)
} else {
Timber.e("Malformed Event match condition")
null
}
}
Condition.Kind.ContainsDisplayName -> {
Kind.ContainsDisplayName -> {
ContainsDisplayNameCondition()
}
Condition.Kind.RoomMemberCount -> {
Kind.RoomMemberCount -> {
if (iz.isNullOrEmpty()) {
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
null
@ -79,7 +81,7 @@ data class PushCondition(
RoomMemberCountCondition(iz)
}
}
Condition.Kind.SenderNotificationPermission -> {
Kind.SenderNotificationPermission -> {
if (key == null) {
Timber.e("Malformed Sender Notification Permission condition")
null
@ -87,7 +89,7 @@ data class PushCondition(
SenderNotificationPermissionCondition(key)
}
}
Condition.Kind.Unrecognised -> {
Kind.Unrecognised -> {
Timber.e("Unknown kind $kind")
null
}

View file

@ -63,7 +63,7 @@ data class PushRule(
* Add the default notification sound.
*/
fun setNotificationSound(): PushRule {
return setNotificationSound(ACTION_VALUE_DEFAULT)
return setNotificationSound(Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT)
}
fun getNotificationSound(): String? {
@ -109,13 +109,13 @@ data class PushRule(
fun setNotify(notify: Boolean): PushRule {
val mutableActions = actions.toMutableList()
mutableActions.remove(ACTION_DONT_NOTIFY)
mutableActions.remove(ACTION_NOTIFY)
mutableActions.remove(Action.ACTION_DONT_NOTIFY)
mutableActions.remove(Action.ACTION_NOTIFY)
if (notify) {
mutableActions.add(ACTION_NOTIFY)
mutableActions.add(Action.ACTION_NOTIFY)
} else {
mutableActions.add(ACTION_DONT_NOTIFY)
mutableActions.add(Action.ACTION_DONT_NOTIFY)
}
return copy(actions = mutableActions)
@ -126,51 +126,12 @@ data class PushRule(
*
* @return true if the rule should play sound
*/
fun shouldNotify() = actions.contains(ACTION_NOTIFY)
fun shouldNotify() = actions.contains(Action.ACTION_NOTIFY)
/**
* Return true if the rule should not highlight the event.
*
* @return true if the rule should not play sound
*/
fun shouldNotNotify() = actions.contains(ACTION_DONT_NOTIFY)
companion object {
/* ==========================================================================================
* Rule id
* ========================================================================================== */
const val RULE_ID_DISABLE_ALL = ".m.rule.master"
const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name"
const val RULE_ID_CONTAIN_DISPLAY_NAME = ".m.rule.contains_display_name"
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
const val RULE_ID_INVITE_ME = ".m.rule.invite_for_me"
const val RULE_ID_PEOPLE_JOIN_LEAVE = ".m.rule.member_event"
const val RULE_ID_CALL = ".m.rule.call"
const val RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS = ".m.rule.suppress_notices"
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
const val RULE_ID_AT_ROOMS = ".m.rule.roomnotif"
const val RULE_ID_TOMBSTONE = ".m.rule.tombstone"
const val RULE_ID_E2E_ONE_TO_ONE_ROOM = ".m.rule.encrypted_room_one_to_one"
const val RULE_ID_E2E_GROUP = ".m.rule.encrypted"
const val RULE_ID_REACTION = ".m.rule.reaction"
const val RULE_ID_FALLBACK = ".m.rule.fallback"
/* ==========================================================================================
* Actions
* ========================================================================================== */
const val ACTION_NOTIFY = "notify"
const val ACTION_DONT_NOTIFY = "dont_notify"
const val ACTION_COALESCE = "coalesce"
const val ACTION_SET_TWEAK_SOUND_VALUE = "sound"
const val ACTION_SET_TWEAK_HIGHLIGHT_VALUE = "highlight"
const val ACTION_PARAMETER_SET_TWEAK = "set_tweak"
const val ACTION_PARAMETER_VALUE = "value"
const val ACTION_VALUE_DEFAULT = "default"
const val ACTION_VALUE_RING = "ring"
}
fun shouldNotNotify() = actions.contains(Action.ACTION_DONT_NOTIFY)
}

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.pushrules.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.RuleSetKey
/**
@ -51,7 +52,7 @@ data class RuleSet(
var result: PushRuleAndKind? = null
// sanity check
if (null != ruleId) {
if (PushRule.RULE_ID_CONTAIN_USER_NAME == ruleId) {
if (RuleIds.RULE_ID_CONTAIN_USER_NAME == ruleId) {
result = findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) }
} else {
// assume that the ruleId is unique.

View file

@ -17,12 +17,12 @@
package org.matrix.android.sdk.internal.database.mapper
import com.squareup.moshi.Types
import org.matrix.android.sdk.api.pushrules.Condition
import io.realm.RealmList
import org.matrix.android.sdk.api.pushrules.Kind
import org.matrix.android.sdk.api.pushrules.rest.PushCondition
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.internal.database.model.PushRuleEntity
import org.matrix.android.sdk.internal.di.MoshiProvider
import io.realm.RealmList
import timber.log.Timber
internal object PushRulesMapper {
@ -39,7 +39,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
PushCondition(Kind.EventMatch.value, "content.body", pushrule.pattern)
)
)
}
@ -60,7 +60,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.ruleId)
PushCondition(Kind.EventMatch.value, "room_id", pushrule.ruleId)
)
)
}
@ -72,7 +72,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled,
ruleId = pushrule.ruleId,
conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.ruleId)
PushCondition(Kind.EventMatch.value, "user_id", pushrule.ruleId)
)
)
}

View file

@ -100,7 +100,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
return rules.firstOrNull { rule ->
// All conditions must hold true for an event in order to apply the action for the event.
rule.enabled && rule.conditions?.all {
it.asExecutableCondition()?.isSatisfied(event, conditionResolver) ?: false
it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false
} ?: false
}
}

View file

@ -18,7 +18,7 @@
package org.matrix.android.sdk.internal.session.room.notification
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.pushrules.Condition
import org.matrix.android.sdk.api.pushrules.Kind
import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushCondition
@ -59,7 +59,7 @@ internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule?
}
else -> {
val condition = PushCondition(
kind = Condition.Kind.EventMatch.value,
kind = Kind.EventMatch.value,
key = "room_id",
pattern = roomId
)

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.util
internal fun String.hasSpecialGlobChar(): Boolean {
return contains("*") || contains("?")
}
// Very simple glob to regexp converter
internal fun String.simpleGlobToRegExp(): String {
val string = this
return buildString {
// append("^")
string.forEach { char ->
when (char) {
'*' -> append(".*")
'?' -> append(".")
'.' -> append("\\.")
'\\' -> append("\\\\")
else -> append(char)
}
}
// append("$")
}
}

View file

@ -52,3 +52,25 @@ fun convertFromUTF8(s: String): String {
}
fun String.withoutPrefix(prefix: String) = if (startsWith(prefix)) substringAfter(prefix) else this
/**
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
*
* @param subString the string to search for
* @return whether a match was found
*/
fun String.caseInsensitiveFind(subString: String): Boolean {
// add sanity checks
if (subString.isEmpty() || isEmpty()) {
return false
}
try {
val regex = Regex("(\\W|^)" + Regex.escape(subString) + "(\\W|$)", RegexOption.IGNORE_CASE)
return regex.containsMatchIn(this)
} catch (e: Exception) {
Timber.e(e, "## caseInsensitiveFind() : failed")
}
return false
}

View file

@ -16,6 +16,12 @@
package org.matrix.android.sdk.api.pushrules
import io.mockk.every
import io.mockk.mockk
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.matrix.android.sdk.MatrixTest
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toContent
@ -24,28 +30,26 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.internal.session.room.RoomGetter
import io.mockk.every
import io.mockk.mockk
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class PushrulesConditionTest: MatrixTest {
class PushRulesConditionTest : MatrixTest {
/* ==========================================================================================
* Test EventMatchCondition
* ========================================================================================== */
@Test
fun test_eventmatch_type_condition() {
val condition = EventMatchCondition("type", "m.room.message")
val simpleTextEvent = Event(
private fun createSimpleTextEvent(text: String): Event {
return Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "Yo wtf?").toContent(),
content = MessageTextContent("m.text", text).toContent(),
originServerTs = 0)
}
@Test
fun test_eventmatch_type_condition() {
val condition = EventMatchCondition("type", "m.room.message", false)
val simpleTextEvent = createSimpleTextEvent("Yo wtf?")
val rm = RoomMemberContent(
Membership.INVITE,
@ -65,13 +69,9 @@ class PushrulesConditionTest: MatrixTest {
@Test
fun test_eventmatch_path_condition() {
val condition = EventMatchCondition("content.msgtype", "m.text")
val condition = EventMatchCondition("content.msgtype", "m.text", false)
val simpleTextEvent = Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "Yo wtf?").toContent(),
originServerTs = 0)
val simpleTextEvent = createSimpleTextEvent("Yo wtf?")
assert(condition.isSatisfied(simpleTextEvent))
@ -86,49 +86,44 @@ class PushrulesConditionTest: MatrixTest {
).toContent(),
originServerTs = 0
).apply {
assert(EventMatchCondition("content.membership", "invite").isSatisfied(this))
assert(EventMatchCondition("content.membership", "invite", false).isSatisfied(this))
}
}
@Test
fun test_eventmatch_cake_condition() {
val condition = EventMatchCondition("content.body", "cake")
val condition = EventMatchCondition("content.body", "cake", false)
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "How was the cake?").toContent(),
originServerTs = 0
).apply {
assert(condition.isSatisfied(this))
}
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "Howwasthecake?").toContent(),
originServerTs = 0
).apply {
assert(condition.isSatisfied(this))
}
assert(condition.isSatisfied(createSimpleTextEvent("How was the cake?")))
assert(condition.isSatisfied(createSimpleTextEvent("Howwasthecake?")))
}
@Test
fun test_eventmatch_cakelie_condition() {
val condition = EventMatchCondition("content.body", "cake*lie")
val condition = EventMatchCondition("content.body", "cake*lie", false)
val simpleTextEvent = Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "How was the cakeisalie?").toContent(),
originServerTs = 0)
assert(condition.isSatisfied(createSimpleTextEvent("How was the cakeisalie?")))
}
assert(condition.isSatisfied(simpleTextEvent))
@Test
fun test_eventmatch_words_only_condition() {
val condition = EventMatchCondition("content.body", "ben", true)
assertFalse(condition.isSatisfied(createSimpleTextEvent("benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEvent("Hello benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEvent("superben")))
assert(condition.isSatisfied(createSimpleTextEvent("ben")))
assert(condition.isSatisfied(createSimpleTextEvent("hello ben")))
assert(condition.isSatisfied(createSimpleTextEvent("ben is there")))
assert(condition.isSatisfied(createSimpleTextEvent("hello ben!")))
assert(condition.isSatisfied(createSimpleTextEvent("hello Ben!")))
assert(condition.isSatisfied(createSimpleTextEvent("BEN")))
}
@Test
fun test_notice_condition() {
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice")
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice", false)
Event(
type = "m.room.message",

View file

@ -25,9 +25,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
@ -40,6 +37,9 @@ import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.SimpleNotifiableEvent
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import timber.log.Timber
/**
@ -196,7 +196,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
description = "",
type = null,
timestamp = System.currentTimeMillis(),
soundName = PushRule.ACTION_VALUE_DEFAULT,
soundName = Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT,
isPushGatewayEvent = true
)
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)

View file

@ -22,6 +22,8 @@ import android.view.View
import android.widget.RadioGroup
import androidx.preference.PreferenceViewHolder
import im.vector.app.R
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
@ -51,7 +53,7 @@ class PushRulePreference : VectorPreference {
get() {
val safeRule = ruleAndKind?.pushRule ?: return NOTIFICATION_OFF_INDEX
if (safeRule.ruleId == PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
if (safeRule.ruleId == RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
if (safeRule.shouldNotNotify()) {
return if (safeRule.enabled) {
NOTIFICATION_OFF_INDEX
@ -108,7 +110,7 @@ class PushRulePreference : VectorPreference {
val safeKind = ruleAndKind?.kind ?: return null
return if (index != ruleStatusIndex) {
if (safeRule.ruleId == PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
if (safeRule.ruleId == RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
when (index) {
NOTIFICATION_OFF_INDEX -> {
safeRule.copy(enabled = true)
@ -128,7 +130,7 @@ class PushRulePreference : VectorPreference {
}
} else {
if (NOTIFICATION_OFF_INDEX == index) {
if (safeKind == RuleSetKey.UNDERRIDE || safeRule.ruleId == PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
if (safeKind == RuleSetKey.UNDERRIDE || safeRule.ruleId == RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
safeRule.setNotify(false)
} else {
safeRule.copy(enabled = false)
@ -137,15 +139,15 @@ class PushRulePreference : VectorPreference {
val newRule = safeRule.copy(enabled = true)
.setNotify(true)
.setHighlight(safeKind != RuleSetKey.UNDERRIDE
&& safeRule.ruleId != PushRule.RULE_ID_INVITE_ME
&& safeRule.ruleId != RuleIds.RULE_ID_INVITE_ME
&& NOTIFICATION_NOISY_INDEX == index)
if (NOTIFICATION_NOISY_INDEX == index) {
newRule.setNotificationSound(
if (safeRule.ruleId == PushRule.RULE_ID_CALL) {
PushRule.ACTION_VALUE_RING
if (safeRule.ruleId == RuleIds.RULE_ID_CALL) {
Action.ACTION_OBJECT_VALUE_VALUE_RING
} else {
PushRule.ACTION_VALUE_DEFAULT
Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT
}
)
} else {

View file

@ -16,13 +16,13 @@
package im.vector.app.features.settings
import androidx.preference.Preference
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
import im.vector.app.R
import im.vector.app.core.preference.PushRulePreference
import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.utils.toast
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
import javax.inject.Inject
class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor()
@ -92,17 +92,17 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor()
companion object {
// preference name <-> rule Id
private val prefKeyToPushRuleId = mapOf(
"SETTINGS_PUSH_RULE_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY" to PushRule.RULE_ID_CONTAIN_DISPLAY_NAME,
"SETTINGS_PUSH_RULE_CONTAINING_MY_USER_NAME_PREFERENCE_KEY" to PushRule.RULE_ID_CONTAIN_USER_NAME,
"SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to PushRule.RULE_ID_ONE_TO_ONE_ROOM,
"SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to PushRule.RULE_ID_ALL_OTHER_MESSAGES_ROOMS,
"SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to PushRule.RULE_ID_INVITE_ME,
"SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to PushRule.RULE_ID_CALL,
"SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS,
"SETTINGS_PUSH_RULE_MESSAGES_CONTAINING_AT_ROOM_PREFERENCE_KEY" to PushRule.RULE_ID_AT_ROOMS,
"SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to PushRule.RULE_ID_E2E_ONE_TO_ONE_ROOM,
"SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to PushRule.RULE_ID_E2E_GROUP,
"SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to PushRule.RULE_ID_TOMBSTONE
"SETTINGS_PUSH_RULE_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME,
"SETTINGS_PUSH_RULE_CONTAINING_MY_USER_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_USER_NAME,
"SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ROOM,
"SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS,
"SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_INVITE_ME,
"SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to RuleIds.RULE_ID_CALL,
"SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS,
"SETTINGS_PUSH_RULE_MESSAGES_CONTAINING_AT_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_ROOM_NOTIF,
"SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM,
"SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED,
"SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to RuleIds.RULE_ID_TOMBSTONE
)
}
}

View file

@ -26,11 +26,11 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.features.notifications.toNotificationAction
import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushRule
@EpoxyModelClass(layout = R.layout.item_pushrule_raw)
abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() {
@ -68,7 +68,7 @@ abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() {
val description = StringBuffer()
pushRule.conditions?.forEachIndexed { i, condition ->
if (i > 0) description.append("\n")
description.append(condition.asExecutableCondition()?.technicalDescription()
description.append(condition.asExecutableCondition(pushRule)?.technicalDescription()
?: "UNSUPPORTED")
}
if (description.isBlank()) {