WIP
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_your_app_with_app_standby
|
||||
|
||||
echo "Standby OFF"
|
||||
echo "adb shell dumpsys battery reset"
|
||||
adb shell dumpsys battery reset
|
||||
|
||||
echo "adb shell am set-inactive im.vector.riotredesign false"
|
||||
adb shell am set-inactive im.vector.riotredesign false
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_your_app_with_app_standby
|
||||
|
||||
echo "Standby ON"
|
||||
echo "adb shell dumpsys battery unplug"
|
||||
adb shell dumpsys battery unplug
|
||||
|
||||
echo "adb shell am set-inactive im.vector.riotredesign true"
|
||||
adb shell am set-inactive im.vector.riotredesign true
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_doze
|
||||
|
||||
echo "Exit doze mode"
|
||||
echo "shell dumpsys deviceidle unforce"
|
||||
adb shell dumpsys deviceidle unforce
|
||||
|
||||
echo "Reactivate device"
|
||||
echo "shell dumpsys battery reset"
|
||||
adb shell dumpsys battery reset
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_doze
|
||||
|
||||
echo "Enable doze mode"
|
||||
echo "adb shell dumpsys deviceidle force-idle"
|
||||
adb shell dumpsys deviceidle force-idle
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayKind"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="20sp"
|
||||
android:textAllCaps="true"
|
||||
tools:text="Http Pusher" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayAppId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="app_id"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayAppIdValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle=""
|
||||
tools:text="im.vector.app.android" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="push_key:"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayKeyValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle=""
|
||||
tools:text="fBbCDxVa-n8:APA91bE0wGY4ijpj-LQkkmjJYhNp2vA_9Xvabh02xaTKua9WA9wpNZwxfHdsbIDWthVXKPFTNcCl75ek1kqMGOggnUwnSCj-8ReF4G69pZVUhz-" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayAppName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="app_display_name"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayAppNameValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle=""
|
||||
tools:text="EBMDOLFJD" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayDeviceName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="device_name:"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayDeviceNameValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle=""
|
||||
tools:text="EBMDOLFJD" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayURL"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="Url:"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayURLValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle=""
|
||||
tools:text="EBMDOLFJD" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayFormat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="Format:"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushGatewayFormatValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle=""
|
||||
tools:text="event_id_only" />
|
||||
|
||||
|
||||
</LinearLayout>
|
|
@ -50,7 +50,7 @@ public class FcmHelper {
|
|||
*
|
||||
* @param activity the first launch Activity
|
||||
*/
|
||||
public static void ensureFcmTokenIsRetrieved(final Activity activity) {
|
||||
public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) {
|
||||
// No op
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,10 @@
|
|||
"client_info": {
|
||||
"mobilesdk_app_id": "1:912726360885:android:448c9b63161abc9c",
|
||||
"android_client_info": {
|
||||
"package_name": "im.vector.riotredesign"
|
||||
"package_name": "im.vector.alpha"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
|
@ -29,15 +25,100 @@
|
|||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:912726360885:android:3120c24f6ef22f2b",
|
||||
"android_client_info": {
|
||||
"package_name": "im.vector.app"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:912726360885:android:25ef253beaff462e",
|
||||
"android_client_info": {
|
||||
"package_name": "im.vector.riotredesign"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:912726360885:android:bb204b7a7b08a10b",
|
||||
"android_client_info": {
|
||||
"package_name": "im.veon"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,16 +23,15 @@ import android.preference.PreferenceManager;
|
|||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
import com.google.firebase.iid.InstanceIdResult;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
import im.vector.riotredesign.R;
|
||||
import im.vector.riotredesign.core.pushers.PushersManager;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
@ -66,6 +65,7 @@ public class FcmHelper {
|
|||
.edit()
|
||||
.putString(PREFS_KEY_FCM_TOKEN, token)
|
||||
.apply();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,8 +73,8 @@ public class FcmHelper {
|
|||
*
|
||||
* @param activity the first launch Activity
|
||||
*/
|
||||
public static void ensureFcmTokenIsRetrieved(final Activity activity) {
|
||||
if (TextUtils.isEmpty(getFcmToken(activity))) {
|
||||
public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) {
|
||||
// if (TextUtils.isEmpty(getFcmToken(activity))) {
|
||||
|
||||
|
||||
//vfe: according to firebase doc
|
||||
|
@ -82,18 +82,11 @@ public class FcmHelper {
|
|||
if (checkPlayServices(activity)) {
|
||||
try {
|
||||
FirebaseInstanceId.getInstance().getInstanceId()
|
||||
.addOnSuccessListener(activity, new OnSuccessListener<InstanceIdResult>() {
|
||||
@Override
|
||||
public void onSuccess(InstanceIdResult instanceIdResult) {
|
||||
storeFcmToken(activity, instanceIdResult.getToken());
|
||||
}
|
||||
.addOnSuccessListener(activity, instanceIdResult -> {
|
||||
storeFcmToken(activity, instanceIdResult.getToken());
|
||||
pushersManager.registerPusherWithFcmKey(instanceIdResult.getToken());
|
||||
})
|
||||
.addOnFailureListener(activity, new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception e) {
|
||||
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage());
|
||||
}
|
||||
});
|
||||
.addOnFailureListener(activity, e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage()));
|
||||
} catch (Throwable e) {
|
||||
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage());
|
||||
}
|
||||
|
@ -101,7 +94,7 @@ public class FcmHelper {
|
|||
Toast.makeText(activity, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show();
|
||||
Timber.e("No valid Google Play Services found. Cannot use FCM.");
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,13 +22,19 @@ package im.vector.riotredesign.push.fcm
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.TextUtils
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.work.*
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||
import im.vector.riotredesign.BuildConfig
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.preference.BingRule
|
||||
import im.vector.riotredesign.core.pushers.PushersManager
|
||||
import im.vector.riotredesign.features.badge.BadgeProxy
|
||||
import im.vector.riotredesign.features.notifications.NotifiableEventResolver
|
||||
import im.vector.riotredesign.features.notifications.NotifiableMessageEvent
|
||||
|
@ -36,13 +42,15 @@ import im.vector.riotredesign.features.notifications.NotificationDrawerManager
|
|||
import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent
|
||||
import org.koin.android.ext.android.inject
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Class extending FirebaseMessagingService.
|
||||
*/
|
||||
class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
|
||||
val notificationDrawerManager by inject<NotificationDrawerManager>()
|
||||
private val notificationDrawerManager by inject<NotificationDrawerManager>()
|
||||
private val pusherManager by inject<PushersManager>()
|
||||
|
||||
private val notifiableEventResolver by lazy {
|
||||
NotifiableEventResolver(this)
|
||||
|
@ -67,18 +75,14 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
Timber.i("## onMessageReceived()" + message.data.toString())
|
||||
Timber.i("## onMessageReceived() from FCM with priority " + message.priority)
|
||||
}
|
||||
|
||||
//safe guard
|
||||
/* TODO
|
||||
val pushManager = Matrix.getInstance(applicationContext).pushManager
|
||||
if (!pushManager.areDeviceNotificationsAllowed()) {
|
||||
Timber.i("## onMessageReceived() : the notifications are disabled")
|
||||
return
|
||||
mUIHandler.post {
|
||||
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
//we are in foreground, let the sync do the things?
|
||||
Timber.v("PUSH received in a foreground state, ignore")
|
||||
} else {
|
||||
onMessageReceivedInternal(message.data)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//TODO if the app is in foreground, we could just ignore this. The sync loop is already going?
|
||||
// TODO mUIHandler.post { onMessageReceivedInternal(message.data, pushManager) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,9 +94,22 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
override fun onNewToken(refreshedToken: String?) {
|
||||
Timber.i("onNewToken: FCM Token has been updated")
|
||||
FcmHelper.storeFcmToken(this, refreshedToken)
|
||||
// TODO Matrix.getInstance(this)?.pushManager?.resetFCMRegistration(refreshedToken)
|
||||
if (refreshedToken == null) {
|
||||
Timber.w("onNewToken:received null token")
|
||||
} else {
|
||||
pusherManager.registerPusherWithFcmKey(refreshedToken)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the FCM server deletes pending messages. This may be due to:
|
||||
* - Too many messages stored on the FCM server.
|
||||
* This can occur when an app's servers send a bunch of non-collapsible messages to FCM servers while the device is offline.
|
||||
* - The device hasn't connected in a long time and the app server has recently (within the last 4 weeks)
|
||||
* sent a message to the app on that device.
|
||||
*
|
||||
* It is recommended that the app do a full sync with the app server after receiving this call.
|
||||
*/
|
||||
override fun onDeletedMessages() {
|
||||
Timber.v("## onDeletedMessages()")
|
||||
}
|
||||
|
@ -103,53 +120,63 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
* @param data Data map containing message data as key/value pairs.
|
||||
* For Set of keys use data.keySet().
|
||||
*/
|
||||
private fun onMessageReceivedInternal(data: Map<String, String> /*, pushManager: PushManager*/) {
|
||||
private fun onMessageReceivedInternal(data: Map<String, String>) {
|
||||
try {
|
||||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
||||
Timber.i("## onMessageReceivedInternal() : $data")
|
||||
}
|
||||
val eventId = data["event_id"]
|
||||
val roomId = data["room_id"]
|
||||
if (eventId == null || roomId == null) {
|
||||
Timber.e("## onMessageReceivedInternal() missing eventId and/or roomId")
|
||||
return
|
||||
}
|
||||
// update the badge counter
|
||||
val unreadCount = data.get("unread")?.let { Integer.parseInt(it) } ?: 0
|
||||
BadgeProxy.updateBadgeCount(applicationContext, unreadCount)
|
||||
|
||||
/* TODO
|
||||
val session = Matrix.getInstance(applicationContext)?.defaultSession
|
||||
val session = safeGetCurrentSession()
|
||||
|
||||
if (VectorApp.isAppInBackground() && !pushManager.isBackgroundSyncAllowed) {
|
||||
//Notification contains metadata and maybe data information
|
||||
handleNotificationWithoutSyncingMode(data, session)
|
||||
if (session == null) {
|
||||
Timber.w("## Can't sync from push, no current session")
|
||||
} else {
|
||||
// Safe guard... (race?)
|
||||
if (isEventAlreadyKnown(data["event_id"], data["room_id"])) return
|
||||
//Catch up!!
|
||||
EventStreamServiceX.onPushReceived(this)
|
||||
if (isEventAlreadyKnown(eventId, roomId)) {
|
||||
Timber.i("Ignoring push, event already knwown")
|
||||
} else {
|
||||
Timber.v("Requesting background sync")
|
||||
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.setInputData(Data.Builder().put("timeout", 0L).build())
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build())
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## onMessageReceivedInternal() failed : " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun safeGetCurrentSession(): Session? {
|
||||
try {
|
||||
return Matrix.getInstance().currentSession
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## Failed to get current session")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// check if the event was not yet received
|
||||
// a previous catchup might have already retrieved the notified event
|
||||
private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean {
|
||||
if (null != eventId && null != roomId) {
|
||||
try {
|
||||
/* TODO
|
||||
val sessions = Matrix.getInstance(applicationContext).sessions
|
||||
|
||||
if (null != sessions && !sessions.isEmpty()) {
|
||||
for (session in sessions) {
|
||||
if (session.dataHandler?.store?.isReady == true) {
|
||||
session.dataHandler.store?.getEvent(eventId, roomId)?.let {
|
||||
Timber.e("## isEventAlreadyKnown() : ignore the event " + eventId
|
||||
+ " in room " + roomId + " because it is already known")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
val session = safeGetCurrentSession() ?: return false
|
||||
val room = session.getRoom(roomId) ?: return false
|
||||
return room.getTimeLineEvent(eventId) != null
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined " + e.message)
|
||||
}
|
||||
|
@ -199,7 +226,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
return
|
||||
} else {
|
||||
|
||||
var notifiableEvent = notifiableEventResolver.resolveEvent(event, null, null /* TODO session.fulfillRule(event) */, session)
|
||||
var notifiableEvent = notifiableEventResolver.resolveEvent(event, null /* TODO session.fulfillRule(event) */, session)
|
||||
|
||||
if (notifiableEvent == null) {
|
||||
Timber.e("Unsupported notifiable event ${eventId}")
|
||||
|
@ -211,7 +238,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
|
||||
if (notifiableEvent is NotifiableMessageEvent) {
|
||||
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
|
||||
notifiableEvent.senderName = data["sender_display_name"] ?: data["sender"] ?: ""
|
||||
notifiableEvent.senderName = data["sender_display_name"]
|
||||
?: data["sender"] ?: ""
|
||||
}
|
||||
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
|
||||
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="im.vector.riotredesign">
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
|
@ -16,6 +15,12 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.Light"
|
||||
tools:replace="android:allowBackup">
|
||||
<receiver
|
||||
android:name=".core.services.RestartBroadcastReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".features.MainActivity"
|
||||
|
@ -32,7 +37,6 @@
|
|||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
|
||||
<activity android:name=".features.home.HomeActivity" />
|
||||
<activity android:name=".features.login.LoginActivity" />
|
||||
<activity android:name=".features.media.ImageMediaViewerActivity" />
|
||||
|
@ -58,9 +62,8 @@
|
|||
android:label="@string/encryption_message_recovery" />
|
||||
|
||||
<activity
|
||||
android:name="im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity"
|
||||
android:name=".features.reactions.EmojiReactionPickerActivity"
|
||||
android:label="@string/title_activity_emoji_reaction_picker" />
|
||||
|
||||
<activity android:name=".features.roomdirectory.RoomDirectoryActivity" />
|
||||
<activity android:name=".features.roomdirectory.roompreview.RoomPreviewActivity" />
|
||||
<activity android:name=".features.home.room.detail.RoomDetailActivity" />
|
||||
|
@ -69,6 +72,15 @@
|
|||
<service
|
||||
android:name=".core.services.CallService"
|
||||
android:exported="false" />
|
||||
<!--<service-->
|
||||
<!--android:name="im.vector.matrix.android.internal.session.sync.job.SyncService"-->
|
||||
<!--android:exported="false" />-->
|
||||
|
||||
<service
|
||||
android:name=".core.services.VectorSyncService"
|
||||
android:exported="false">
|
||||
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
@ -79,7 +91,6 @@
|
|||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/riotx_provider_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -16,14 +16,25 @@
|
|||
|
||||
package im.vector.riotredesign
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.Application
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.res.Configuration
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.IBinder
|
||||
import androidx.core.provider.FontRequest
|
||||
import androidx.core.provider.FontsContractCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.*
|
||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.facebook.stetho.Stetho
|
||||
|
@ -31,24 +42,32 @@ import com.github.piasy.biv.BigImageViewer
|
|||
import com.github.piasy.biv.loader.glide.GlideImageLoader
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncService
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||
import im.vector.riotredesign.core.di.AppModule
|
||||
import im.vector.riotredesign.core.services.RestartBroadcastReceiver
|
||||
import im.vector.riotredesign.core.services.VectorSyncService
|
||||
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
|
||||
import im.vector.riotredesign.features.home.HomeModule
|
||||
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||
import im.vector.riotredesign.features.notifications.NotificationUtils
|
||||
import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
|
||||
import im.vector.riotredesign.features.rageshake.VectorFileLogger
|
||||
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
|
||||
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule
|
||||
import im.vector.riotredesign.features.version.getVersion
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.log.EmptyLogger
|
||||
import org.koin.standalone.StandAloneContext.startKoin
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class VectorApplication : Application(), SyncService.SyncListener {
|
||||
|
||||
class VectorApplication : Application() {
|
||||
|
||||
lateinit var appContext: Context
|
||||
//font thread handler
|
||||
|
@ -56,6 +75,29 @@ class VectorApplication : Application() {
|
|||
|
||||
val vectorConfiguration: VectorConfiguration by inject()
|
||||
|
||||
private var mBinder: SyncService.LocalBinder? = null
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
Timber.v("Service unbounded")
|
||||
mBinder?.removeListener(this@VectorApplication)
|
||||
mBinder = null
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
Timber.v("Service bounded")
|
||||
mBinder = service as SyncService.LocalBinder
|
||||
mBinder?.addListener(this@VectorApplication)
|
||||
mBinder?.getService()?.nextBatchDelay = 0
|
||||
mBinder?.getService()?.timeout = 10_000L
|
||||
mBinder?.getService()?.doSync()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// var slowMode = false
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
appContext = this
|
||||
|
@ -91,10 +133,111 @@ class VectorApplication : Application() {
|
|||
R.array.com_google_android_gms_fonts_certs
|
||||
)
|
||||
|
||||
// val efp = koin.koinContext.get<EmojiCompatFontProvider>()
|
||||
FontsContractCompat.requestFont(this, fontRequest, koin.koinContext.get<EmojiCompatFontProvider>(), getFontThreadHandler())
|
||||
|
||||
vectorConfiguration.initConfiguration()
|
||||
|
||||
NotificationUtils.createNotificationChannels(applicationContext)
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver {
|
||||
|
||||
fun cancelAlarm() {
|
||||
val intent = Intent(applicationContext, RestartBroadcastReceiver::class.java)
|
||||
val pIntent = PendingIntent.getBroadcast(applicationContext, RestartBroadcastReceiver.REQUEST_CODE,
|
||||
intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
alarm.cancel(pIntent)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun entersForeground() {
|
||||
// HttpLongPoolingSyncService.startService(applicationContext)
|
||||
// cancelAlarm()
|
||||
if (Matrix.getInstance().currentSession == null) return
|
||||
WorkManager.getInstance().cancelAllWorkByTag("BG_SYNC")
|
||||
Intent(applicationContext, VectorSyncService::class.java).also { intent ->
|
||||
// intent.action = "NORMAL"
|
||||
// try {
|
||||
// startService(intent)
|
||||
// } catch (e: Throwable) {
|
||||
// Timber.e("Failed to launch sync service")
|
||||
// }
|
||||
bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var isPushAvailable = true
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
fun entersBackground() {
|
||||
Timber.i("App entered background")
|
||||
//we have here 3 modes
|
||||
|
||||
if (isPushAvailable) {
|
||||
// PUSH IS AVAILABLE:
|
||||
// Just stop the service, we will sync when a notification is received
|
||||
try {
|
||||
unbindService(connection)
|
||||
mBinder?.getService()?.stopMe()
|
||||
mBinder = null
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t)
|
||||
}
|
||||
} else {
|
||||
|
||||
// NO PUSH, and don't care about battery
|
||||
// unbindService(connection)
|
||||
// mBinder?.getService()?.stopMe()// kill also
|
||||
// mBinder = null
|
||||
//In this case we will keep a permanent
|
||||
|
||||
//TODO if no push schedule reccuring alarm
|
||||
|
||||
// val workRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.MINUTES)
|
||||
// .setConstraints(Constraints.Builder()
|
||||
// .setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
// .build())
|
||||
// .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
|
||||
// .build()
|
||||
// WorkManager.getInstance().enqueueUniquePeriodicWork(
|
||||
// "BG_SYNC",
|
||||
// ExistingPeriodicWorkPolicy.KEEP,
|
||||
// workRequest)
|
||||
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||
// .setInitialDelay(30_000, TimeUnit.MILLISECONDS)
|
||||
.setInputData(Data.Builder().put("timeout", 0L).build())
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build())
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
|
||||
|
||||
// val intent = Intent(applicationContext, RestartBroadcastReceiver::class.java)
|
||||
// // Create a PendingIntent to be triggered when the alarm goes off
|
||||
// val pIntent = PendingIntent.getBroadcast(applicationContext, RestartBroadcastReceiver.REQUEST_CODE,
|
||||
// intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
// // Setup periodic alarm every every half hour from this point onwards
|
||||
// val firstMillis = System.currentTimeMillis(); // alarm is set right away
|
||||
// val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
// // First parameter is the type: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC_WAKEUP
|
||||
// // Interval can be INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_DAY
|
||||
//// alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis,
|
||||
//// 30_000L, pIntent)
|
||||
// alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pIntent);
|
||||
|
||||
Timber.i("Alarm scheduled to restart service")
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
Matrix.getInstance().currentSession?.let {
|
||||
it.refreshPushers()
|
||||
//bind to the sync service
|
||||
get<PushRuleTriggerListener>().startWithSession(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logInfo() {
|
||||
|
@ -130,4 +273,36 @@ class VectorApplication : Application() {
|
|||
return mFontThreadHandler!!
|
||||
}
|
||||
|
||||
override fun onSyncFinsh() {
|
||||
//in foreground sync right now!!
|
||||
Timber.v("Sync just finished")
|
||||
// mBinder?.getService()?.doSync()
|
||||
}
|
||||
|
||||
override fun networkNotAvailable() {
|
||||
//we then want to retry in 10s?
|
||||
}
|
||||
|
||||
override fun onFailed(failure: Throwable) {
|
||||
//stop it also?
|
||||
// if (failure is Failure.NetworkConnection
|
||||
// && failure.cause is SocketTimeoutException) {
|
||||
// // Timeout are not critical just retry?
|
||||
// //TODO
|
||||
// }
|
||||
//
|
||||
// if (failure !is Failure.NetworkConnection
|
||||
// || failure.cause is JsonEncodingException) {
|
||||
// //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
|
||||
// mBinder?.getService()?.unbindService(connection)
|
||||
// mBinder?.getService()?.stopMe()
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,8 @@ import android.content.Context.MODE_PRIVATE
|
|||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotredesign.EmojiCompatFontProvider
|
||||
import im.vector.riotredesign.core.error.ErrorFormatter
|
||||
import im.vector.riotredesign.core.pushers.PushersManager
|
||||
import im.vector.riotredesign.core.resources.AppNameProvider
|
||||
import im.vector.riotredesign.core.resources.LocaleProvider
|
||||
import im.vector.riotredesign.core.resources.StringArrayProvider
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
|
@ -33,7 +35,9 @@ import im.vector.riotredesign.features.home.room.list.AlphabeticalRoomComparator
|
|||
import im.vector.riotredesign.features.home.room.list.ChronologicalRoomComparator
|
||||
import im.vector.riotredesign.features.navigation.DefaultNavigator
|
||||
import im.vector.riotredesign.features.navigation.Navigator
|
||||
import im.vector.riotredesign.features.notifications.NotifiableEventResolver
|
||||
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
|
||||
import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
|
||||
import org.koin.dsl.module.module
|
||||
|
||||
class AppModule(private val context: Context) {
|
||||
|
@ -80,10 +84,18 @@ class AppModule(private val context: Context) {
|
|||
ErrorFormatter(get())
|
||||
}
|
||||
|
||||
single {
|
||||
PushRuleTriggerListener(get(),get())
|
||||
}
|
||||
|
||||
single {
|
||||
NotificationDrawerManager(context)
|
||||
}
|
||||
|
||||
single {
|
||||
NotifiableEventResolver(context)
|
||||
}
|
||||
|
||||
factory {
|
||||
Matrix.getInstance().currentSession!!
|
||||
}
|
||||
|
@ -103,5 +115,13 @@ class AppModule(private val context: Context) {
|
|||
single {
|
||||
EmojiCompatFontProvider()
|
||||
}
|
||||
|
||||
single {
|
||||
AppNameProvider(context)
|
||||
}
|
||||
|
||||
single {
|
||||
PushersManager(get(), get(), get(), get())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,8 @@ abstract class VectorPreferenceFragment : PreferenceFragmentCompat() {
|
|||
activity as VectorBaseActivity
|
||||
}
|
||||
|
||||
abstract var titleRes: Int
|
||||
|
||||
/* ==========================================================================================
|
||||
* Life cycle
|
||||
* ========================================================================================== */
|
||||
|
@ -36,6 +38,7 @@ abstract class VectorPreferenceFragment : PreferenceFragmentCompat() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(titleRes)
|
||||
Timber.v("onResume Fragment ${this.javaClass.simpleName}")
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package im.vector.riotredesign.core.pushers
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.resources.AppNameProvider
|
||||
import im.vector.riotredesign.core.resources.LocaleProvider
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
|
||||
private const val DEFAULT_PUSHER_FILE_TAG = "mobile"
|
||||
|
||||
class PushersManager(
|
||||
private val currentSession: Session,
|
||||
private val localeProvider: LocaleProvider,
|
||||
private val stringProvider: StringProvider,
|
||||
private val appNameProvider: AppNameProvider
|
||||
) {
|
||||
|
||||
fun registerPusherWithFcmKey(pushKey: String) {
|
||||
var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.sessionParams.credentials.userId.hashCode())
|
||||
|
||||
currentSession.addHttpPusher(
|
||||
pushKey,
|
||||
stringProvider.getString(R.string.pusher_app_id),
|
||||
profileTag,
|
||||
localeProvider.current().language,
|
||||
appNameProvider.getAppName(),
|
||||
currentSession.sessionParams.credentials.deviceId ?: "MOBILE",
|
||||
stringProvider.getString(R.string.pusher_http_url),
|
||||
false,
|
||||
true
|
||||
)
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package im.vector.riotredesign.core.resources
|
||||
|
||||
import android.content.Context
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
class AppNameProvider(private val context: Context) {
|
||||
|
||||
fun getAppName(): String {
|
||||
try {
|
||||
val appPackageName = context.applicationContext.packageName
|
||||
val pm = context.packageManager
|
||||
val appInfo = pm.getApplicationInfo(appPackageName, 0)
|
||||
var appName = pm.getApplicationLabel(appInfo).toString()
|
||||
|
||||
// Use appPackageName instead of appName if appName contains any non-ASCII character
|
||||
if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) {
|
||||
appName = appPackageName
|
||||
}
|
||||
return appName
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## AppNameProvider() : failed " + e.message)
|
||||
return "RiotXAndroid"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,4 @@ class LocaleProvider(private val resources: Resources) {
|
|||
fun current(): Locale {
|
||||
return ConfigurationCompat.getLocales(resources.configuration)[0]
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
//package im.vector.riotredesign.core.services
|
||||
//
|
||||
//import android.app.NotificationManager
|
||||
//import android.content.Context
|
||||
//import android.content.Intent
|
||||
//import android.os.Build.VERSION.SDK_INT
|
||||
//import android.os.Build.VERSION_CODES
|
||||
//import android.os.Handler
|
||||
//import android.os.HandlerThread
|
||||
//import android.os.Looper
|
||||
//import androidx.core.content.ContextCompat.startForegroundService
|
||||
//import im.vector.matrix.android.api.Matrix
|
||||
//import im.vector.matrix.android.api.session.Session
|
||||
//import im.vector.riotredesign.R
|
||||
//import im.vector.riotredesign.features.notifications.NotificationUtils
|
||||
//import timber.log.Timber
|
||||
//import java.net.HttpURLConnection
|
||||
//import java.net.URL
|
||||
//
|
||||
//
|
||||
///**
|
||||
// *
|
||||
// * This is used to display message notifications to the user when Push is not enabled (or not configured)
|
||||
// *
|
||||
// * This service is used to implement a long pooling mechanism in order to get messages from
|
||||
// * the home server when the user is not interacting with the app.
|
||||
// *
|
||||
// * It is intended to be started when the app enters background, and stopped when app is in foreground.
|
||||
// *
|
||||
// * When in foreground, the app uses another mechanism to get messages (doing sync wia a thread).
|
||||
// *
|
||||
// */
|
||||
//class HttpLongPoolingSyncService : VectorService() {
|
||||
//
|
||||
// private var mServiceLooper: Looper? = null
|
||||
// private var mHandler: Handler? = null
|
||||
// private val currentSessions = ArrayList<Session>()
|
||||
// private var mCount = 0
|
||||
// private var lastTimeMs = System.currentTimeMillis()
|
||||
//
|
||||
// lateinit var myRun: () -> Unit
|
||||
// override fun onCreate() {
|
||||
// //Add the permanent listening notification
|
||||
// super.onCreate()
|
||||
//
|
||||
// if (SDK_INT >= VERSION_CODES.O) {
|
||||
// val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
// val notification = NotificationUtils.buildForegroundServiceNotification(applicationContext, R.string.notification_listening_for_events, false)
|
||||
// startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||
// }
|
||||
// val thread = HandlerThread("My service Handler")
|
||||
// thread.start()
|
||||
//
|
||||
// mServiceLooper = thread.looper
|
||||
// mHandler = Handler(mServiceLooper)
|
||||
// myRun = {
|
||||
// val diff = System.currentTimeMillis() - lastTimeMs
|
||||
// lastTimeMs = System.currentTimeMillis()
|
||||
// val isAlive = Matrix.getInstance().currentSession?.isSyncThreadAlice()
|
||||
// val state = Matrix.getInstance().currentSession?.syncThreadState()
|
||||
// Timber.w(" timeDiff[${diff/1000}] Yo me here $mCount, sync thread is Alive? $isAlive, state:$state")
|
||||
// mCount++
|
||||
// mHandler?.postDelayed(Runnable { myRun() }, 10_000L)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
// //START_STICKY mode makes sense for things that will be explicitly started
|
||||
// //and stopped to run for arbitrary periods of time
|
||||
//
|
||||
// mHandler?.post {
|
||||
// myRun()
|
||||
// }
|
||||
// return START_STICKY
|
||||
// }
|
||||
//
|
||||
//
|
||||
// override fun onDestroy() {
|
||||
// //TODO test if this service should be relaunched (preference)
|
||||
// Timber.i("Service is destroyed, relaunch asap")
|
||||
// Intent(applicationContext, RestartBroadcastReceiver::class.java).also { sendBroadcast(it) }
|
||||
// super.onDestroy()
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
//
|
||||
// fun startService(context: Context) {
|
||||
// Timber.i("Start sync service")
|
||||
// val intent = Intent(context, HttpLongPoolingSyncService::class.java)
|
||||
// try {
|
||||
// if (SDK_INT >= VERSION_CODES.O) {
|
||||
// startForegroundService(context, intent)
|
||||
// } else {
|
||||
// context.startService(intent)
|
||||
// }
|
||||
// } catch (ex: Throwable) {
|
||||
// //TODO
|
||||
// Timber.e(ex)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,37 @@
|
|||
package im.vector.riotredesign.core.services
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.legacy.content.WakefulBroadcastReceiver
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncService
|
||||
import timber.log.Timber
|
||||
|
||||
class RestartBroadcastReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
|
||||
Timber.d("RestartBroadcastReceiver received intent")
|
||||
Intent(context,VectorSyncService::class.java).also {
|
||||
it.action = "SLOW"
|
||||
context.startService(it)
|
||||
try {
|
||||
if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
//TODO
|
||||
Timber.e(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REQUEST_CODE = 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotredesign.core.services
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.IBinder
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncService
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.features.notifications.NotificationUtils
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
class VectorSyncService : SyncService() {
|
||||
|
||||
override fun onCreate() {
|
||||
Timber.v("VectorSyncService - onCreate ")
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Timber.v("VectorSyncService - onDestroy ")
|
||||
removeForegroundNotif()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun removeForegroundNotif() {
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Service is started only in fdroid mode when no FCM is available
|
||||
* Otherwise it is bounded
|
||||
*/
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Timber.v("VectorSyncService - onStartCommand ")
|
||||
if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notification = NotificationUtils.buildForegroundServiceNotification(applicationContext, R.string.notification_listening_for_events, false)
|
||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
/**
|
||||
* If the service is bounded and the service was previously started we can remove foreground notif
|
||||
*/
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
Timber.v("VectorSyncService - onBind ")
|
||||
stopForeground(true)
|
||||
return super.onBind(intent)
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
Timber.v("VectorSyncService - onUnbind ")
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
}
|
|
@ -59,17 +59,24 @@ class MainActivity : VectorBaseActivity() {
|
|||
} else {
|
||||
// Handle some wanted cleanup
|
||||
when {
|
||||
clearCredentials -> session.signOut(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.w("SIGN_OUT: success, start app")
|
||||
start()
|
||||
}
|
||||
})
|
||||
clearCache -> session.clearCache(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
start()
|
||||
}
|
||||
})
|
||||
clearCredentials -> {
|
||||
session.signOut(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.w("SIGN_OUT: success, start app")
|
||||
//TODO stop sync service
|
||||
start()
|
||||
}
|
||||
})
|
||||
}
|
||||
clearCache -> {
|
||||
//TODO stop sync service
|
||||
session.clearCache(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
//TODO start sync service
|
||||
start()
|
||||
}
|
||||
})
|
||||
}
|
||||
else -> start()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,11 @@ import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
|||
import im.vector.riotredesign.core.platform.VectorBaseActivity
|
||||
import im.vector.riotredesign.features.crypto.keysrequest.KeyRequestHandler
|
||||
import im.vector.riotredesign.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
import im.vector.riotredesign.core.pushers.PushersManager
|
||||
import im.vector.riotredesign.features.rageshake.BugReporter
|
||||
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
|
||||
import im.vector.riotredesign.features.workers.signout.SignOutUiWorker
|
||||
import im.vector.riotredesign.push.fcm.FcmHelper
|
||||
import kotlinx.android.synthetic.main.activity_home.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.android.scope.ext.android.bindScope
|
||||
|
@ -52,7 +54,7 @@ import im.vector.riotredesign.features.workers.signout.SignOutViewModel
|
|||
|
||||
class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
|
||||
// Supported navigation actions for this Activity
|
||||
// Supported navigation domainActions for this Activity
|
||||
sealed class Navigation {
|
||||
object OpenDrawer : Navigation()
|
||||
}
|
||||
|
@ -60,6 +62,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
|
||||
private lateinit var navigationViewModel: HomeNavigationViewModel
|
||||
private val homeNavigator by inject<HomeNavigator>()
|
||||
private val pushManager by inject<PushersManager>()
|
||||
|
||||
// TODO Move this elsewhere
|
||||
private val incomingVerificationRequestHandler by inject<IncomingVerificationRequestHandler>()
|
||||
|
@ -80,6 +83,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
super.onCreate(savedInstanceState)
|
||||
bindScope(getOrCreateScope(HomeModule.HOME_SCOPE))
|
||||
homeNavigator.activity = this
|
||||
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager)
|
||||
|
||||
navigationViewModel = ViewModelProviders.of(this).get(HomeNavigationViewModel::class.java)
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import im.vector.matrix.android.api.session.user.model.User
|
|||
* QUOTE: User is currently quoting a message
|
||||
* EDIT: User is currently editing an existing message
|
||||
*
|
||||
* Depending on the state the bottom toolbar will change (icons/preview/actions...)
|
||||
* Depending on the state the bottom toolbar will change (icons/preview/domainActions...)
|
||||
*/
|
||||
enum class SendMode {
|
||||
REGULAR,
|
||||
|
|
|
@ -20,7 +20,7 @@ import androidx.lifecycle.ViewModel
|
|||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
|
||||
/**
|
||||
* Activity shared view model to handle message actions
|
||||
* Activity shared view model to handle message domainActions
|
||||
*/
|
||||
class ActionsHandler : ViewModel() {
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInf
|
|||
import kotlinx.android.synthetic.main.bottom_sheet_message_actions.*
|
||||
|
||||
/**
|
||||
* Bottom sheet fragment that shows a message preview with list of contextual actions
|
||||
* (Includes fragments for quick reactions and list of actions)
|
||||
* Bottom sheet fragment that shows a message preview with list of contextual domainActions
|
||||
* (Includes fragments for quick reactions and list of domainActions)
|
||||
*/
|
||||
class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?,
|
|||
data class MessageMenuState(val actions: List<SimpleAction> = emptyList()) : MvRxState
|
||||
|
||||
/**
|
||||
* Manages list actions for a given message (copy / paste / forward...)
|
||||
* Manages list domainActions for a given message (copy / paste / forward...)
|
||||
*/
|
||||
class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<MessageMenuState>(initialState) {
|
||||
|
||||
|
|
|
@ -32,10 +32,12 @@ import im.vector.riotredesign.R
|
|||
import im.vector.riotredesign.core.extensions.showPassword
|
||||
import im.vector.riotredesign.core.platform.VectorBaseActivity
|
||||
import im.vector.riotredesign.features.home.HomeActivity
|
||||
import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.Function3
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
private const val DEFAULT_HOME_SERVER_URI = "https://matrix.org"
|
||||
private const val DEFAULT_IDENTITY_SERVER_URI = "https://vector.im"
|
||||
|
@ -74,8 +76,10 @@ class LoginActivity : VectorBaseActivity() {
|
|||
Matrix.getInstance().currentSession = data
|
||||
data.open()
|
||||
data.setFilter(FilterService.FilterPreset.RiotFilter)
|
||||
data.startSync()
|
||||
|
||||
//TODO sync
|
||||
// data.shoudPauseOnBackground(false)
|
||||
// data.startSync()
|
||||
get<PushRuleTriggerListener>().startWithSession(data)
|
||||
goToHome()
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,16 @@
|
|||
package im.vector.riotredesign.features.notifications
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
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.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.preference.BingRule
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO Remove
|
||||
class RoomState {
|
||||
|
@ -36,128 +43,128 @@ class NotifiableEventResolver(val context: Context) {
|
|||
|
||||
//private val eventDisplay = RiotEventDisplay(context)
|
||||
|
||||
fun resolveEvent(event: Event, roomState: RoomState?, bingRule: BingRule?, session: Session): NotifiableEvent? {
|
||||
// TODO
|
||||
return null
|
||||
/*
|
||||
val store = session.dataHandler.store
|
||||
if (store == null) {
|
||||
Log.e("## NotifiableEventResolver, unable to get store")
|
||||
//TODO notify somehow that something did fail?
|
||||
return null
|
||||
}
|
||||
fun resolveEvent(event: Event/*, roomState: RoomState?*/, bingRule: PushRule?, session: Session): NotifiableEvent? {
|
||||
|
||||
|
||||
// val store = session.dataHandler.store
|
||||
// if (store == null) {
|
||||
// Log.e("## NotifiableEventResolver, unable to get store")
|
||||
// //TODO notify somehow that something did fail?
|
||||
// return null
|
||||
// }
|
||||
|
||||
when (event.getClearType()) {
|
||||
EventType.MESSAGE -> {
|
||||
return resolveMessageEvent(event, bingRule, session, store)
|
||||
}
|
||||
EventType.ENCRYPTED -> {
|
||||
val messageEvent = resolveMessageEvent(event, bingRule, session, store)
|
||||
messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
|
||||
return messageEvent
|
||||
}
|
||||
EventType.STATE_ROOM_MEMBER -> {
|
||||
return resolveStateRoomEvent(event, bingRule, session, store)
|
||||
EventType.MESSAGE -> {
|
||||
return resolveMessageEvent(event, bingRule, session)
|
||||
}
|
||||
// EventType.ENCRYPTED -> {
|
||||
// val messageEvent = resolveMessageEvent(event, bingRule, session, store)
|
||||
// messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
|
||||
// return messageEvent
|
||||
// }
|
||||
// EventType.STATE_ROOM_MEMBER -> {
|
||||
// return resolveStateRoomEvent(event, bingRule, session, store)
|
||||
// }
|
||||
else -> {
|
||||
|
||||
//If the event can be displayed, display it as is
|
||||
eventDisplay.getTextualDisplay(event, roomState)?.toString()?.let { body ->
|
||||
return SimpleNotifiableEvent(
|
||||
session.myUserId,
|
||||
eventId = event.eventId,
|
||||
noisy = bingRule?.notificationSound != null,
|
||||
timestamp = event.originServerTs,
|
||||
description = body,
|
||||
soundName = bingRule?.notificationSound,
|
||||
title = context.getString(R.string.notification_unknown_new_event),
|
||||
type = event.getClearType())
|
||||
}
|
||||
// eventDisplay.getTextualDisplay(event, roomState)?.toString()?.let { body ->
|
||||
// return SimpleNotifiableEvent(
|
||||
// session.myUserId,
|
||||
// eventId = event.eventId,
|
||||
// noisy = bingRule?.notificationSound != null,
|
||||
// timestamp = event.originServerTs,
|
||||
// description = body,
|
||||
// soundName = bingRule?.notificationSound,
|
||||
// title = context.getString(R.string.notification_unknown_new_event),
|
||||
// type = event.type)
|
||||
// }
|
||||
|
||||
//Unsupported event
|
||||
Timber.w("NotifiableEventResolver Received an unsupported event matching a bing rule")
|
||||
return null
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
private fun resolveMessageEvent(event: Event, bingRule: BingRule?, session: MXSession, store: IMXStore): NotifiableEvent? {
|
||||
|
||||
private fun resolveMessageEvent(event: Event, pushRule: PushRule?, session: Session): NotifiableEvent? {
|
||||
//If we are here, that means that the event should be notified to the user, we check now how it should be presented (sound)
|
||||
val soundName = bingRule?.notificationSound
|
||||
val noisy = bingRule?.notificationSound != null
|
||||
// val soundName = pushRule?.notificationSound
|
||||
|
||||
val noisy = true//pushRule?.notificationSound != null
|
||||
|
||||
//The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
|
||||
val room = store.getRoom(event.roomId /*roomID cannot be null (see Matrix SDK code)*/)
|
||||
val room = session.getRoom(event.roomId!! /*roomID cannot be null (see Matrix SDK code)*/)
|
||||
|
||||
|
||||
if (room == null) {
|
||||
Timber.e("## Unable to resolve room for eventId [${event.eventId}] and roomID [${event.roomId}]")
|
||||
// Ok room is not known in store, but we can still display something
|
||||
val body = eventDisplay.getTextualDisplay(event, null)?.toString()
|
||||
val body = event.content?.toModel<MessageContent>()?.body
|
||||
?: context.getString(R.string.notification_unknown_new_event)
|
||||
val roomName = context.getString(R.string.notification_unknown_room_name)
|
||||
val senderDisplayName = event.sender ?: ""
|
||||
val senderDisplayName = event.senderId ?: ""
|
||||
|
||||
val notifiableEvent = NotifiableMessageEvent(
|
||||
eventId = event.eventId,
|
||||
timestamp = event.originServerTs,
|
||||
eventId = event.eventId ?: "",
|
||||
timestamp = event.originServerTs ?: 0,
|
||||
noisy = noisy,
|
||||
senderName = senderDisplayName,
|
||||
senderId = event.sender,
|
||||
senderId = event.senderId,
|
||||
body = body,
|
||||
roomId = event.roomId,
|
||||
roomId = event.roomId ?: "",
|
||||
roomName = roomName)
|
||||
|
||||
notifiableEvent.matrixID = session.myUserId
|
||||
notifiableEvent.soundName = soundName
|
||||
notifiableEvent.matrixID = session.sessionParams.credentials.userId
|
||||
// notifiableEvent.soundName = soundName
|
||||
|
||||
return notifiableEvent
|
||||
} else {
|
||||
|
||||
val body = eventDisplay.getTextualDisplay(event, room.state)?.toString()
|
||||
val tEvent = room.getTimeLineEvent(event.eventId!!)
|
||||
val body = event.content?.toModel<MessageContent>()?.body
|
||||
?: context.getString(R.string.notification_unknown_new_event)
|
||||
val roomName = room.getRoomDisplayName(context)
|
||||
val senderDisplayName = room.state.getMemberName(event.sender) ?: event.sender ?: ""
|
||||
val roomName = event.roomId ?: "room"
|
||||
val senderDisplayName = tEvent?.senderName ?: "?"
|
||||
|
||||
val notifiableEvent = NotifiableMessageEvent(
|
||||
eventId = event.eventId,
|
||||
timestamp = event.originServerTs,
|
||||
eventId = event.eventId!!,
|
||||
timestamp = event.originServerTs ?: 0,
|
||||
noisy = noisy,
|
||||
senderName = senderDisplayName,
|
||||
senderId = event.sender,
|
||||
senderId = event.senderId,
|
||||
body = body,
|
||||
roomId = event.roomId,
|
||||
roomId = event.roomId ?: "00",
|
||||
roomName = roomName,
|
||||
roomIsDirect = room.isDirect)
|
||||
roomIsDirect = true)
|
||||
|
||||
notifiableEvent.matrixID = session.myUserId
|
||||
notifiableEvent.soundName = soundName
|
||||
notifiableEvent.matrixID = session.sessionParams.credentials.userId
|
||||
notifiableEvent.soundName = null
|
||||
|
||||
|
||||
val roomAvatarPath = session.mediaCache?.thumbnailCacheFile(room.avatarUrl, 50)
|
||||
if (roomAvatarPath != null) {
|
||||
notifiableEvent.roomAvatarPath = roomAvatarPath.path
|
||||
} else {
|
||||
// prepare for the next time
|
||||
session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), room.avatarUrl, 50)
|
||||
}
|
||||
|
||||
room.state.getMember(event.sender)?.avatarUrl?.let {
|
||||
val size = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
|
||||
val userAvatarUrlPath = session.mediaCache?.thumbnailCacheFile(it, size)
|
||||
if (userAvatarUrlPath != null) {
|
||||
notifiableEvent.senderAvatarPath = userAvatarUrlPath.path
|
||||
} else {
|
||||
// prepare for the next time
|
||||
session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), it, size)
|
||||
}
|
||||
}
|
||||
// val roomAvatarPath = session.mediaCache?.thumbnailCacheFile(room.avatarUrl, 50)
|
||||
// if (roomAvatarPath != null) {
|
||||
// notifiableEvent.roomAvatarPath = roomAvatarPath.path
|
||||
// } else {
|
||||
// // prepare for the next time
|
||||
// session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), room.avatarUrl, 50)
|
||||
// }
|
||||
//
|
||||
// room.state.getMember(event.sender)?.avatarUrl?.let {
|
||||
// val size = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
|
||||
// val userAvatarUrlPath = session.mediaCache?.thumbnailCacheFile(it, size)
|
||||
// if (userAvatarUrlPath != null) {
|
||||
// notifiableEvent.senderAvatarPath = userAvatarUrlPath.path
|
||||
// } else {
|
||||
// // prepare for the next time
|
||||
// session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), it, size)
|
||||
// }
|
||||
// }
|
||||
|
||||
return notifiableEvent
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
private fun resolveStateRoomEvent(event: Event, bingRule: BingRule?, session: MXSession, store: IMXStore): NotifiableEvent? {
|
||||
if (RoomMember.MEMBERSHIP_INVITE == event.contentAsJsonObject?.getAsJsonPrimitive("membership")?.asString) {
|
||||
val room = store.getRoom(event.roomId /*roomID cannot be null (see Matrix SDK code)*/)
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.koin.standalone.inject
|
|||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.)
|
||||
* Receives domainActions broadcast by notification (on click, on dismiss, inline replies, etc.)
|
||||
*/
|
||||
class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent {
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class NotificationDrawerManager(val context: Context) {
|
|||
private var firstTime = true
|
||||
|
||||
private var eventList = loadEventInfo()
|
||||
private var myUserDisplayName: String = ""
|
||||
private var myUserDisplayName: String = "Todo"
|
||||
private var myUserAvatarUrl: String = ""
|
||||
|
||||
private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
|
||||
|
@ -185,7 +185,8 @@ class NotificationDrawerManager(val context: Context) {
|
|||
|
||||
if (myUserDisplayName.isBlank()) {
|
||||
// Should not happen, but myUserDisplayName cannot be blank if used to create a Person
|
||||
return
|
||||
//TODO
|
||||
// return
|
||||
}
|
||||
|
||||
synchronized(eventList) {
|
||||
|
|
|
@ -64,7 +64,7 @@ object NotificationUtils {
|
|||
const val NOTIFICATION_ID_FOREGROUND_SERVICE = 61
|
||||
|
||||
/* ==========================================================================================
|
||||
* IDs for actions
|
||||
* IDs for domainActions
|
||||
* ========================================================================================== */
|
||||
|
||||
private const val JOIN_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.JOIN_ACTION"
|
||||
|
@ -180,7 +180,7 @@ object NotificationUtils {
|
|||
* @return the polling thread listener notification
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun buildForegroundServiceNotification(context: Context, @StringRes subTitleResId: Int): Notification {
|
||||
fun buildForegroundServiceNotification(context: Context, @StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
|
||||
// build the pending intent go to the home screen if this is clicked.
|
||||
val i = Intent(context, HomeActivity::class.java)
|
||||
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
|
@ -190,16 +190,21 @@ object NotificationUtils {
|
|||
|
||||
val builder = NotificationCompat.Builder(context, LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle(context.getString(subTitleResId))
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.setSmallIcon(R.drawable.logo_transparent)
|
||||
.setProgress(0, 0, true)
|
||||
.setSmallIcon(R.drawable.sync)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setColor(accentColor)
|
||||
.setContentIntent(pi)
|
||||
.apply {
|
||||
if (withProgress) {
|
||||
setProgress(0, 0, true)
|
||||
}
|
||||
}
|
||||
|
||||
// hide the notification from the status bar
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
builder.priority = NotificationCompat.PRIORITY_MIN
|
||||
}
|
||||
// PRIORITY_MIN should not be used with Service#startForeground(int, Notification)
|
||||
builder.priority = NotificationCompat.PRIORITY_LOW
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// builder.priority = NotificationCompat.PRIORITY_MIN
|
||||
// }
|
||||
|
||||
val notification = builder.build()
|
||||
|
||||
|
@ -220,7 +225,7 @@ object NotificationUtils {
|
|||
PendingIntent::class.java)
|
||||
deprecatedMethod.invoke(notification, context, context.getString(R.string.app_name), context.getString(subTitleResId), pi)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "## buildNotification(): Exception - setLatestEventInfo() Msg=" + ex.message)
|
||||
Timber.e(ex, "## buildNotification(): Exception - setLatestEventInfo() Msg=")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -421,7 +426,7 @@ object NotificationUtils {
|
|||
priority = NotificationCompat.PRIORITY_LOW
|
||||
}
|
||||
|
||||
//Add actions and notification intents
|
||||
//Add domainActions and notification intents
|
||||
// Mark room as read
|
||||
val markRoomReadIntent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||
markRoomReadIntent.action = MARK_ROOM_READ_ACTION
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package im.vector.riotredesign.features.notifications
|
||||
|
||||
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 timber.log.Timber
|
||||
|
||||
|
||||
class PushRuleTriggerListener(
|
||||
private val resolver: NotifiableEventResolver,
|
||||
private val drawerManager: NotificationDrawerManager
|
||||
) : PushRuleService.PushRuleListener {
|
||||
|
||||
|
||||
var session: Session? = null
|
||||
|
||||
override fun onMatchRule(event: Event, actions: List<Action>) {
|
||||
if (session == null) {
|
||||
Timber.e("Called without active session")
|
||||
return
|
||||
}
|
||||
resolver.resolveEvent(event,null,session!!)?.let {
|
||||
drawerManager.onNotifiableEventReceived(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun batchFinish() {
|
||||
drawerManager.refreshNotificationDrawer(null)
|
||||
}
|
||||
|
||||
fun startWithSession(session: Session) {
|
||||
if (this.session != null) {
|
||||
stop()
|
||||
}
|
||||
this.session = session
|
||||
session.addPushRuleListener(this)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
session?.removePushRuleListener(this)
|
||||
session = null
|
||||
drawerManager.clearAllEvents()
|
||||
drawerManager.refreshNotificationDrawer(null)
|
||||
}
|
||||
}
|
|
@ -50,6 +50,8 @@ public class PreferencesManager {
|
|||
public static final String SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY";
|
||||
public static final String SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY = "SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY";
|
||||
public static final String SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY = "SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY";
|
||||
|
||||
//TODO delete
|
||||
public static final String SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY";
|
||||
public static final String SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY";
|
||||
public static final String SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY";
|
||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.riotredesign.R
|
|||
import im.vector.riotredesign.core.platform.VectorBaseActivity
|
||||
import kotlinx.android.synthetic.main.activity_vector_settings.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Displays the client settings.
|
||||
|
@ -35,7 +36,6 @@ class VectorSettingsActivity : VectorBaseActivity(),
|
|||
FragmentManager.OnBackStackChangedListener,
|
||||
VectorSettingsFragmentInteractionListener {
|
||||
|
||||
private lateinit var vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment
|
||||
|
||||
override fun getLayoutRes() = R.layout.activity_vector_settings
|
||||
|
||||
|
@ -48,14 +48,15 @@ class VectorSettingsActivity : VectorBaseActivity(),
|
|||
override fun initUiAndData() {
|
||||
configureToolbar(settingsToolbar)
|
||||
|
||||
var vectorSettingsPreferencesFragment: Fragment? = null
|
||||
if (isFirstCreation()) {
|
||||
vectorSettingsPreferencesFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId)
|
||||
vectorSettingsPreferencesFragment = VectorSettingsPreferencesFragmentV2.newInstance()
|
||||
// display the fragment
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.vector_settings_page, vectorSettingsPreferencesFragment, FRAGMENT_TAG)
|
||||
.commit()
|
||||
} else {
|
||||
vectorSettingsPreferencesFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as VectorSettingsPreferencesFragment
|
||||
vectorSettingsPreferencesFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
|
||||
}
|
||||
|
||||
|
||||
|
@ -77,19 +78,33 @@ class VectorSettingsActivity : VectorBaseActivity(),
|
|||
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean {
|
||||
var oFragment: Fragment? = null
|
||||
|
||||
if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
|
||||
if ("Legacy" == pref?.title) {
|
||||
oFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId)
|
||||
} else if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
|
||||
oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.sessionParams.credentials.userId)
|
||||
} else if (PreferencesManager.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) {
|
||||
oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.sessionParams.credentials.userId)
|
||||
} else {
|
||||
try {
|
||||
pref?.fragment?.let {
|
||||
oFragment = supportFragmentManager.fragmentFactory
|
||||
.instantiate(
|
||||
classLoader,
|
||||
it, pref.extras)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showSnackbar(getString(R.string.not_implemented))
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (oFragment != null) {
|
||||
oFragment.setTargetFragment(caller, 0)
|
||||
oFragment!!.setTargetFragment(caller, 0)
|
||||
// Replace the existing Fragment with the new Fragment
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom,
|
||||
R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom)
|
||||
.replace(R.id.vector_settings_page, oFragment, pref?.title.toString())
|
||||
.setCustomAnimations(R.anim.right_in, R.anim.fade_out,
|
||||
R.anim.fade_in, R.anim.right_out)
|
||||
.replace(R.id.vector_settings_page, oFragment!!, pref?.title.toString())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
return true
|
||||
|
|
|
@ -51,6 +51,8 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
|
|||
}
|
||||
} */
|
||||
|
||||
override var titleRes: Int = R.string.settings_notification_advanced
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
// define the layout
|
||||
addPreferencesFromResource(R.xml.vector_settings_notification_advanced_preferences)
|
||||
|
@ -177,7 +179,6 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notification_advanced)
|
||||
// find the view from parent activity
|
||||
mLoadingView = activity!!.findViewById(R.id.vector_settings_spinner_views)
|
||||
|
||||
|
@ -222,14 +223,14 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
|
|||
if (TextUtils.equals(ruleId, BingRule.RULE_ID_DISABLE_ALL) || TextUtils.equals(ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) {
|
||||
isEnabled = !isEnabled
|
||||
} else if (isEnabled) {
|
||||
val actions = rule!!.actions
|
||||
val domainActions = rule!!.domainActions
|
||||
|
||||
// no action -> noting will be done
|
||||
if (null == actions || actions.isEmpty()) {
|
||||
if (null == domainActions || domainActions.isEmpty()) {
|
||||
isEnabled = false
|
||||
} else if (1 == actions.size) {
|
||||
} else if (1 == domainActions.size) {
|
||||
try {
|
||||
isEnabled = !TextUtils.equals(actions[0] as String, BingRule.ACTION_DONT_NOTIFY)
|
||||
isEnabled = !TextUtils.equals(domainActions[0] as String, BingRule.ACTION_DONT_NOTIFY)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## refreshPreferences failed")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package im.vector.riotredesign.features.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseActivity
|
||||
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
|
||||
|
||||
|
||||
class VectorSettingsNotificationPreferenceFragment : VectorPreferenceFragment() {
|
||||
|
||||
override var titleRes: Int = R.string.settings_notifications
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.vector_settings_notifications)
|
||||
}
|
||||
|
||||
}
|
|
@ -84,6 +84,7 @@ import java.util.*
|
|||
|
||||
class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
override var titleRes: Int = R.string.title_activity_settings
|
||||
// members
|
||||
private val mSession by inject<Session>()
|
||||
|
||||
|
@ -1493,14 +1494,14 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
|||
if (TextUtils.equals(ruleId, BingRule.RULE_ID_DISABLE_ALL) || TextUtils.equals(ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) {
|
||||
isEnabled = !isEnabled
|
||||
} else if (isEnabled) {
|
||||
val actions = rule?.actions
|
||||
val domainActions = rule?.domainActions
|
||||
|
||||
// no action -> noting will be done
|
||||
if (null == actions || actions.isEmpty()) {
|
||||
if (null == domainActions || domainActions.isEmpty()) {
|
||||
isEnabled = false
|
||||
} else if (1 == actions.size) {
|
||||
} else if (1 == domainActions.size) {
|
||||
try {
|
||||
isEnabled = !TextUtils.equals(actions[0] as String, BingRule.ACTION_DONT_NOTIFY)
|
||||
isEnabled = !TextUtils.equals(domainActions[0] as String, BingRule.ACTION_DONT_NOTIFY)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## refreshPreferences failed " + e.message)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package im.vector.riotredesign.features.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.withArgs
|
||||
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
|
||||
|
||||
class VectorSettingsPreferencesFragmentV2 : VectorPreferenceFragment() {
|
||||
|
||||
override var titleRes: Int = R.string.title_activity_settings
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.vector_settings_preferences_root)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun newInstance() = VectorSettingsPreferencesFragmentV2()
|
||||
.withArgs {
|
||||
//putString(ARG_MATRIX_ID, matrixId)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package im.vector.riotredesign.features.settings.push
|
||||
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
|
||||
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_pushgateway)
|
||||
abstract class PushGatewayItem : EpoxyModelWithHolder<PushGatewayItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var pusher: Pusher
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.kind.text = when (pusher.kind) {
|
||||
"http" -> "Http Pusher"
|
||||
"mail" -> "Email Pusher"
|
||||
else -> pusher.kind
|
||||
}
|
||||
|
||||
holder.appId.text = pusher.appId
|
||||
holder.pushKey.text = pusher.pushKey
|
||||
holder.appName.text = pusher.appDisplayName
|
||||
holder.url.text = pusher.data.url
|
||||
holder.format.text = pusher.data.format
|
||||
holder.deviceName.text = pusher.deviceDisplayName
|
||||
}
|
||||
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val kind by bind<TextView>(R.id.pushGatewayKind)
|
||||
val pushKey by bind<TextView>(R.id.pushGatewayKeyValue)
|
||||
val deviceName by bind<TextView>(R.id.pushGatewayDeviceNameValue)
|
||||
val format by bind<TextView>(R.id.pushGatewayFormatValue)
|
||||
val url by bind<TextView>(R.id.pushGatewayURLValue)
|
||||
val appName by bind<TextView>(R.id.pushGatewayAppNameValue)
|
||||
val appId by bind<TextView>(R.id.pushGatewayAppIdValue)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleItem.Holder>() {
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.settings.push
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseActivity
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import kotlinx.android.synthetic.main.fragment_settings_pushgateways.*
|
||||
|
||||
class PushGatewaysFragment : VectorBaseFragment() {
|
||||
|
||||
override fun getLayoutResId(): Int = R.layout.fragment_settings_pushgateways
|
||||
|
||||
private val viewModel: PushGatewaysViewModel by fragmentViewModel(PushGatewaysViewModel::class)
|
||||
|
||||
private val epoxyController by lazy { PushGateWayController() }
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_gateways)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||
epoxyRecyclerView.layoutManager = lmgr
|
||||
val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
|
||||
lmgr.orientation)
|
||||
epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
|
||||
epoxyRecyclerView.adapter = epoxyController.adapter
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
epoxyController.setData(it)
|
||||
}
|
||||
|
||||
class PushGateWayController : TypedEpoxyController<PushGatewayViewState>() {
|
||||
override fun buildModels(data: PushGatewayViewState?) {
|
||||
val pushers = data?.pushgateways?.invoke() ?: return
|
||||
pushers.forEach {
|
||||
pushGatewayItem {
|
||||
id("${it.pushKey}_${it.appId}")
|
||||
pusher(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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.riotredesign.features.settings.push
|
||||
|
||||
import androidx.lifecycle.Observer
|
||||
import com.airbnb.mvrx.*
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.riotredesign.core.platform.VectorViewModel
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
|
||||
data class PushGatewayViewState(
|
||||
val pushgateways: Async<List<Pusher>> = Uninitialized)
|
||||
: MvRxState
|
||||
|
||||
class PushGatewaysViewModel(initialState: PushGatewayViewState) : VectorViewModel<PushGatewayViewState>(initialState) {
|
||||
|
||||
|
||||
companion object : MvRxViewModelFactory<PushGatewaysViewModel, PushGatewayViewState> {
|
||||
|
||||
override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? {
|
||||
val session = viewModelContext.activity.get<Session>()
|
||||
val fragment = (viewModelContext as FragmentViewModelContext).fragment
|
||||
|
||||
val livePushers = session.livePushers()
|
||||
|
||||
val viewModel = PushGatewaysViewModel(state)
|
||||
|
||||
livePushers.observe(fragment, Observer {
|
||||
viewModel.setState {
|
||||
PushGatewayViewState(Success(it))
|
||||
}
|
||||
})
|
||||
return viewModel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<alpha
|
||||
android:duration="300"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
||||
|
||||
</set>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<alpha
|
||||
android:duration="300"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0" />
|
||||
|
||||
</set>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<translate
|
||||
android:duration="300"
|
||||
android:fromXDelta="100%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toXDelta="0%" />
|
||||
</set>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<translate
|
||||
android:duration="300"
|
||||
android:fromXDelta="0%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toXDelta="100%" />
|
||||
|
||||
<alpha
|
||||
android:duration="300"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0" />
|
||||
|
||||
</set>
|
Before Width: | Height: | Size: 726 B |
After Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 538 B |
After Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 1018 B |
After Width: | Height: | Size: 609 B |
Before Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 882 B |
Before Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="14">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M12,10.002L0,10.002a1.8,1.8 0,0 0,1.8 -1.8L1.8,5.2a4.2,4.2 0,1 1,8.4 0v3a1.8,1.8 0,0 0,1.8 1.8zM7.038,12.402a1.2,1.2 0,0 1,-2.076 0h2.076z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#454545"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="10dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="10"
|
||||
android:viewportHeight="14">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M1,4.818a4,3.818 0,1 0,8 0a4,3.818 0,1 0,-8 0z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#454545"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M2.834,8.03L2.143,13 5,11.364 7.857,13l-0.691,-4.975"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#454545"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="13dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="13"
|
||||
android:viewportHeight="14">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M2.2,6.4L10.8,6.4A1.2,1.2 0,0 1,12 7.6L12,11.8A1.2,1.2 0,0 1,10.8 13L2.2,13A1.2,1.2 0,0 1,1 11.8L1,7.6A1.2,1.2 0,0 1,2.2 6.4z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#454545"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M3.444,6.4L3.444,4c0,-1.657 1.368,-3 3.056,-3s3.056,1.343 3.056,3v2.4"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#454545"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|