This commit is contained in:
Valere
2019-06-19 10:46:59 +02:00
committed by Benoit Marty
parent 79735c6338
commit 0e46fc4c0a
112 changed files with 3957 additions and 249 deletions

View File

@ -21,7 +21,10 @@ import androidx.lifecycle.ProcessLifecycleOwner
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.internal.auth.AuthModule
import im.vector.matrix.android.internal.di.MatrixKoinComponent
@ -30,7 +33,9 @@ import im.vector.matrix.android.internal.di.MatrixModule
import im.vector.matrix.android.internal.di.NetworkModule
import im.vector.matrix.android.internal.network.UserAgentHolder
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import org.koin.standalone.get
import org.koin.standalone.inject
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
/**
@ -56,7 +61,9 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent {
currentSession = it
it.open()
it.setFilter(FilterService.FilterPreset.RiotFilter)
it.startSync()
//TODO check if using push or not (should pause if we use push)
// it.shoudPauseOnBackground(false)
// it.startSync()
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.pushrules.rest.PushRule
import timber.log.Timber
class Action(val type: Type) {
enum class Type(val value: String) {
NOTIFY("notify"),
DONT_NOTIFY("dont_notify"),
COALESCE("coalesce"),
SET_TWEAK("set_tweak");
companion object {
fun safeValueOf(value: String): Type? {
try {
return valueOf(value)
} catch (e: IllegalArgumentException) {
return null
}
}
}
}
var tweak_action: String? = null
var stringValue: String? = null
var boolValue: Boolean? = null
}
fun PushRule.domainActions(): List<Action>? {
val actions = ArrayList<Action>()
this.actions.forEach { actionStrOrObj ->
if (actionStrOrObj is String) {
val action = when (actionStrOrObj) {
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
else -> {
Timber.w("Unsupported action type ${actionStrOrObj}")
null
}
}?.let {
actions.add(it)
}
} else if (actionStrOrObj is Map<*, *>) {
val tweakAction = actionStrOrObj["set_tweak"] as? String
when (tweakAction) {
"sound" -> {
(actionStrOrObj["value"] as? String)?.let { stringValue ->
Action(Action.Type.SET_TWEAK).also {
it.tweak_action = "sound"
it.stringValue = stringValue
actions.add(it)
}
}
}
"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}")
}
}
} else {
Timber.w("Unsupported action type ${actionStrOrObj}")
return null
}
}
return if (actions.isEmpty()) null else actions
}

View File

@ -0,0 +1,51 @@
/*
* 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
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"),
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
else -> UNRECOGNIZE
}
}
}
}
abstract fun isSatisfied(event: Event): Boolean
companion object {
//TODO factory methods?
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.internal.di.MoshiProvider
import timber.log.Timber
class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.EVENT_MATCH) {
override fun isSatisfied(event: Event): Boolean {
//TODO encrypted events?
val rawJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJsonValue(event) as? Map<*, *>
?: return false
val value = extractField(rawJson, key) ?: return false
//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)
} catch (e: Throwable) {
//e.g PatternSyntaxException
Timber.e(e, "Failed to evaluate push condition")
return false
}
}
private fun extractField(jsonObject: Map<*, *>, fieldPath: String): String? {
val fieldParts = fieldPath.split(".")
if (fieldParts.isEmpty()) return null
var jsonElement: Map<*, *> = jsonObject
fieldParts.forEachIndexed { index, pathSegment ->
if (index == fieldParts.lastIndex) {
return jsonElement[pathSegment]?.toString()
} else {
val sub = jsonElement[pathSegment] ?: return null
if (sub is Map<*, *>) {
jsonElement = sub
} else {
return null
}
}
}
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 (i in 0 until glob.length) {
val c = glob[i]
when (c) {
'*' -> out += ".*"
'?' -> out += '.'.toString()
'.' -> out += "\\."
'\\' -> out += "\\\\"
else -> out += c
}
}
out += ""//'$'.toString()
return out
}
}
}

View File

@ -0,0 +1,2 @@
package im.vector.matrix.android.api.pushrules

View File

@ -0,0 +1,35 @@
/*
* 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
interface PushRuleService {
//TODO get push rule set
//TODO update rule
fun addPushRuleListener(listener: PushRuleListener)
fun removePushRuleListener(listener: PushRuleListener)
interface PushRuleListener {
fun onMatchRule(event: Event, actions: List<Action>)
fun batchFinish()
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.pushrules.rest.PushRule
interface PushRulesProvider {
fun getOrderedPushrules(): List<PushRule>
fun onRulesUpdate(newRules: List<PushRule>)
}

View File

@ -0,0 +1,26 @@
/*
* 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
enum class RulesetKey(val value: String) {
CONTENT("content"),
OVERRIDE("override"),
ROOM("room"),
SENDER("sender"),
UNDERRIDE("underride"),
UNKNOWN("")
}

View File

@ -0,0 +1,54 @@
/*
* 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.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 timber.log.Timber
@JsonClass(generateAdapter = true)
data class PushCondition(
//Required. The kind of condition to apply.
val kind: String,
//Required for event_match conditions. The dot- separated field of the event to match.
val key: String? = null,
//Required for event_match conditions.
val pattern: String? = null,
//Required for room_member_count conditions.
// A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
// A prefix of < matches rooms where the member count is strictly less than the given number and so forth. If no prefix is present, this parameter defaults to ==.
@Json(name = "is") val iz: String? = null
) {
fun asExecutableCondition(): Condition? {
return when (Condition.Kind.fromString(this.kind)) {
Condition.Kind.EVENT_MATCH -> {
if (this.key != null && this.pattern != null) {
EventMatchCondition(key, pattern)
} else {
Timber.e("Malformed Event match condition")
null
}
}
Condition.Kind.CONTAINS_DISPLAY_NAME -> TODO()
Condition.Kind.ROOM_MEMBER_COUNT -> TODO()
Condition.Kind.SENDER_NOTIFICATION_PERMISSION -> TODO()
Condition.Kind.UNRECOGNIZE -> null
}
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PushRule(
//Required. The domainActions to perform when this rule is matched.
val actions: List<Any>,
//Required. Whether this is a default rule, or has been set explicitly.
val default: Boolean,
//Required. Whether the push rule is enabled or not.
val enabled: Boolean,
//Required. The ID of this rule.
@Json(name = "rule_id") val ruleId: String,
//The conditions that must hold true for an event in order for a rule to be applied to an event
val conditions: List<PushCondition>? = null,
//The glob-style pattern to match against. Only applicable to content rules.
val pattern: String? = null
)

View File

@ -0,0 +1,30 @@
/*
* 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.rest
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.pushrules.rest.Ruleset
/**
* All push rulesets for a user.
*/
@JsonClass(generateAdapter = true)
data class PushruleResponse(
//Global rules, account level applying to all devices
val global: Ruleset,
//Device specific rules, apply only to current device
val device: Ruleset? = null
)

