forked from GitHub-Mirror/riotX-android
WIP
This commit is contained in:
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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?
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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>)
|
||||
}
|
@ -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("")
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -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.
|
||||
|
@ -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
|
||||
)
|
@ -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>>
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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.
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@ -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() }
|
||||
)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
)
|
@ -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>>
|
||||
|
||||
}
|
@ -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
|
||||
)
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user