Remove / Add pusher from enable notif preference

+Added Retrofit/Moshi null serializer for pusher kind
This commit is contained in:
Valere 2019-06-24 09:57:40 +02:00 committed by Benoit Marty
parent 4e6b34b9d1
commit 74099be316
20 changed files with 259 additions and 73 deletions

View File

@ -22,10 +22,10 @@ data class Pusher(
val pushKey: String,
val kind: String,
val appId: String,
val appDisplayName: String,
val deviceDisplayName: String,
val appDisplayName: String?,
val deviceDisplayName: String?,
val profileTag: String? = null,
val lang: String,
val lang: String?,
val data: PusherData,

val state: PusherState
@ -34,6 +34,7 @@ data class Pusher(
enum class PusherState {
UNREGISTRED,
REGISTERING,
UNREGISTERING,
REGISTERED,
FAILED_TO_REGISTER
}

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.api.session.pushers

import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import java.util.*


@ -52,6 +53,9 @@ interface PushersService {
append: Boolean,
withEventIdOnly: Boolean): UUID


fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>)

companion object {
const val EVENT_ID_ONLY = "event_id_only"
}

View File

@ -28,7 +28,7 @@ internal object PushersMapper {
return Pusher(
userId = pushEntity.userId,
pushKey = pushEntity.pushKey,
kind = pushEntity.kind,
kind = pushEntity.kind ?: "",
appId = pushEntity.appId,
appDisplayName = pushEntity.appDisplayName,
deviceDisplayName = pushEntity.deviceDisplayName,
@ -49,7 +49,7 @@ internal object PushersMapper {
deviceDisplayName = pusher.deviceDisplayName,
profileTag = pusher.profileTag,
lang = pusher.lang,
data = PusherDataEntity(pusher.data.url, pusher.data.format)
data = PusherDataEntity(pusher.data?.url, pusher.data?.format)
)
}
}

View File

@ -30,12 +30,12 @@ import io.realm.annotations.Index
internal open class PusherEntity(
@Index var userId: String = "",
var pushKey: String = "",
var kind: String = "",
var kind: String? = null,
var appId: String = "",
var appDisplayName: String = "",
var deviceDisplayName: String = "",
var appDisplayName: String? = null,
var deviceDisplayName: String? = null,
var profileTag: String? = null,
var lang: String = "",
var lang: String? = null,
var data: PusherDataEntity? = null
) : RealmObject() {
private var stateStr: String = PusherState.UNREGISTRED.name

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirec
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataFallback
import im.vector.matrix.android.internal.util.JsonCanonicalizer


object MoshiProvider {

private val moshi: Moshi = Moshi.Builder()
@ -42,6 +43,7 @@ object MoshiProvider {
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
)
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
.build()

fun providesMoshi(): Moshi {
@ -63,3 +65,4 @@ object MoshiProvider {
}
}



View File

@ -0,0 +1,23 @@
package im.vector.matrix.android.internal.di

import androidx.annotation.Nullable
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonQualifier
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.lang.reflect.Type

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class SerializeNulls {
companion object {
val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory {
@Nullable
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
val nextAnnotations = Types.nextAnnotations(annotations, SerializeNulls::class.java)
?: return null
return moshi.nextAdapter<Any>(this, type, nextAnnotations).serializeNulls()
}
}
}
}

View File

@ -495,6 +495,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
)
}

override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>) {
pushersService.removeHttpPusher(pushkey, appId, callback)
}

override fun livePushers(): LiveData<List<Pusher>> {
return pushersService.livePushers()
}

View File

@ -37,13 +37,10 @@ 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.DefaultProcessEventForPushTask
import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
import im.vector.matrix.android.internal.session.pushers.*
import im.vector.matrix.android.internal.session.pushers.DefaultGetPusherTask
import im.vector.matrix.android.internal.session.pushers.DefaultPusherService
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
import im.vector.matrix.android.internal.session.pushers.GetPushersTask
import im.vector.matrix.android.internal.session.pushers.PushersAPI
import im.vector.matrix.android.internal.session.room.*
import im.vector.matrix.android.internal.session.room.directory.DefaultGetPublicRoomTask
import im.vector.matrix.android.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask
@ -190,9 +187,12 @@ internal class SessionModule(private val sessionParams: SessionParams) {
DefaultGetPushrulesTask(get()) as GetPushRulesTask
}

scope(DefaultSession.SCOPE) {
DefaultProcessEventForPushTask(get()) as ProcessEventForPushTask
}

scope(DefaultSession.SCOPE) {
BingRuleWatcher(get(), get())
BingRuleWatcher(get(), get(), get(), get())
}