View File

@ -0,0 +1,27 @@
/*
* 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.rest
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Ruleset(
val content: List<PushRule>? = null,
val override: List<PushRule>? = null,
val room: List<PushRule>? = null,
val sender: List<PushRule>? = null,
val underride: List<PushRule>? = null
)

View File

@ -19,11 +19,13 @@ package im.vector.matrix.android.api.session
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.signout.SignOutService
@ -43,7 +45,9 @@ interface Session :
CryptoService,
CacheService,
SignOutService,
FilterService {
FilterService,
PushRuleService,
PushersService {
/**
* The params associated to the session
@ -56,17 +60,35 @@ interface Session :
@MainThread
fun open()
/**
* This method start the sync thread.
*/
@MainThread
fun startSync()
// /**
// * This method start the sync thread.
// */
// @MainThread
// fun startSync()
//
//
// fun isSyncThreadAlice() : Boolean
// fun syncThreadState() : String
//
//// fun pauseSync()
//// fun resumeSync()
//
// fun shoudPauseOnBackground(shouldPause: Boolean)
/**
* This method stop the sync thread.
* Configures the sync long pooling options
* @param timoutMS The maximum time to wait, in milliseconds, before returning the sync request.
* If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
* If set to 0 the server will return immediately even if the response is empty.
* @param delayMs When the server responds to a sync request, the client waits for `longPoolDelay` before calling a new sync.
*/
@MainThread
fun stopSync()
// fun configureSyncLongPooling(timoutMS : Long, delayMs : Long )
// /**
// * This method stop the sync thread.
// */
// @MainThread
// fun stopSync()
/**
* This method allows to listen the sync state.

View File

@ -0,0 +1,44 @@
/*
* 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.session.pushers
data class Pusher(
val userId: String,
val pushKey: String,
val kind: String,
val appId: String,
val appDisplayName: String,
val deviceDisplayName: String,
val profileTag: String? = null,
val lang: String,
val data: PusherData,
val state: PusherState
)
enum class PusherState {
UNREGISTRED,
REGISTERING,
REGISTERED,
FAILED_TO_REGISTER
}
data class PusherData(
val url: String? = null,
val format: String? = null
)

View File

@ -0,0 +1,60 @@
/*
* 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.session.pushers
import androidx.lifecycle.LiveData
import java.util.*
interface PushersService {
/**
* Refresh pushers from server state
*/
fun refreshPushers()
/**
* Add a new HTTP pusher.
*
* @param pushkey the pushkey
* @param appId the application id
* @param profileTag the profile tag
* @param lang the language
* @param appDisplayName a human-readable application name
* @param deviceDisplayName a human-readable device name
* @param url the URL that should be used to send notifications
* @param append append the pusher
* @param withEventIdOnly true to limit the push content
*
* @return A work request uuid. Can be used to listen to the status
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
*/
fun addHttpPusher(pushkey: String,
appId: String,
profileTag: String,
lang: String,
appDisplayName: String,
deviceDisplayName: String,
url: String,
append: Boolean,
withEventIdOnly: Boolean): UUID
companion object {
const val EVENT_ID_ONLY = "event_id_only"
}
fun livePushers(): LiveData<List<Pusher>>
}

View File

@ -0,0 +1,63 @@
/*
* 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 im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PusherData
import im.vector.matrix.android.internal.database.model.PusherDataEntity
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.session.pushers.JsonPusher
internal object PushersMapper {
fun map(pushEntity: PusherEntity): Pusher {
return Pusher(
userId = pushEntity.userId,
pushKey = pushEntity.pushKey,
kind = pushEntity.kind,
appId = pushEntity.appId,
appDisplayName = pushEntity.appDisplayName,
deviceDisplayName = pushEntity.deviceDisplayName,
profileTag = pushEntity.profileTag,
lang = pushEntity.lang,
data = PusherData(pushEntity.data?.url, pushEntity.data?.format),
state = pushEntity.state
)
}
fun map(pusher: JsonPusher, userId: String): PusherEntity {
return PusherEntity(
userId = userId,
pushKey = pusher.pushKey,
kind = pusher.kind,
appId = pusher.appId,
appDisplayName = pusher.appDisplayName,
deviceDisplayName = pusher.deviceDisplayName,
profileTag = pusher.profileTag,
lang = pusher.lang,
data = PusherDataEntity(pusher.data.url, pusher.data.format)
)
}
}
internal fun PusherEntity.asDomain(): Pusher {
return PushersMapper.map(this)
}
internal fun JsonPusher.toEntity(userId: String): PusherEntity {
return PushersMapper.map(this, userId)
}

View File

@ -0,0 +1,29 @@
/*
* 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.model
import io.realm.RealmObject
internal open class PushConditionEntity(
var kind: String = "",
var key: String? = null,
var pattern: String? = null,
var iz: String? = null
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,39 @@
/*
* 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.model
import io.realm.RealmList
import io.realm.RealmObject
internal open class PushRuleEntity(
//Required. The domainActions to perform when this rule is matched.
var actionsStr: String? = null,
//Required. Whether this is a default rule, or has been set explicitly.
var default: Boolean = false,
//Required. Whether the push rule is enabled or not.
var enabled: Boolean = true,
//Required. The ID of this rule.
var ruleId: String = "",
//The conditions that must hold true for an event in order for a rule to be applied to an event
var conditions: RealmList<PushConditionEntity>? = RealmList(),
//The glob-style pattern to match against. Only applicable to content rules.
var pattern: String? = null
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,30 @@
/*
* 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.model
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Index
internal open class PushRulesEntity(
@Index var userId: String = "",
var scope: String = "",
var rulesetKey: String = "",
var pushRules: RealmList<PushRulesEntity> = RealmList()
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,25 @@
/*
* 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.model
import io.realm.RealmObject
internal open class PusherDataEntity(
var url: String? = null,
var format: String? = null
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,58 @@
/*
* 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.model
import im.vector.matrix.android.api.session.pushers.PusherState
import io.realm.RealmObject
import io.realm.annotations.Index
//TODO
// at java.lang.Thread.run(Thread.java:764)
// Caused by: java.lang.IllegalArgumentException: 'value' is not a valid managed object.
// at io.realm.ProxyState.checkValidObject(ProxyState.java:213)
// at io.realm.im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.realmSet$data(im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.java:413)
// at im.vector.matrix.android.internal.database.model.PusherEntity.setData(PusherEntity.kt:16)
// at im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70)
// at io.realm.Realm.executeTransaction(Realm.java:1493)
internal open class PusherEntity(
@Index var userId: String = "",
var pushKey: String = "",
var kind: String = "",
var appId: String = "",
var appDisplayName: String = "",
var deviceDisplayName: String = "",
var profileTag: String? = null,
var lang: String = "",
var data: PusherDataEntity? = null
) : RealmObject() {
private var stateStr: String = PusherState.UNREGISTRED.name
var state: PusherState
get() {
try {
return PusherState.valueOf(stateStr)
} catch (e: Exception) {
//can this happen?
return PusherState.UNREGISTRED
}
}
set(value) {
stateStr = value.name
}
companion object
}

View File

@ -36,6 +36,11 @@ import io.realm.annotations.RealmModule
UserEntity::class,
EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class
EditAggregatedSummaryEntity::class,
PushRulesEntity::class,
PushRuleEntity::class,
PushConditionEntity::class,
PusherEntity::class,
PusherDataEntity::class
])
internal class SessionRealmModule

View File

@ -53,6 +53,14 @@ internal fun EventEntity.Companion.where(realm: Realm,
}
internal fun EventEntity.Companion.types(realm: Realm,
typeList: List<String> = emptyList()): RealmQuery<EventEntity> {
val query = realm.where<EventEntity>()
query.`in`(EventEntityFields.TYPE, typeList.toTypedArray())
return query
}
internal fun EventEntity.Companion.latestEvent(realm: Realm,
roomId: String,
includedTypes: List<String> = emptyList(),

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.query
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
internal fun PusherEntity.Companion.where(realm: Realm, userId: String, pushKey: String? = null): RealmQuery<PusherEntity> {
return realm.where<PusherEntity>()
.equalTo(PusherEntityFields.USER_ID, userId)
.apply {
if (pushKey != null) {
equalTo(PusherEntityFields.PUSH_KEY, pushKey)
}
}
}

View File

@ -24,6 +24,7 @@ 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.listeners.ProgressListener
import im.vector.matrix.android.api.pushrules.PushRuleService
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
@ -36,6 +37,8 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.group.Group
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService
@ -65,6 +68,7 @@ import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.di.MatrixKoinHolder
import im.vector.matrix.android.internal.session.content.ContentModule
import im.vector.matrix.android.internal.session.group.GroupModule
import im.vector.matrix.android.internal.session.notification.BingRuleWatcher
import im.vector.matrix.android.internal.session.room.RoomModule
import im.vector.matrix.android.internal.session.signout.SignOutModule
import im.vector.matrix.android.internal.session.sync.SyncModule
@ -73,6 +77,7 @@ import im.vector.matrix.android.internal.session.user.UserModule
import org.koin.core.scope.Scope
import org.koin.standalone.inject
import timber.log.Timber
import java.util.*
internal class DefaultSession(override val sessionParams: SessionParams) : Session, MatrixKoinComponent {
@ -96,8 +101,12 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
private val syncThread by inject<SyncThread>()
private val contentUrlResolver by inject<ContentUrlResolver>()
private val contentUploadProgressTracker by inject<ContentUploadStateTracker>()
private val pushRuleService by inject<PushRuleService>()
private val pushersService by inject<PushersService>()
private var isOpen = false
private val bingRuleWatcher by inject<BingRuleWatcher>()
@MainThread
override fun open() {
assertMainThread()
@ -124,19 +133,48 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
monarchy.openManually()
}
liveEntityUpdaters.forEach { it.start() }
bingRuleWatcher.start()
}
@MainThread
override fun startSync() {
assert(isOpen)
syncThread.start()
}
// @MainThread
// override fun startSync() {
// assert(isOpen)
// if (!syncThread.isAlive) {
// syncThread.start()
// } else {
// syncThread.restart()
// Timber.w("Attempt to start an already started thread")
// }
// }
//
// override fun isSyncThreadAlice(): Boolean = syncThread.isAlive
//
// override fun syncThreadState(): String = syncThread.getSyncState()
//
// override fun shoudPauseOnBackground(shouldPause: Boolean) {
// //TODO check if using push or not (should pause if we use push)
// syncThread.shouldPauseOnBackground = shouldPause
// }
@MainThread
override fun stopSync() {
assert(isOpen)
syncThread.kill()
}
// override fun resumeSync() {
// assert(isOpen)
// syncThread.restart()
// }
//
// override fun pauseSync() {
// assert(isOpen)
// syncThread.pause()
// }
// override fun configureSyncLongPooling(timoutMS: Long, delayMs: Long) {
// syncThread.configureLongPoolingSettings(timoutMS, delayMs)
// }
//
// @MainThread
// override fun stopSync() {
// assert(isOpen)
// syncThread.kill()
// }
@MainThread
override fun close() {
@ -144,6 +182,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
assert(isOpen)
liveEntityUpdaters.forEach { it.dispose() }
cryptoService.close()
bingRuleWatcher.dispose()
if (monarchy.isMonarchyThreadOpen) {
monarchy.closeManually()
}
@ -160,8 +199,8 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
Timber.w("SIGN_OUT: start")
assert(isOpen)
Timber.w("SIGN_OUT: kill sync thread")
syncThread.kill()
//Timber.w("SIGN_OUT: kill sync thread")
//syncThread.kill()
Timber.w("SIGN_OUT: call webservice")
return signOutService.signOut(object : MatrixCallback<Unit> {
@ -261,11 +300,11 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
override fun clearCache(callback: MatrixCallback<Unit>) {
assert(isOpen)
syncThread.pause()
// syncThread.pause()
cacheService.clearCache(object : MatrixCallbackDelegate<Unit>(callback) {
override fun onSuccess(data: Unit) {
// Restart the sync
syncThread.restart()
// syncThread.restart()
super.onSuccess(data)
}
@ -426,6 +465,16 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
cryptoService.clearCryptoCache(callback)
}
// PUSH RULE SERVICE
override fun addPushRuleListener(listener: PushRuleService.PushRuleListener) {
pushRuleService.addPushRuleListener(listener)
}
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
pushRuleService.removePushRuleListener(listener)
}
// Private methods *****************************************************************************
private fun assertMainThread() {
@ -434,4 +483,28 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
}
}
override fun refreshPushers() {
pushersService.refreshPushers()
}
override fun addHttpPusher(
pushkey: String,
appId: String,
profileTag: String,
lang: String,
appDisplayName: String,
deviceDisplayName: String,
url: String,
append: Boolean,
withEventIdOnly: Boolean): UUID {
return pushersService
.addHttpPusher(
pushkey, appId, profileTag, lang, appDisplayName, deviceDisplayName, url, append, withEventIdOnly
)
}
override fun livePushers(): LiveData<List<Pusher>> {
return pushersService.livePushers()
}
}

View File

@ -19,8 +19,11 @@ package im.vector.matrix.android.internal.session
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
import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.signout.SignOutService
@ -34,6 +37,14 @@ import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
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.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.GetPushersTask
import im.vector.matrix.android.internal.session.pushers.PushersAPI
import im.vector.matrix.android.internal.session.room.*
import im.vector.matrix.android.internal.session.room.directory.DefaultGetPublicRoomTask
import im.vector.matrix.android.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask
@ -163,6 +174,21 @@ internal class SessionModule(private val sessionParams: SessionParams) {
retrofit.create(FilterApi::class.java)
}
scope(DefaultSession.SCOPE) {
MockPushRuleProvider() as PushRulesProvider
}
scope(DefaultSession.SCOPE) {
get<PushRulesManager>() as PushRuleService
}
scope(DefaultSession.SCOPE) {
PushRulesManager(get(), get())
}
scope(DefaultSession.SCOPE) {
BingRuleWatcher(get(), get(), get(), get())
}
scope(DefaultSession.SCOPE) {
val groupSummaryUpdater = GroupSummaryUpdater(get())
val userEntityUpdater = UserEntityUpdater(get(), get(), get())
@ -172,6 +198,16 @@ internal class SessionModule(private val sessionParams: SessionParams) {
listOf<LiveEntityObserver>(groupSummaryUpdater, userEntityUpdater, aggregationUpdater, eventsPruner)
}
scope(DefaultSession.SCOPE) {
get<Retrofit>().create(PushersAPI::class.java)
}
scope(DefaultSession.SCOPE) {
DefaultGetPusherTask(get()) as GetPushersTask
}
scope(DefaultSession.SCOPE) {
DefaultPusherService(get(), get(), get(), get()) as PushersService
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.auth.data.Credentials
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
internal class BingRuleWatcher(monarchy: Monarchy,
private val credentials: Credentials,
private val taskExecutor: TaskExecutor,
private val pushRulesManager: PushRulesManager) :
RealmLiveEntityObserver<EventEntity>(monarchy) {
override val query = Monarchy.Query<EventEntity> {
EventEntity.types(it, listOf(
EventType.REDACTION, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED)
)
}
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
//TODO task
inserted.map { it.asDomain() }.let {
pushRulesManager.processEvents(it)
}
}
}

View File

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

View File

@ -0,0 +1,85 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.notification
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.PushRulesProvider
import im.vector.matrix.android.api.pushrules.domainActions
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event
internal class PushRulesManager(
private val sessionParams: SessionParams,
private val pushRulesProvider: PushRulesProvider) : PushRuleService {
private var listeners = ArrayList<PushRuleService.PushRuleListener>()
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
listeners.remove(listener)
}
override fun addPushRuleListener(listener: PushRuleService.PushRuleListener) {
if (!listeners.contains(listener))
listeners.add(listener)
}
fun processEvents(events: List<Event>) {
var hasDoneSomething = false
events.forEach { event ->
fulfilledBingRule(event)?.let {
hasDoneSomething = true
dispatchBing(event, it)
}
}
if (hasDoneSomething)
dispatchFinish()
}
fun dispatchBing(event: Event, rule: PushRule) {
try {
listeners.forEach {
it.onMatchRule(event, rule.domainActions() ?: emptyList())
}
} catch (e: Throwable) {
}
}
fun dispatchFinish() {
try {
listeners.forEach {
it.batchFinish()
}
} catch (e: Throwable) {
}
}
fun fulfilledBingRule(event: Event): PushRule? {
pushRulesProvider.getOrderedPushrules().forEach { rule ->
rule.conditions?.mapNotNull { it.asExecutableCondition() }?.forEach {
if (it.isSatisfied(event)) return rule
}
}
return null
}
}

View File

@ -0,0 +1,98 @@
/*
* 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 android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.PusherDataEntity
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject
class AddHttpPusherWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params), MatrixKoinComponent {
@JsonClass(generateAdapter = true)
internal data class Params(
val pusher: JsonPusher,
val userId: String
)
private val pushersAPI by inject<PushersAPI>()
private val monarchy by inject<Monarchy>()
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()
val pusher = params.pusher
if (pusher.pushKey.isBlank()) {
return Result.failure()
}
val result = executeRequest<Map<String, Any>> {
apiCall = pushersAPI.setPusher(pusher)
}
return result.fold({
when (it) {
is Failure.NetworkConnection -> Result.retry()
else -> {
monarchy.runTransactionSync { realm ->
PusherEntity.where(realm, params.userId, pusher.pushKey).findFirst()?.let {
//update it
it.state = PusherState.FAILED_TO_REGISTER
}
}
//always return success, or the chain will be stuck for ever!
Result.failure()
}
}
}, {
monarchy.runTransactionSync { realm ->
val echo = PusherEntity.where(realm, params.userId, pusher.pushKey).findFirst()
if (echo != null) {
//update it
echo.appDisplayName = pusher.appDisplayName
echo.appId = pusher.appId
echo.kind = pusher.kind
echo.lang = pusher.lang
echo.profileTag = pusher.profileTag
echo.data = PusherDataEntity(pusher.data.url, pusher.data.format)
echo.state = PusherState.REGISTERED
} else {
pusher.toEntity(params.userId).also {
it.state = PusherState.REGISTERED
realm.insertOrUpdate(it)
}
}
}
Result.success()
})
}
}

View File

@ -0,0 +1,107 @@
/*
* 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 androidx.lifecycle.LiveData
import androidx.work.*
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.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import java.util.*
import java.util.concurrent.TimeUnit
internal class DefaultPusherService(
private val monarchy: Monarchy,
private val sessionParam: SessionParams,
private val getPusherTask: GetPushersTask,
private val taskExecutor: TaskExecutor
) : PushersService {
override fun refreshPushers() {
getPusherTask
.configureWith(Unit)
.dispatchTo(object : MatrixCallback<PushersResponse> {
override fun onSuccess(data: PushersResponse) {
monarchy.runTransactionSync { realm ->
//clear existings?
realm.where(PusherEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParam.credentials.userId)
.findAll().deleteAllFromRealm()
data.pushers?.forEach { jsonPusher ->
jsonPusher.toEntity(sessionParam.credentials.userId).also {
it.state = PusherState.REGISTERED
realm.insertOrUpdate(it)
}
}
}
}
})
.executeBy(taskExecutor)
}
/**
*
*/
override fun addHttpPusher(pushkey: String, appId: String, profileTag: String,
lang: String, appDisplayName: String, deviceDisplayName: String,
url: String, append: Boolean, withEventIdOnly: Boolean)
: UUID {
val pusher = JsonPusher(
pushKey = pushkey,
kind = "http",
appId = appId,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
profileTag = profileTag,
lang = lang,
data = JsonPusherData(url, if (withEventIdOnly) PushersService.EVENT_ID_ONLY else null),
append = append)
val params = AddHttpPusherWorker.Params(pusher, sessionParam.credentials.userId)
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val request = OneTimeWorkRequestBuilder<AddHttpPusherWorker>()
.setConstraints(constraints)
.setInputData(WorkerParamsFactory.toData(params))
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueue(request)
return request.id
}
override fun livePushers(): LiveData<List<Pusher>> {
return monarchy.findAllMappedWithChanges(
{ realm -> PusherEntity.where(realm, sessionParam.credentials.userId) },
{ it.asDomain() }
)
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
internal interface GetPushersTask : Task<Unit, PushersResponse>
internal class DefaultGetPusherTask(private val pushersAPI: PushersAPI) : GetPushersTask {
override suspend fun execute(params: Unit): Try<PushersResponse> {
return executeRequest {
apiCall = pushersAPI.getPushers()
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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 com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* pushkey string Required. This is a unique identifier for this pusher. See /set for more detail. Max length, 512 bytes.
* kind string Required. The kind of pusher. "http" is a pusher that sends HTTP pokes.
* app_id string Required. This is a reverse-DNS style identifier for the application. Max length, 64 chars.
* app_display_name string Required. A string that will allow the user to identify what application owns this pusher.
* device_display_name string Required. A string that will allow the user to identify what device owns this pusher.
* profile_tag string This string determines which set of device specific rules this pusher executes.
* lang string Required. The preferred language for receiving notifications (e.g. 'en' or 'en-US')
* data PusherData Required. A dictionary of information for the pusher implementation itself.
*
* <code>
* {
* "pushers": [
* {
* "pushkey": "Xp/MzCt8/9DcSNE9cuiaoT5Ac55job3TdLSSmtmYl4A=",
* "kind": "http",
* "app_id": "face.mcapp.appy.prod",
* "app_display_name": "Appy McAppface",
* "device_display_name": "Alice's Phone",
* "profile_tag": "xyz",
* "lang": "en-US",
* "data": {
* "url": "https://example.com/_matrix/push/v1/notify"
* }
* }]
* }
* </code>
*/
@JsonClass(generateAdapter = true)
internal data class JsonPusher(
@Json(name = "pushkey") val pushKey: String,
@Json(name = "kind") val kind: String,
@Json(name = "app_id") val appId: String,
@Json(name = "app_display_name") val appDisplayName: String,
@Json(name = "device_display_name") val deviceDisplayName: String,
@Json(name = "profile_tag") val profileTag: String? = null,
@Json(name = "lang") val lang: String,
@Json(name = "data") val data: JsonPusherData,
// Only used to update add Pusher (body of api request)
// Used If true, the homeserver should add another pusher with the given pushkey and App ID in addition
// to any others with different user IDs.
// Otherwise, the homeserver must remove any other pushers with the same App ID and pushkey for different users.
// The default is false.
@Json(name = "append") val append: Boolean? = false
)

View File

@ -0,0 +1,27 @@
/*
* 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 com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class JsonPusherData(
//Required if kind is http. The URL to use to send notifications to.
@Json(name = "url") val url: String? = null,
//The format to use when sending notifications to the Push Gateway.
@Json(name = "format") val format: String? = null
)

View File

@ -0,0 +1,42 @@
/*
* 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.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
internal interface PushersAPI {
/**
* Get the pushers for this user.
*
* Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-thirdparty-protocols
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers")
fun getPushers(): Call<PushersResponse>
/**
* This endpoint allows the creation, modification and deletion of pushers for this user ID.
* The behaviour of this endpoint varies depending on the values in the JSON body.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set")
fun setPusher(@Body jsonPusher: JsonPusher): Call<Map<String, Any>>
}

View File

@ -0,0 +1,25 @@
/*
* 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 com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal class PushersResponse(
@Json(name = "pushers") val pushers: List<JsonPusher>? = null
)

View File

@ -29,7 +29,7 @@ import im.vector.matrix.android.internal.task.Task
internal interface SyncTask : Task<SyncTask.Params, SyncResponse> {
data class Params(val token: String?)
data class Params(val token: String?, var timeout: Long = 30_1000L)
}
@ -42,10 +42,10 @@ internal class DefaultSyncTask(private val syncAPI: SyncAPI,
override suspend fun execute(params: SyncTask.Params): Try<SyncResponse> {
val requestParams = HashMap<String, String>()
var timeout = 0
var timeout = 0L
if (params.token != null) {
requestParams["since"] = params.token
timeout = 30000
timeout = params.timeout
}
requestParams["timeout"] = timeout.toString()
requestParams["filter"] = filterRepository.getFilter()

View File

@ -0,0 +1,238 @@
/*
* 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.sync.job
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import com.squareup.moshi.JsonEncodingException
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import org.koin.standalone.inject
import timber.log.Timber
import java.net.SocketTimeoutException
import java.util.*
import kotlin.collections.ArrayList
private const val DEFAULT_LONG_POOL_TIMEOUT = 10_000L
private const val BACKGROUND_LONG_POOL_TIMEOUT = 0L
/**
* Can execute periodic sync task.
* An IntentService is used in conjunction with the AlarmManager and a Broadcast Receiver
* in order to be able to perform a sync even if the app is not running.
* The <receiver> and <service> must be declared in the Manifest or the app using the SDK
*/
open class SyncService : Service(), MatrixKoinComponent {
private var mIsSelfDestroyed: Boolean = false
private var cancelableTask: Cancelable? = null
private val syncTokenStore: SyncTokenStore by inject()
private val syncTask: SyncTask by inject()
private val networkConnectivityChecker: NetworkConnectivityChecker by inject()
private val taskExecutor: TaskExecutor by inject()
private var localBinder = LocalBinder()
var timer = Timer()
var nextBatchDelay = 0L
var timeout = 10_000L
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.i("onStartCommand ${intent}")
nextBatchDelay = 60_000L
timeout = 0
intent?.let {
if (cancelableTask == null) {
timer.cancel()
timer = Timer()
doSync()
} else {
//Already syncing ignore
Timber.i("Received a start while was already syncking... ignore")
}
}
//No intent just start the service, an alarm will should call with intent
return START_STICKY
}
override fun onDestroy() {
Timber.i("## onDestroy() : $this")
if (!mIsSelfDestroyed) {
Timber.w("## Destroy by the system : $this")
}
cancelableTask?.cancel()
super.onDestroy()
}
fun stopMe() {
timer.cancel()
timer = Timer()
cancelableTask?.cancel()
mIsSelfDestroyed = true
stopSelf()
}
fun doSync() {
var nextBatch = syncTokenStore.getLastToken()
if (!networkConnectivityChecker.isConnected()) {
Timber.v("Sync is Paused. Waiting...")
//TODO Retry in ?
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, 10_000L)
} else {
Timber.v("Execute sync request with token $nextBatch and timeout $timeout")
val params = SyncTask.Params(nextBatch, timeout)
cancelableTask = syncTask.configureWith(params)
.callbackOn(TaskThread.CALLER)
.executeOn(TaskThread.CALLER)
.dispatchTo(object : MatrixCallback<SyncResponse> {
override fun onSuccess(data: SyncResponse) {
cancelableTask = null
nextBatch = data.nextBatch
syncTokenStore.saveToken(nextBatch)
localBinder.notifySyncFinish()
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, nextBatchDelay)
}
override fun onFailure(failure: Throwable) {
Timber.e(failure)
cancelableTask = null
localBinder.notifyFailure(failure)
if (failure is Failure.NetworkConnection
&& failure.cause is SocketTimeoutException) {
// Timeout are not critical
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, 10_000L)
}
if (failure !is Failure.NetworkConnection
|| failure.cause is JsonEncodingException) {
// Wait 10s before retrying
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, 10_000L)
}
if (failure is Failure.ServerError
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// No token or invalid token, stop the thread
stopSelf()
}
}
})
.executeBy(taskExecutor)
}
}
override fun onBind(intent: Intent?): IBinder {
return localBinder
}
inner class LocalBinder : Binder() {
private var listeners = ArrayList<SyncListener>()
fun addListener(listener: SyncListener) {
if (!listeners.contains(listener)) {
listeners.add(listener)
}
}
fun removeListener(listener: SyncListener) {
listeners.remove(listener)
}
internal fun notifySyncFinish() {
listeners.forEach {
try {
it.onSyncFinsh()
} catch (t: Throwable) {
Timber.e("Failed to notify listener $it")
}
}
}
internal fun notifyNetworkNotAvailable() {
listeners.forEach {
try {
it.networkNotAvailable()
} catch (t: Throwable) {
Timber.e("Failed to notify listener $it")
}
}
}
internal fun notifyFailure(throwable: Throwable) {
listeners.forEach {
try {
it.onFailed(throwable)
} catch (t: Throwable) {
Timber.e("Failed to notify listener $it")
}
}
}
fun getService(): SyncService = this@SyncService
}
interface SyncListener {
fun onSyncFinsh()
fun networkNotAvailable()
fun onFailed(throwable: Throwable)
}
companion object {
fun startLongPool(delay: Long) {
}
}
}