scope(DefaultSession.SCOPE) {
@ -207,12 +207,16 @@ internal class SessionModule(private val sessionParams: SessionParams) {
scope(DefaultSession.SCOPE) {
get<Retrofit>().create(PushersAPI::class.java)
}

scope(DefaultSession.SCOPE) {
DefaultGetPusherTask(get()) as GetPushersTask
}
scope(DefaultSession.SCOPE) {
DefaultRemovePusherTask(get(), get()) as RemovePusherTask
}

scope(DefaultSession.SCOPE) {
DefaultPusherService(get(), get(), get(), get()) as PushersService
DefaultPusherService(get(), get(), get(), get(), get()) as PushersService
}

}

View File

@ -16,19 +16,19 @@
package im.vector.matrix.android.internal.session.notification

import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver
import timber.log.Timber
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith


internal class BingRuleWatcher(monarchy: Monarchy,
private val defaultPushRuleService: DefaultPushRuleService) :
private val task: ProcessEventForPushTask,
private val defaultPushRuleService: DefaultPushRuleService,
private val taskExecutor: TaskExecutor) :
RealmLiveEntityObserver<EventEntity>(monarchy) {

override val query = Monarchy.Query<EventEntity> {
@ -40,34 +40,11 @@ internal class BingRuleWatcher(monarchy: Monarchy,
}

override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
//TODO task
val rules = defaultPushRuleService.getPushrules("global")
inserted.map { it.asDomain() }.let { events ->
events.forEach { event ->
fulfilledBingRule(event, rules)?.let {
Timber.v("Rule $it match for event ${event.eventId}")
defaultPushRuleService.dispatchBing(event, it)
}
}
task.configureWith(ProcessEventForPushTask.Params(events, rules))
.executeBy(taskExecutor)
}
defaultPushRuleService.dispatchFinish()
}

private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
val conditionResolver = DefaultConditionResolver(event)
rules.filter { it.enabled }.forEach { rule ->
val isFullfilled = rule.conditions?.map {
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false
}?.fold(true/*A rule with no conditions always matches*/, { acc, next ->
//All conditions must hold true for an event in order to apply the action for the event.
acc && next
}) ?: false

if (isFullfilled) {
return rule
}
}
return null
}



View File

@ -0,0 +1,51 @@
package im.vector.matrix.android.internal.session.notification

import arrow.core.Try
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver
import im.vector.matrix.android.internal.task.Task
import timber.log.Timber

internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params, Unit> {
data class Params(
val events: List<Event>,
val rules: List<PushRule>
)
}

internal class DefaultProcessEventForPushTask(
private val defaultPushRuleService: DefaultPushRuleService
) : ProcessEventForPushTask {


override suspend fun execute(params: ProcessEventForPushTask.Params): Try<Unit> {
return Try {
params.events.forEach { event ->
fulfilledBingRule(event, params.rules)?.let {
Timber.v("Rule $it match for event ${event.eventId}")
defaultPushRuleService.dispatchBing(event, it)
}
}
defaultPushRuleService.dispatchFinish()
}
}

private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
val conditionResolver = DefaultConditionResolver(event)
rules.filter { it.enabled }.forEach { rule ->
val isFullfilled = rule.conditions?.map {
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false
}?.fold(true/*A rule with no conditions always matches*/, { acc, next ->
//All conditions must hold true for an event in order to apply the action for the event.
acc && next
}) ?: false

if (isFullfilled) {
return rule
}
}
return null
}

}

View File

@ -23,7 +23,6 @@ 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
@ -55,7 +54,7 @@ class AddHttpPusherWorker(context: Context, params: WorkerParameters)
return Result.failure()
}