View File

@ -0,0 +1,201 @@
package im.vector.matrix.android.internal.session.sync.job
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import com.squareup.moshi.JsonEncodingException
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import org.koin.standalone.inject
import timber.log.Timber
import java.net.SocketTimeoutException
import java.util.*
import kotlin.collections.ArrayList
private const val DEFAULT_LONG_POOL_TIMEOUT = 10_000L
private const val BACKGROUND_LONG_POOL_TIMEOUT = 0L
/**
* Can execute periodic sync task.
* An IntentService is used in conjunction with the AlarmManager and a Broadcast Receiver
* in order to be able to perform a sync even if the app is not running.
* The <receiver> and <service> must be declared in the Manifest or the app using the SDK
*/
open class SyncServiceOld : Service(), MatrixKoinComponent {
private var mIsSelfDestroyed: Boolean = false
private var cancelableTask: Cancelable? = null
private val syncTokenStore: SyncTokenStore by inject()
private val syncTask: SyncTask by inject()
private val networkConnectivityChecker: NetworkConnectivityChecker by inject()
private val taskExecutor: TaskExecutor by inject()
private var localBinder = LocalBinder()
val timer = Timer()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.i("onStartCommand ${intent}")
intent?.let {
if (cancelableTask == null) {
doSync(BACKGROUND_LONG_POOL_TIMEOUT)
} else {
//Already syncing ignore
Timber.i("Received a start while was already syncking... ignore")
}
}
//No intent just start the service, an alarm will should call with intent
return START_STICKY
}
override fun onDestroy() {
Timber.i("## onDestroy() : $this")
if (!mIsSelfDestroyed) {
Timber.w("## Destroy by the system : $this")
}
cancelableTask?.cancel()
super.onDestroy()
}
fun stopMe() {
cancelableTask?.cancel()
mIsSelfDestroyed = true
stopSelf()
}
fun doSync(currentLongPoolTimeoutMs: Long = DEFAULT_LONG_POOL_TIMEOUT) {
var nextBatch = syncTokenStore.getLastToken()
if (!networkConnectivityChecker.isConnected()) {
Timber.v("Sync is Paused. Waiting...")
//TODO Retry in ?
} else {
Timber.v("Execute sync request with token $nextBatch and timeout $currentLongPoolTimeoutMs")
val params = SyncTask.Params(nextBatch, currentLongPoolTimeoutMs)
cancelableTask = syncTask.configureWith(params)
.callbackOn(TaskThread.CALLER)
.executeOn(TaskThread.CALLER)
.dispatchTo(object : MatrixCallback<SyncResponse> {
override fun onSuccess(data: SyncResponse) {
cancelableTask = null
nextBatch = data.nextBatch
syncTokenStore.saveToken(nextBatch)
localBinder.notifySyncFinish()
}
override fun onFailure(failure: Throwable) {
Timber.e(failure)
cancelableTask = null
localBinder.notifyFailure(failure)
// if (failure is Failure.NetworkConnection
// && failure.cause is SocketTimeoutException) {
// // Timeout are not critical
// localBinder.notifyFailure()
// }
//
// if (failure !is Failure.NetworkConnection
// || failure.cause is JsonEncodingException) {
// // Wait 10s before retrying
//// Thread.sleep(RETRY_WAIT_TIME_MS)
// //TODO Retry in 10S?
// }
//
// if (failure is Failure.ServerError
// && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// // No token or invalid token, stop the thread
// stopSelf()
// }
}
})
.executeBy(taskExecutor)
//TODO return and schedule a new one?
// Timber.v("Waiting for $currentLongPoolDelayMs delay before new pool...")
// if (currentLongPoolDelayMs > 0) Thread.sleep(currentLongPoolDelayMs)
// Timber.v("...Continue")
}
}
override fun onBind(intent: Intent?): IBinder {
return localBinder
}
inner class LocalBinder : Binder() {
private var listeners = ArrayList<SyncListener>()
fun addListener(listener: SyncListener) {
if (!listeners.contains(listener)) {
listeners.add(listener)
}
}
fun removeListener(listener: SyncListener) {
listeners.remove(listener)
}
internal fun notifySyncFinish() {
listeners.forEach {
try {
it.onSyncFinsh()
} catch (t: Throwable) {
Timber.e("Failed to notify listener $it")
}
}
}
internal fun notifyNetworkNotAvailable() {
listeners.forEach {
try {
it.networkNotAvailable()
} catch (t: Throwable) {
Timber.e("Failed to notify listener $it")
}
}
}
internal fun notifyFailure(throwable: Throwable) {
listeners.forEach {
try {
it.onFailed(throwable)
} catch (t: Throwable) {
Timber.e("Failed to notify listener $it")
}
}
}
fun getService(): SyncServiceOld = this@SyncServiceOld
}
interface SyncListener {
fun onSyncFinsh()
fun networkNotAvailable()
fun onFailed(throwable: Throwable)
}
companion object {
fun startLongPool(delay: Long) {
}
}
}