val result = executeRequest<Map<String, Any>> {
val result = executeRequest<Unit> {
apiCall = pushersAPI.setPusher(pusher)
}
return result.fold({
@ -82,7 +81,8 @@ class AddHttpPusherWorker(context: Context, params: WorkerParameters)
echo.kind = pusher.kind
echo.lang = pusher.lang
echo.profileTag = pusher.profileTag
echo.data = PusherDataEntity(pusher.data.url, pusher.data.format)
echo.data?.format = pusher.data?.format
echo.data?.url = pusher.data?.url
echo.state = PusherState.REGISTERED
} else {
pusher.toEntity(params.userId).also {

View File

@ -39,6 +39,7 @@ internal class DefaultPusherService(
private val monarchy: Monarchy,
private val sessionParam: SessionParams,
private val getPusherTask: GetPushersTask,
private val removePusherTask: RemovePusherTask,
private val taskExecutor: TaskExecutor
) : PushersService {

@ -98,6 +99,15 @@ internal class DefaultPusherService(
return request.id
}

override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>) {
val params = RemovePusherTask.Params(sessionParam.credentials.userId,pushkey,appId)
removePusherTask
.configureWith(params)
.dispatchTo(callback)
//.enableRetry() ??
.executeBy(taskExecutor)
}

override fun livePushers(): LiveData<List<Pusher>> {
return monarchy.findAllMappedWithChanges(
{ realm -> PusherEntity.where(realm, sessionParam.credentials.userId) },

View File

@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.pushers

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.di.SerializeNulls

/**
* pushkey string Required. This is a unique identifier for this pusher. See /set for more detail. Max length, 512 bytes.
@ -50,13 +51,13 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class JsonPusher(
@Json(name = "pushkey") val pushKey: String,
@Json(name = "kind") val kind: String,
@Json(name = "kind") @SerializeNulls 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 = "app_display_name") val appDisplayName: String? = null,
@Json(name = "device_display_name") val deviceDisplayName: String? = null,
@Json(name = "profile_tag") val profileTag: String? = null,
@Json(name = "lang") val lang: String,
@Json(name = "data") val data: JsonPusherData,
@Json(name = "lang") val lang: String? = null,
@Json(name = "data") val data: JsonPusherData? = null,

// 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

View File

@ -37,6 +37,6 @@ internal interface PushersAPI {
* 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>>
fun setPusher(@Body jsonPusher: JsonPusher): Call<Unit>

}

View File

@ -0,0 +1,60 @@
package im.vector.matrix.android.internal.session.pushers

import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync

internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> {
data class Params(val userId: String, val pushKey: String, val pushAppId: String)
}

internal class DefaultRemovePusherTask(
private val pushersAPI: PushersAPI,
private val monarchy: Monarchy
) : RemovePusherTask {

override suspend fun execute(params: RemovePusherTask.Params): Try<Unit> {
return Try {
var existing: Pusher? = null
monarchy.runTransactionSync {
val existingEntity = PusherEntity.where(it, params.userId, params.pushKey).findFirst()
existingEntity?.state == PusherState.UNREGISTERING
existing = existingEntity?.asDomain()
}
if (existing == null) {
throw Exception("No existing pusher")
} else {
existing!!
}
}.flatMap {
executeRequest<Unit> {
val deleteRequest = JsonPusher(
pushKey = params.pushKey,
appId = params.pushAppId,
kind = null, //null deletes the pusher
appDisplayName = it.appDisplayName ?: "",
deviceDisplayName = it.deviceDisplayName ?: "",
profileTag = it.profileTag ?: "",
lang = it.lang,
data = JsonPusherData(it.data.url, it.data.format),
append = false
)
apiCall = pushersAPI.setPusher(deleteRequest)
}
}.flatMap {
monarchy.tryTransactionSync {
val existing = PusherEntity.where(it, params.userId, params.pushKey).findFirst()
existing?.deleteFromRealm()
}
}
}


}

View File

@ -1,5 +1,6 @@
package im.vector.riotredesign.core.pushers

import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.AppNameProvider
@ -29,6 +30,9 @@ class PushersManager(
false,
true
)
}

fun unregisterPusher(pushKey: String, callback: MatrixCallback<Unit>) {
currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id),callback)
}
}

View File

@ -243,6 +243,10 @@ public class PreferencesManager {
editor.apply();
}

public static boolean areNotificationEnabledForDevice(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, false);
}

/**
* Tells if we have already asked the user to disable battery optimisations on android >= M devices.
*

View File

@ -1,17 +0,0 @@
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)
}

}

View File

@ -0,0 +1,56 @@
package im.vector.riotredesign.features.settings

import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.push.fcm.FcmHelper
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject


class VectorSettingsNotificationPreferenceFragment : VectorPreferenceFragment() {

override var titleRes: Int = R.string.settings_notifications

val pushManager: PushersManager by inject()


override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.vector_settings_notifications)
}

override fun onResume() {
super.onResume()
Matrix.getInstance().currentSession?.refreshPushers()
}

override fun onPreferenceTreeClick(preference: Preference?): Boolean {
if (preference?.key == PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) {
val switchPref = preference as SwitchPreference
if (switchPref.isChecked) {
FcmHelper.getFcmToken(requireContext())?.let {
pushManager.registerPusherWithFcmKey(it)
}
} else {
FcmHelper.getFcmToken(requireContext())?.let {
pushManager.unregisterPusher(it, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
super.onSuccess(data)
}

override fun onFailure(failure: Throwable) {
super.onFailure(failure)
}
})
}
}
}
return super.onPreferenceTreeClick(preference)
}
}

View File

@ -7,8 +7,8 @@
<!--android:title="@string/settings_enable_all_notif" />-->

<SwitchPreference

android:key="SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"
android:defaultValue="true"
android:title="@string/settings_enable_this_device" />
<!--android:dependency="SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY"-->

@ -26,6 +26,7 @@
android:dependency="SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"
android:key="SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY"
android:persistent="false"
android:visibility="gone"
android:summary="@string/settings_notification_advanced_summary"
android:title="@string/settings_notification_advanced"
android:enabled="false"