View File

@ -37,6 +37,13 @@ import java.net.SocketTimeoutException
import java.util.concurrent.CountDownLatch
private const val RETRY_WAIT_TIME_MS = 10_000L
private const val DEFAULT_LONG_POOL_TIMEOUT = 10_000L
private const val DEFAULT_LONG_POOL_DELAY = 0L
private const val DEFAULT_BACKGROUND_LONG_POOL_TIMEOUT = 0L
private const val DEFAULT_BACKGROUND_LONG_POOL_DELAY = 30_000L
internal class SyncThread(private val syncTask: SyncTask,
private val networkConnectivityChecker: NetworkConnectivityChecker,
@ -55,6 +62,27 @@ internal class SyncThread(private val syncTask: SyncTask,
updateStateTo(SyncState.IDLE)
}
/**
* The maximum time to wait, in milliseconds, before returning this request.
* If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
* If set to 0 the server will return immediately even if the response is empty.
*/
private var longPoolTimeoutMs = DEFAULT_LONG_POOL_TIMEOUT
/**
* When the server responds to a sync request, the client waits for `longPoolDelay` before calling a new sync.
*/
private var longPoolDelayMs = DEFAULT_LONG_POOL_DELAY
var shouldPauseOnBackground: Boolean = true
private var backgroundedLongPoolTimeoutMs = DEFAULT_BACKGROUND_LONG_POOL_TIMEOUT
private var backgroundedLongPoolDelayMs = DEFAULT_BACKGROUND_LONG_POOL_DELAY
private var currentLongPoolTimeoutMs = longPoolTimeoutMs
private var currentLongPoolDelayMs = longPoolDelayMs
fun restart() = synchronized(lock) {
if (state is SyncState.PAUSED) {
Timber.v("Resume sync...")
@ -65,6 +93,30 @@ internal class SyncThread(private val syncTask: SyncTask,
}
}
/**
* Configures the long pooling settings
*/
fun configureLongPoolingSettings(timoutMS: Long, delayMs: Long) {
longPoolTimeoutMs = Math.max(0, timoutMS)
longPoolDelayMs = Math.max(0, delayMs)
}
/**
* Configures the long pooling settings in background mode (used only if should not pause on BG)
*/
fun configureBackgroundeLongPoolingSettings(timoutMS: Long, delayMs: Long) {
backgroundedLongPoolTimeoutMs = Math.max(0, timoutMS)
backgroundedLongPoolDelayMs = Math.max(0, delayMs)
}
fun resetLongPoolingSettings() {
longPoolTimeoutMs = DEFAULT_LONG_POOL_TIMEOUT
longPoolDelayMs = DEFAULT_LONG_POOL_DELAY
backgroundedLongPoolTimeoutMs = DEFAULT_BACKGROUND_LONG_POOL_TIMEOUT
backgroundedLongPoolDelayMs = DEFAULT_BACKGROUND_LONG_POOL_DELAY
}
fun pause() = synchronized(lock) {
if (state is SyncState.RUNNING) {
Timber.v("Pause sync...")
@ -91,14 +143,14 @@ internal class SyncThread(private val syncTask: SyncTask,
while (state != SyncState.KILLING) {
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
Timber.v("Waiting...")
Timber.v("Sync is Paused. Waiting...")
synchronized(lock) {
lock.wait()
}
} else {
Timber.v("Execute sync request with token $nextBatch")
Timber.v("Execute sync request with token $nextBatch and timeout $currentLongPoolTimeoutMs")
val latch = CountDownLatch(1)
val params = SyncTask.Params(nextBatch)
val params = SyncTask.Params(nextBatch, currentLongPoolTimeoutMs)
cancelableTask = syncTask.configureWith(params)
.callbackOn(TaskThread.CALLER)
.executeOn(TaskThread.CALLER)
@ -135,10 +187,15 @@ internal class SyncThread(private val syncTask: SyncTask,
})
.executeBy(taskExecutor)
latch.await()
latch.await()
if (state is SyncState.RUNNING) {
updateStateTo(SyncState.RUNNING(catchingUp = false))
}
Timber.v("Waiting for $currentLongPoolDelayMs delay before new pool...")
if (currentLongPoolDelayMs > 0) sleep(currentLongPoolDelayMs)
Timber.v("...Continue")
}
}
Timber.v("Sync killed")
@ -159,14 +216,22 @@ internal class SyncThread(private val syncTask: SyncTask,
}
override fun onMoveToForeground() {
currentLongPoolTimeoutMs = longPoolTimeoutMs
currentLongPoolDelayMs = longPoolDelayMs
restart()
}
override fun onMoveToBackground() {
pause()
if (shouldPauseOnBackground) {
pause()
} else {
Timber.v("Slower sync in background mode")
//we continue but with a slower pace
currentLongPoolTimeoutMs = backgroundedLongPoolTimeoutMs
currentLongPoolDelayMs = backgroundedLongPoolDelayMs
}
}
}

View File

@ -0,0 +1,119 @@
/*
* 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.sync.job
import android.content.Context
import androidx.work.*
import arrow.core.failure
import arrow.core.recoverWith
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.filter.FilterRepository
import im.vector.matrix.android.internal.session.sync.SyncAPI
import im.vector.matrix.android.internal.session.sync.SyncResponseHandler
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject
import timber.log.Timber
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
private const val DEFAULT_LONG_POOL_TIMEOUT = 0L
class SyncWorker(context: Context,
workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters), MatrixKoinComponent {
@JsonClass(generateAdapter = true)
internal data class Params(
val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT
)
private val syncAPI by inject<SyncAPI>()
private val filterRepository by inject<FilterRepository>()
private val syncResponseHandler by inject<SyncResponseHandler>()
private val sessionParamsStore by inject<SessionParamsStore>()
private val syncTokenStore by inject<SyncTokenStore>()
val autoMode = false
override suspend fun doWork(): Result {
Timber.i("Sync work starting")
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: Params()
val requestParams = HashMap<String, String>()
requestParams["timeout"] = params.timeout.toString()
requestParams["filter"] = filterRepository.getFilter()
val token = syncTokenStore.getLastToken()?.also { requestParams["since"] = it }
Timber.i("Sync work last token $token")
return executeRequest<SyncResponse> {
apiCall = syncAPI.sync(requestParams)
}.recoverWith { throwable ->
// Intercept 401
if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete()
}
Timber.i("Sync work failed $throwable")
// Transmit the throwable
throwable.failure()
}.fold(
{
Timber.i("Sync work failed $it")
again()
if (it is Failure.NetworkConnection && it.cause is SocketTimeoutException) {
// Timeout are not critical
Result.Success()
} else {
Result.Success()
}
},
{
Timber.i("Sync work success next batch ${it.nextBatch}")
syncResponseHandler.handleResponse(it, token, false)
syncTokenStore.saveToken(it.nextBatch)
again()
Result.success()
}
)
}
fun again() {
if (autoMode) {
Timber.i("Sync work Again!!")
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInitialDelay(30_000, TimeUnit.MILLISECONDS)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.APPEND, workRequest)
}
}
}

View File

@ -0,0 +1,72 @@
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.di.MoshiProvider
import org.junit.Assert
import org.junit.Test
class PushRuleActionsTest {
@Test
fun test_action_parsing() {
val rawPushRule = """
{
"rule_id": ".m.rule.invite_for_me",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
},
{
"key": "content.membership",
"kind": "event_match",
"pattern": "invite"
},
{
"key": "state_key",
"kind": "event_match",
"pattern": "[the user's Matrix ID]"
}
],
"domainActions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight",
"value": false
}
]
}
""".trimIndent()
val pushRule = MoshiProvider.providesMoshi().adapter<PushRule>(PushRule::class.java).fromJson(rawPushRule)
Assert.assertNotNull("Should have parsed the rule", pushRule)
Assert.assertNotNull("Failed to parse domainActions", pushRule?.domainActions())
Assert.assertEquals(3, pushRule!!.domainActions()!!.size)
Assert.assertEquals("First action should be notify", Action.Type.NOTIFY, pushRule!!.domainActions()!![0].type)
Assert.assertEquals("Second action should be tweak", Action.Type.SET_TWEAK, pushRule!!.domainActions()!![1].type)
Assert.assertEquals("Second action tweak key should be sound", "sound", pushRule!!.domainActions()!![1].tweak_action)
Assert.assertEquals("Second action should have default as stringValue", "default", pushRule!!.domainActions()!![1].stringValue)
Assert.assertNull("Second action boolValue should be null", pushRule!!.domainActions()!![1].boolValue)
Assert.assertEquals("Third action should be tweak", Action.Type.SET_TWEAK, pushRule!!.domainActions()!![2].type)
Assert.assertEquals("Third action tweak key should be highlight", "highlight", pushRule!!.domainActions()!![2].tweak_action)
Assert.assertEquals("Third action tweak param should be false", false, pushRule!!.domainActions()!![2].boolValue)
Assert.assertNull("Third action stringValue should be null", pushRule!!.domainActions()!![2].stringValue)
}
}

View File

@ -0,0 +1,103 @@
package im.vector.matrix.android.api.pushrules
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.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import org.junit.Test
class PushrulesConditionTest {
@Test
fun test_eventmatch_type_condition() {
val condition = EventMatchCondition("type", "m.room.message")
val simpleTextEvent = Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "Yo wtf?").toContent(),
originServerTs = 0)
val rm = RoomMember(
Membership.INVITE,
displayName = "Foo",
avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"
)
val simpleRoomMemberEvent = Event(
type = "m.room.member",
eventId = "mx0",
stateKey = "@foo:matrix.org",
content = rm.toContent(),
originServerTs = 0)
assert(condition.isSatisfied(simpleTextEvent))
assert(!condition.isSatisfied(simpleRoomMemberEvent))
}
@Test
fun test_eventmatch_path_condition() {
val condition = EventMatchCondition("content.msgtype", "m.text")
val simpleTextEvent = Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "Yo wtf?").toContent(),
originServerTs = 0)
assert(condition.isSatisfied(simpleTextEvent))
Event(
type = "m.room.member",
eventId = "mx0",
stateKey = "@foo:matrix.org",
content = RoomMember(
Membership.INVITE,
displayName = "Foo",
avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"
).toContent(),
originServerTs = 0
).apply {
assert(EventMatchCondition("content.membership", "invite").isSatisfied(this))
}
}
@Test
fun test_eventmatch_cake_condition() {
val condition = EventMatchCondition("content.body", "cake")
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))
}
}
@Test
fun test_eventmatch_cakelie_condition() {
val condition = EventMatchCondition("content.body", "cake*lie")
val simpleTextEvent = Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "How was the cakeisalie?").toContent(),
originServerTs = 0)
assert(condition.isSatisfied(simpleTextEvent))
}
}