From 0e46fc4c0ac002da167a9b6e986cfe83ef34d049 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 19 Jun 2019 10:46:59 +0200 Subject: [PATCH 01/26] WIP --- .../im/vector/matrix/android/api/Matrix.kt | 9 +- .../matrix/android/api/pushrules/Action.kt | 93 +++++++ .../matrix/android/api/pushrules/Condition.kt | 51 ++++ .../api/pushrules/EventMatchCondition.kt | 88 +++++++ .../matrix/android/api/pushrules/PushExt.kt | 2 + .../android/api/pushrules/PushRuleService.kt | 35 +++ .../api/pushrules/PushRulesProvider.kt | 25 ++ .../android/api/pushrules/RulesetKey.kt | 26 ++ .../api/pushrules/rest/PushCondition.kt | 54 ++++ .../android/api/pushrules/rest/PushRule.kt | 38 +++ .../api/pushrules/rest/PushruleResponse.kt | 30 +++ .../android/api/pushrules/rest/Ruleset.kt | 27 ++ .../matrix/android/api/session/Session.kt | 40 ++- .../android/api/session/pushers/Pusher.kt | 44 ++++ .../api/session/pushers/PushersService.kt | 60 +++++ .../internal/database/mapper/PushersMapper.kt | 63 +++++ .../database/model/PushConditionEntity.kt | 29 +++ .../internal/database/model/PushRuleEntity.kt | 39 +++ .../database/model/PushRulesEntity.kt | 30 +++ .../database/model/PusherDataEntity.kt | 25 ++ .../internal/database/model/PusherEntity.kt | 58 +++++ .../database/model/SessionRealmModule.kt | 7 +- .../database/query/EventEntityQueries.kt | 8 + .../internal/database/query/PushersQueries.kt | 33 +++ .../internal/session/DefaultSession.kt | 101 ++++++-- .../android/internal/session/SessionModule.kt | 36 +++ .../session/notification/BingRuleWatcher.kt | 50 ++++ .../notification/MockPushRuleProvider.kt | 41 +++ .../session/notification/PushRulesManager.kt | 85 +++++++ .../session/pushers/AddHttpPusherWorker.kt | 98 ++++++++ .../session/pushers/DefaultPusherService.kt | 107 ++++++++ .../session/pushers/GetPushersTask.kt | 31 +++ .../internal/session/pushers/JsonPusher.kt | 68 +++++ .../session/pushers/JsonPusherData.kt | 27 ++ .../internal/session/pushers/PushersAPI.kt | 42 ++++ .../session/pushers/PushersResponse.kt | 25 ++ .../android/internal/session/sync/SyncTask.kt | 6 +- .../internal/session/sync/job/SyncService.kt | 238 ++++++++++++++++++ .../session/sync/job/SyncServiceOld.kt | 201 +++++++++++++++ .../internal/session/sync/job/SyncThread.kt | 77 +++++- .../internal/session/sync/job/SyncWorker.kt | 119 +++++++++ .../api/pushrules/PushRuleActionsTest.kt | 72 ++++++ .../api/pushrules/PushrulesConditionTest.kt | 103 ++++++++ tools/tests/app_standby_off.sh | 10 + tools/tests/app_standby_on.sh | 10 + tools/tests/doze_mode_disable.sh | 11 + tools/tests/doze_mode_enable.sh | 7 + .../src/debug/res/layout/item_pushgateway.xml | 122 +++++++++ .../riotredesign/push/fcm/FcmHelper.java | 2 +- vector/src/gplay/google-services.json | 107 +++++++- .../riotredesign/push/fcm/FcmHelper.java | 35 ++- .../fcm/VectorFirebaseMessagingService.kt | 110 +++++--- vector/src/main/AndroidManifest.xml | 21 +- .../vector/riotredesign/VectorApplication.kt | 179 ++++++++++++- .../vector/riotredesign/core/di/AppModule.kt | 20 ++ .../core/platform/VectorPreferenceFragment.kt | 3 + .../core/pushers/PushersManager.kt | 34 +++ .../core/resources/AppNameProvider.kt | 26 ++ .../core/resources/LocaleProvider.kt | 2 - .../services/HttpLongPoolingSyncService.kt | 102 ++++++++ .../core/services/RestartBroadcastReceiver.kt | 37 +++ .../core/services/VectorSyncService.kt | 74 ++++++ .../riotredesign/features/MainActivity.kt | 29 ++- .../features/home/HomeActivity.kt | 6 +- .../home/room/detail/RoomDetailViewState.kt | 2 +- .../detail/timeline/action/ActionsHandler.kt | 2 +- .../action/MessageActionsBottomSheet.kt | 4 +- .../timeline/action/MessageMenuViewModel.kt | 2 +- .../features/login/LoginActivity.kt | 8 +- .../notifications/NotifiableEventResolver.kt | 157 ++++++------ .../NotificationBroadcastReceiver.kt | 2 +- .../NotificationDrawerManager.kt | 5 +- .../notifications/NotificationUtils.kt | 27 +- .../notifications/PushRuleTriggerListener.kt | 46 ++++ .../features/settings/PreferencesManager.java | 2 + .../settings/VectorSettingsActivity.kt | 31 ++- ...sAdvancedNotificationPreferenceFragment.kt | 11 +- .../settings/VectorSettingsNotification.kt | 17 ++ .../VectorSettingsPreferencesFragment.kt | 9 +- .../VectorSettingsPreferencesFragmentV2.kt | 24 ++ .../features/settings/push/PushGatewayItem.kt | 46 ++++ .../settings/push/PushGatewaysFragment.kt | 71 ++++++ .../settings/push/PushGatewaysViewModel.kt | 54 ++++ vector/src/main/res/anim/fade_in.xml | 11 + vector/src/main/res/anim/fade_out.xml | 11 + vector/src/main/res/anim/right_in.xml | 10 + vector/src/main/res/anim/right_out.xml | 16 ++ .../main/res/drawable-hdpi/ic_settings.png | Bin 726 -> 0 bytes vector/src/main/res/drawable-hdpi/sync.png | Bin 0 -> 466 bytes .../main/res/drawable-mdpi/ic_settings.png | Bin 538 -> 0 bytes vector/src/main/res/drawable-mdpi/sync.png | Bin 0 -> 345 bytes .../main/res/drawable-xhdpi/ic_settings.png | Bin 1018 -> 0 bytes vector/src/main/res/drawable-xhdpi/sync.png | Bin 0 -> 609 bytes .../main/res/drawable-xxhdpi/ic_settings.png | Bin 1510 -> 0 bytes vector/src/main/res/drawable-xxhdpi/sync.png | Bin 0 -> 882 bytes .../main/res/drawable-xxxhdpi/ic_settings.png | Bin 1962 -> 0 bytes vector/src/main/res/drawable-xxxhdpi/sync.png | Bin 0 -> 1210 bytes vector/src/main/res/drawable/ic_bell.xml | 14 ++ vector/src/main/res/drawable/ic_flair.xml | 22 ++ vector/src/main/res/drawable/ic_lock.xml | 22 ++ .../main/res/drawable/ic_settings_general.xml | 22 ++ .../src/main/res/drawable/ic_settings_lab.xml | 14 ++ .../src/main/res/drawable/ic_settings_x.xml | 4 +- vector/src/main/res/drawable/ic_sliders.xml | 14 ++ .../main/res/layout/fragment_home_drawer.xml | 1 + .../layout/fragment_settings_pushgateways.xml | 16 ++ vector/src/main/res/values/config.xml | 9 +- vector/src/main/res/values/strings_riotX.xml | 8 + vector/src/main/res/values/theme_dark.xml | 3 + vector/src/main/res/values/theme_light.xml | 2 + .../res/xml/vector_settings_notifications.xml | 45 ++++ .../xml/vector_settings_preferences_root.xml | 54 ++++ 112 files changed, 3957 insertions(+), 249 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RulesetKey.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/Ruleset.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushConditionEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherDataEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersResponse.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncServiceOld.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt create mode 100644 matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt create mode 100644 matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt create mode 100755 tools/tests/app_standby_off.sh create mode 100755 tools/tests/app_standby_on.sh create mode 100755 tools/tests/doze_mode_disable.sh create mode 100755 tools/tests/doze_mode_enable.sh create mode 100644 vector/src/debug/res/layout/item_pushgateway.xml create mode 100644 vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/core/resources/AppNameProvider.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt create mode 100644 vector/src/main/res/anim/fade_in.xml create mode 100644 vector/src/main/res/anim/fade_out.xml create mode 100644 vector/src/main/res/anim/right_in.xml create mode 100644 vector/src/main/res/anim/right_out.xml delete mode 100755 vector/src/main/res/drawable-hdpi/ic_settings.png create mode 100644 vector/src/main/res/drawable-hdpi/sync.png delete mode 100755 vector/src/main/res/drawable-mdpi/ic_settings.png create mode 100644 vector/src/main/res/drawable-mdpi/sync.png delete mode 100755 vector/src/main/res/drawable-xhdpi/ic_settings.png create mode 100644 vector/src/main/res/drawable-xhdpi/sync.png delete mode 100755 vector/src/main/res/drawable-xxhdpi/ic_settings.png create mode 100644 vector/src/main/res/drawable-xxhdpi/sync.png delete mode 100755 vector/src/main/res/drawable-xxxhdpi/ic_settings.png create mode 100644 vector/src/main/res/drawable-xxxhdpi/sync.png create mode 100644 vector/src/main/res/drawable/ic_bell.xml create mode 100644 vector/src/main/res/drawable/ic_flair.xml create mode 100644 vector/src/main/res/drawable/ic_lock.xml create mode 100644 vector/src/main/res/drawable/ic_settings_general.xml create mode 100644 vector/src/main/res/drawable/ic_settings_lab.xml create mode 100644 vector/src/main/res/drawable/ic_sliders.xml create mode 100644 vector/src/main/res/layout/fragment_settings_pushgateways.xml create mode 100644 vector/src/main/res/xml/vector_settings_notifications.xml create mode 100644 vector/src/main/res/xml/vector_settings_preferences_root.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 3844e27b7a..87f0621303 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -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() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt new file mode 100644 index 0000000000..a7789aaaae --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt @@ -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? { + val actions = ArrayList() + 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 +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt new file mode 100644 index 0000000000..d20f969263 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt @@ -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? + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt new file mode 100644 index 0000000000..0c729799bc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt new file mode 100644 index 0000000000..fd9a95bea2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt @@ -0,0 +1,2 @@ +package im.vector.matrix.android.api.pushrules + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt new file mode 100644 index 0000000000..47328d1755 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt @@ -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) + fun batchFinish() + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt new file mode 100644 index 0000000000..75520f9a22 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt @@ -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 + + fun onRulesUpdate(newRules: List) +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RulesetKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RulesetKey.kt new file mode 100644 index 0000000000..834bdf2add --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RulesetKey.kt @@ -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("") +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt new file mode 100644 index 0000000000..72fc2b242c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt new file mode 100644 index 0000000000..58d36cfcdb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt @@ -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, + //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? = null, + //The glob-style pattern to match against. Only applicable to content rules. + val pattern: String? = null +) + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt new file mode 100644 index 0000000000..5642bf820c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt @@ -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 +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/Ruleset.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/Ruleset.kt new file mode 100644 index 0000000000..5f4ca15ac0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/Ruleset.kt @@ -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? = null, + val override: List? = null, + val room: List? = null, + val sender: List? = null, + val underride: List? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 6f8d745e85..1ad26c2daf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -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. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt new file mode 100644 index 0000000000..f0f9453f80 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt @@ -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 +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt new file mode 100644 index 0000000000..c28f831373 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt @@ -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 status = workManager.getWorkInfoByIdLiveData()) + */ + 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> +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt new file mode 100644 index 0000000000..c1df3b2e29 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt @@ -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) +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushConditionEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushConditionEntity.kt new file mode 100644 index 0000000000..e6c3d406c8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushConditionEntity.kt @@ -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 + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt new file mode 100644 index 0000000000..30ef90a817 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt @@ -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? = RealmList(), + //The glob-style pattern to match against. Only applicable to content rules. + var pattern: String? = null +) : RealmObject() { + + companion object + +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt new file mode 100644 index 0000000000..fc8d66b599 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt @@ -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 = RealmList() +) : RealmObject() { + companion object +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherDataEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherDataEntity.kt new file mode 100644 index 0000000000..87694fb723 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherDataEntity.kt @@ -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 +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt new file mode 100644 index 0000000000..117aea5725 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt @@ -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 +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index 20ba8d44d0..b926da9907 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 8fca0abd38..6f4db6f4b4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -53,6 +53,14 @@ internal fun EventEntity.Companion.where(realm: Realm, } +internal fun EventEntity.Companion.types(realm: Realm, + typeList: List = emptyList()): RealmQuery { + val query = realm.where() + query.`in`(EventEntityFields.TYPE, typeList.toTypedArray()) + return query +} + + internal fun EventEntity.Companion.latestEvent(realm: Realm, roomId: String, includedTypes: List = emptyList(), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt new file mode 100644 index 0000000000..f5e9a6d00e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt @@ -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 { + return realm.where() + .equalTo(PusherEntityFields.USER_ID, userId) + .apply { + if (pushKey != null) { + equalTo(PusherEntityFields.PUSH_KEY, pushKey) + } + } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index ecbdd15603..20dc4cf79b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -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() private val contentUrlResolver by inject() private val contentUploadProgressTracker by inject() + private val pushRuleService by inject() + private val pushersService by inject() private var isOpen = false + private val bingRuleWatcher by inject() + @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 { @@ -261,11 +300,11 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi override fun clearCache(callback: MatrixCallback) { assert(isOpen) - syncThread.pause() + // syncThread.pause() cacheService.clearCache(object : MatrixCallbackDelegate(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> { + return pushersService.livePushers() + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 4a5d3b4e53..9d24ae54c2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -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() 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(groupSummaryUpdater, userEntityUpdater, aggregationUpdater, eventsPruner) } + scope(DefaultSession.SCOPE) { + get().create(PushersAPI::class.java) + } + scope(DefaultSession.SCOPE) { + DefaultGetPusherTask(get()) as GetPushersTask + } + + scope(DefaultSession.SCOPE) { + DefaultPusherService(get(), get(), get(), get()) as PushersService + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt new file mode 100644 index 0000000000..371c7f2ce0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -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(monarchy) { + + override val query = Monarchy.Query { + + EventEntity.types(it, listOf( + EventType.REDACTION, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED) + ) + + } + + override fun processChanges(inserted: List, updated: List, deleted: List) { + //TODO task + inserted.map { it.asDomain() }.let { + pushRulesManager.processEvents(it) + } + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt new file mode 100644 index 0000000000..e4329c6574 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt @@ -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 { + 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::class.java).fromJson(raw) + + return listOf( + pushRule!! + ) + } + + override fun onRulesUpdate(newRules: List) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt new file mode 100644 index 0000000000..5c5a7daba2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt @@ -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() + + + 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) { + 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 + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt new file mode 100644 index 0000000000..0eb6344f12 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt @@ -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() + private val monarchy by inject() + + override suspend fun doWork(): Result { + + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.failure() + + val pusher = params.pusher + + if (pusher.pushKey.isBlank()) { + return Result.failure() + } + + val result = executeRequest> { + 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() + + }) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt new file mode 100644 index 0000000000..40b7fe42b2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -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 { + 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() + .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> { + return monarchy.findAllMappedWithChanges( + { realm -> PusherEntity.where(realm, sessionParam.credentials.userId) }, + { it.asDomain() } + ) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt new file mode 100644 index 0000000000..5c730d14b5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt @@ -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 + +internal class DefaultGetPusherTask(private val pushersAPI: PushersAPI) : GetPushersTask { + + override suspend fun execute(params: Unit): Try { + return executeRequest { + apiCall = pushersAPI.getPushers() + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt new file mode 100644 index 0000000000..7616be492a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt @@ -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. + * + * + * { + * "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" + * } + * }] + * } + * + */ + +@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 +) + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt new file mode 100644 index 0000000000..0f0a55ee31 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt @@ -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 +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt new file mode 100644 index 0000000000..972515130b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt @@ -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 + + /** + * 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> + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersResponse.kt new file mode 100644 index 0000000000..426b5c9e0e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersResponse.kt @@ -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? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 1f09f4c611..63bc3f43f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -29,7 +29,7 @@ import im.vector.matrix.android.internal.task.Task internal interface SyncTask : Task { - 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 { val requestParams = HashMap() - 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() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt new file mode 100644 index 0000000000..300ae4c5cd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -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 and 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 { + 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() + + 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) { + + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncServiceOld.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncServiceOld.kt new file mode 100644 index 0000000000..a76aa99d84 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncServiceOld.kt @@ -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 and 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 { + 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() + + 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) { + + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 7bad5e64d9..6d644ca547 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -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 + } } - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt new file mode 100644 index 0000000000..441f7ef770 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -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() + private val filterRepository by inject() + private val syncResponseHandler by inject() + private val sessionParamsStore by inject() + private val syncTokenStore by inject() + + val autoMode = false + + override suspend fun doWork(): Result { + Timber.i("Sync work starting") + val params = WorkerParamsFactory.fromData(inputData) + ?: Params() + + val requestParams = HashMap() + 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 { + 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() + .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) + + } + + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt new file mode 100644 index 0000000000..eeef3f5c66 --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt @@ -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::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) + + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt new file mode 100644 index 0000000000..2c7e29b227 --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -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)) + } +} diff --git a/tools/tests/app_standby_off.sh b/tools/tests/app_standby_off.sh new file mode 100755 index 0000000000..fc3de6a436 --- /dev/null +++ b/tools/tests/app_standby_off.sh @@ -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 diff --git a/tools/tests/app_standby_on.sh b/tools/tests/app_standby_on.sh new file mode 100755 index 0000000000..cdd843338b --- /dev/null +++ b/tools/tests/app_standby_on.sh @@ -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 diff --git a/tools/tests/doze_mode_disable.sh b/tools/tests/doze_mode_disable.sh new file mode 100755 index 0000000000..396a3db5fa --- /dev/null +++ b/tools/tests/doze_mode_disable.sh @@ -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 diff --git a/tools/tests/doze_mode_enable.sh b/tools/tests/doze_mode_enable.sh new file mode 100755 index 0000000000..ebe3be6cfb --- /dev/null +++ b/tools/tests/doze_mode_enable.sh @@ -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 diff --git a/vector/src/debug/res/layout/item_pushgateway.xml b/vector/src/debug/res/layout/item_pushgateway.xml new file mode 100644 index 0000000000..e50a001507 --- /dev/null +++ b/vector/src/debug/res/layout/item_pushgateway.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.java b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.java index 82f2df9aca..3efc4990e9 100755 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.java +++ b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.java @@ -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 } } diff --git a/vector/src/gplay/google-services.json b/vector/src/gplay/google-services.json index 8ffc2cef44..187c727a24 100644 --- a/vector/src/gplay/google-services.json +++ b/vector/src/gplay/google-services.json @@ -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 + } + ] } } } diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java index 00fff7dd93..17bdc909aa 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java @@ -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() { - @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."); } - } +// } } /** diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt index dfdfe6be77..ba942124d1 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt @@ -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() + private val notificationDrawerManager by inject() + private val pusherManager by inject() 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 /*, pushManager: PushManager*/) { + private fun onMessageReceivedInternal(data: Map) { 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() + .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) ?: "" diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index fdbbd370f0..18e7eb7727 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -3,7 +3,6 @@ xmlns:tools="http://schemas.android.com/tools" package="im.vector.riotredesign"> - + + + - @@ -58,9 +62,8 @@ android:label="@string/encryption_message_recovery" /> - @@ -69,6 +72,15 @@ + + + + + + + - \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index 2f2de8ed08..d8af4433b9 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -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() FontsContractCompat.requestFont(this, fontRequest, koin.koinContext.get(), 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(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() +// .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().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() +// } + + } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 5b875fd1ca..926a5bc718 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -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()) + } } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/platform/VectorPreferenceFragment.kt b/vector/src/main/java/im/vector/riotredesign/core/platform/VectorPreferenceFragment.kt index a915ea8aff..ddd908090b 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/platform/VectorPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/platform/VectorPreferenceFragment.kt @@ -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}") } diff --git a/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt new file mode 100644 index 0000000000..7bf5a4a2c0 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt @@ -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 + ) + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/resources/AppNameProvider.kt b/vector/src/main/java/im/vector/riotredesign/core/resources/AppNameProvider.kt new file mode 100644 index 0000000000..4adfde792d --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/resources/AppNameProvider.kt @@ -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" + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/riotredesign/core/resources/LocaleProvider.kt index 991177a7e7..8393bc0960 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/resources/LocaleProvider.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/resources/LocaleProvider.kt @@ -25,6 +25,4 @@ class LocaleProvider(private val resources: Resources) { fun current(): Locale { return ConfigurationCompat.getLocales(resources.configuration)[0] } - - } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt b/vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt new file mode 100644 index 0000000000..501a2c7763 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt @@ -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() +// 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) +// } +// } +// } +//} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt new file mode 100644 index 0000000000..1834a994ed --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt new file mode 100644 index 0000000000..e56399b917 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt @@ -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) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/MainActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/MainActivity.kt index 2cc31dff1e..f22655a30d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/MainActivity.kt @@ -59,17 +59,24 @@ class MainActivity : VectorBaseActivity() { } else { // Handle some wanted cleanup when { - clearCredentials -> session.signOut(object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.w("SIGN_OUT: success, start app") - start() - } - }) - clearCache -> session.clearCache(object : MatrixCallback { - override fun onSuccess(data: Unit) { - start() - } - }) + clearCredentials -> { + session.signOut(object : MatrixCallback { + 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 { + override fun onSuccess(data: Unit) { + //TODO start sync service + start() + } + }) + } else -> start() } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index ffe59919c6..ee63d806ec 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -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() + private val pushManager by inject() // TODO Move this elsewhere private val incomingVerificationRequestHandler by inject() @@ -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) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt index 927bbba11b..2cf4134948 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt @@ -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, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt index 84cfc40f0f..85b9012923 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt @@ -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() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 95777b8d7e..f80a62f8bd 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -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() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 1d9c33f6fa..f523a77798 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -37,7 +37,7 @@ data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, data class MessageMenuState(val actions: List = 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(initialState) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt index ffadac1186..eba8a85e75 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt @@ -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().startWithSession(data) goToHome() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt index eb83b12a7e..befff24a58 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt @@ -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()?.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()?.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)*/) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt index 36692b7def..574d267965 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index ce207397c2..15a3ef8bac 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt index 9596fdad42..aed05d2589 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt new file mode 100644 index 0000000000..b94cb7f6e7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt @@ -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) { + 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) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java b/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java index 4a91759299..2d0000c2c0 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java @@ -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"; diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt index 6208c75e4b..85f1e0bec9 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt index eee04547ec..05109c283e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -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") } diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt new file mode 100644 index 0000000000..d1ce1e77dc --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt @@ -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) + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt index d821c7f7fa..d073d56066 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt @@ -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() @@ -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) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt new file mode 100644 index 0000000000..0ef9d93a65 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt @@ -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) + } + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt new file mode 100644 index 0000000000..f57d76b853 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt @@ -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() { + + @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(R.id.pushGatewayKind) + val pushKey by bind(R.id.pushGatewayKeyValue) + val deviceName by bind(R.id.pushGatewayDeviceNameValue) + val format by bind(R.id.pushGatewayFormatValue) + val url by bind(R.id.pushGatewayURLValue) + val appName by bind(R.id.pushGatewayAppNameValue) + val appId by bind(R.id.pushGatewayAppIdValue) + } +} + +// +//abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder() { \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt new file mode 100644 index 0000000000..bdb29e8358 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt @@ -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() { + override fun buildModels(data: PushGatewayViewState?) { + val pushers = data?.pushgateways?.invoke() ?: return + pushers.forEach { + pushGatewayItem { + id("${it.pushKey}_${it.appId}") + pusher(it) + } + } + } + + } +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt new file mode 100644 index 0000000000..69c0b9c45b --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt @@ -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> = Uninitialized) + : MvRxState + +class PushGatewaysViewModel(initialState: PushGatewayViewState) : VectorViewModel(initialState) { + + + companion object : MvRxViewModelFactory { + + override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? { + val session = viewModelContext.activity.get() + 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 + } + + } + +} \ No newline at end of file diff --git a/vector/src/main/res/anim/fade_in.xml b/vector/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000000..fbbaaf17d0 --- /dev/null +++ b/vector/src/main/res/anim/fade_in.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/vector/src/main/res/anim/fade_out.xml b/vector/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000000..fe99ebf7c2 --- /dev/null +++ b/vector/src/main/res/anim/fade_out.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/vector/src/main/res/anim/right_in.xml b/vector/src/main/res/anim/right_in.xml new file mode 100644 index 0000000000..35f261c926 --- /dev/null +++ b/vector/src/main/res/anim/right_in.xml @@ -0,0 +1,10 @@ + + + + diff --git a/vector/src/main/res/anim/right_out.xml b/vector/src/main/res/anim/right_out.xml new file mode 100644 index 0000000000..504c39b8bf --- /dev/null +++ b/vector/src/main/res/anim/right_out.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/vector/src/main/res/drawable-hdpi/ic_settings.png b/vector/src/main/res/drawable-hdpi/ic_settings.png deleted file mode 100755 index 7ef944dbd04ec62fc7b8a6e18e0df5e094a19fce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 726 zcmV;{0xA88P)YN+qP{pwr$(_iEZ1qZ96-5PuJ~1MYHO+QMS^|($dl>N@s#Yarwv9 zgt}IWNn3F}1)0MRITalju!24H z<{N4eRYSg^Cwp4WrHYCMq*7x=!^5Q7)3W0zMv`jdIYw6eh7bS9hI3eo4=XrF7F5NT znsVb_qS(lAO7b%=^C~}4hG9f=uUzPXT`O+TUbuyp*!7YN^RZdYhl+vUv05${K4m>_ zYr`{}Z?V|GS90SK=Hl|39LSE#0v@xEW4#E|TL$zd%s?)Z9mi0T*zS=5_Yhk-j#X5= z$4Y_>krAU!&B2ae2$D-ilpx5zGO<_8_hrPV1SzO^QHmgs$%rQjQdaSzEJ2=>5l;}L zwBkiUf_yF`J|ajy#f$$4QcOnVO6t0w4_Qr+(K2EvLDuj|JS)=*#CDeqxSQB2a;)sQ zh=GX@9rPhge=d*>kFfxkta9L2T;}qK-1v?SSbVDxJit2K*7K=cSccW_&gS4YUoP~* zt|d1oIEC#d7aopllK<-F@H1r?9`Wtl$TzaMCvRTyK4D&xZ`?wds}8J-1VxpWmX>C<36o)sW-vyoq5uE@07*qo IM6N<$f_6$-d;kCd diff --git a/vector/src/main/res/drawable-hdpi/sync.png b/vector/src/main/res/drawable-hdpi/sync.png new file mode 100644 index 0000000000000000000000000000000000000000..f191e2dddfea38d409d64db9a74de4504463018c GIT binary patch literal 466 zcmV;@0WJQCP)E$TJgBlDSx?>5L zf6{AiAm$vTrCxFBLZ)LhYe@|yLl@Em0r+=%bfrGt9Wup?b%)C~m@s*uFgw&?hk8=K zGDGHTy#zuUdc@3~WJctf&LZdojX0;Nv?_j2JaDSv#Ok@_S5A^@PNgo@`D(hniRMSv zrN3xMYpG`$n)yKOpa1{>07*qo IM6N<$g1t)3TL1t6 literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-mdpi/ic_settings.png b/vector/src/main/res/drawable-mdpi/ic_settings.png deleted file mode 100755 index 10f0d15d0faf245aff34014bc156473e3d4923d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmV+#0_FXQP)d|n3Pmz_ z`2aj)9+l;zBR8?RNoV<}${d~pA1JCQEGN!c_7G(cXNa>}ZdBwCN#RjVF6<$x_REfz zB!`wVBL{bI^SJHD!|e_^WWq39_RxSl@{xxI?8arJYyFr_R0uuzhRrIOkdLjHO_Kqm zFxyH#*)Rc*uuHPw0%0Cgjb67-#1vLG+t3F@u*U?@QkTzOZqAM7t_@FOAKO94piys8_S2Wu2kyssMK?h=epq<>9!au@J%YrL}c}$WG1=xkz zNEt94vyJ4H2`jPrP7j4J7Q3Y~VKgqgX+U22$V)?Z<1$PpZVn9dIJQJwj`1l~|o z(P%_7#b7liixfZ(8cJ#f cWf0l^0}KSHGP+ePUH||907*qoM6N<$g2_JaI{*Lx diff --git a/vector/src/main/res/drawable-mdpi/sync.png b/vector/src/main/res/drawable-mdpi/sync.png new file mode 100644 index 0000000000000000000000000000000000000000..2542f326ca3bea1b97bd3cb36d5c0b064577023e GIT binary patch literal 345 zcmV-f0jB=ts1uWYtc9+S2>uhGnjBy-y+Brr9tS^xl6!&p z5wap8*>{2JUgK2@^g%Q^fsdpo4&4IuI6#*tm5&}Gs1XWu2`EmYfLN6XlTCnXyphx$ z0YRY>c?FS5& rdnl$-g9CK68W8hS!>my=Xd40mUB3jB(r)1K00000NkvXXu0mjfKc#*+ literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xhdpi/ic_settings.png b/vector/src/main/res/drawable-xhdpi/ic_settings.png deleted file mode 100755 index 56d5d9fc27d936b53b8cf346777de4d87ddde38f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1018 zcmVYN+qP}nwr$(CZQHgn?)=}Fc`Dhpt0$?8e*3!3wW>8hrBbO>Dizk_ z=!Vl_%%yy>0v2m3S6qO_HOdu_V)24<#g|z8qC7DiRwFA%%!t)I$`Pw#wSjWPo>(2C z9C0pIS13n3h}AR75ual9gIbGGxPu=!i76Ds!gR-OWd$)A$MQJ)GQ7&d3G`Lt9#&Kx z7T{rGJ;(g2!!q1XL0@O7985`f2K5PhF{b)qLT<;z{;n zb`64&*_g*kah&WJmri&+%KQpoAs)l4D-+3z74hlT{90u*df>CFtQec0iT67T$bd!p zjd;H?Ufd`2o5ZQHq>PxCF2wneY2u$lmk{G1MPN;0+`uUEU{g9{^R6OsoB8J5S$@pI zmsl*XNK8XPbKfwJ(l7@1(9H94;8vP>jImS(_NSnco#epgG*aUThEZ8Kjj)1oT z5O$%;#Px)IEGIr8>^7B&#|V2`PCP@{lPVLh5O%YixP`EnTekx@$cY z%5h=(aC^&d;yYC)E+(vCEN5v`v}81yVK{;sjch6hcBGLGj%QeXLmKr>G> zuF^1%Dcl#fuXRW2`<{8^$If)e=5|HmJ#4zNgLhcz1`K>fI0q8r21bhiTK|wZU6@x! zEJ=+x?=odvt@1nZelv#p`CLo?g|W;0X~18rY(Y1CR*)4F(;crz8WvlR;MIw7WXFl5 zc$7^BzwMZfeR-M`hbRW4@+)cn{wCEojG#DdNlw=GWq@2fBM%lL4+XQz zhik~ex$gm&9SDHwroXr~VtMrl}^u&?p6EoI4})kyrydCbHx?99KU z_>-+A=HwDO8&ZZ3vr;gq_t}Nf)DM$z4_>!0;ULjjb zVm(qH78!;WxtAJ!9j~%50{ij=$1t&iSQ)$SjjnB`;$(i{c1Bfe@e@{`C`UYp)dR{A zS7CLIa>QX+?V%j85mu`xN6d%S49XE>VpA}j^2Bdge4$+N5*CjrS6qw51`&ZbiU0rr07*qoM6N<$f<}Vr^#A|> diff --git a/vector/src/main/res/drawable-xhdpi/sync.png b/vector/src/main/res/drawable-xhdpi/sync.png new file mode 100644 index 0000000000000000000000000000000000000000..64478c4e139e7c86fcef3839f789aded4a8be109 GIT binary patch literal 609 zcmV-n0-pVeP)Y5QcL>6hRxc34T=CDmG$aWhGX2VrOG5cIFR=h`+$X zLJ=D+gIEbF*eKXoT16v*S{Oe9hMdoe#U9vH7D-9WaP1tHo? zd;tszc{RbGz8mr#;3S}S;Z97Y>~NbU7aT8--tc)JnylIobXV{Pk~C9d%L>s`pB&{zXybg27CEN@7>f ztd!$U_)#nB8Yq+86IHhDBGm0qkY1vU6 z_L0vt%u5u>#VKSVMxFly>;<(aMAMx@VqyXlO58|+6$f1ddkGMgmN-TIS7|bqpGPQ- zO|16Z1CTzNVsWN literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxhdpi/ic_settings.png b/vector/src/main/res/drawable-xxhdpi/ic_settings.png deleted file mode 100755 index 879eb9a7024f2c8f72a7a3a14ec94ae2e76a5158..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1510 zcmVAUu#RmzIs2jqv2EM7t+Ow-ZQHhO-5OcU&Oc6$bKPB4eLFSv>&I@DJ}8PH z2!bF8f*=TjAP52ma3W{Kx{jhkg@SP;@eU;_1oR`REmR0NoTScFAz%ziU9UpGLnQUG z3IU&yRHgh!7cwL_hkVEKnCLIxu@NSAlJ7VS6Q_kqKDTS+J08Tu3-TSGV&d1rp5((s z&vKaRBcHJ$rnXnUpp>2zD;5sL)X9p4uJoi_>0>8qfm+6~iXvfN&Y~7m4GdvHMZ$^< zqZ(*nKc$QQwDZ3g*^@Hmz7b1BX~eeQ z7(#`ngB2J;HB+uz?e9Oj~48`og6U@k=N(bfqKrYpc{`*^^ zh>^H<2_^q5ATFYkT&kE`Y2X~9c#b`Jg*3dwo;*erW91%8&`iF{u!>ycR`S`?a*MUd zcbmx_I`KaF?rVzV3j5)MW90_3Q-u%em|re13}0O4<|L;TUu5Yi+qe&(yezv|7vF4W z_aymKeDeb(PBVHuaH{NJZt8F=!z@#wWP(h)hw5eRXUTJH#{=7aC-W z_pt3dmQw<3Pb0Q{EK_Vv7Ms3eNhQQ;eAfY&i>yyIw*1U8GC(()v892{WCu&~7B;le zTZY&XJ3e7~*+U7}VD30s!18i9b zH_;831B@rSMU*y{Cb|>l0H+v?f$q_R=nk3Xg;m`<_5(Vs5FgMA4$9!%{bI{2Z7fD~ zTx;1BScrE^zl7W{VWIgnW$=%KIY^bH-n^u^72aNSGwrN*Tiwc zEY6qwkv{2`LNyc5`?8EDh^K{pl?I9#M-qc9uj8}CcMZjIi$!^ZWNw#5TthOiv9R1> zZ7NAB+hGpv*OVI^M|%ee`@D{cHjZ?WME(Hg&aw2O%-uwp>|tfTz>Xbch~Bhe!+R_# zJJ^OMY-ysK46qD8V@oyb+gbD2^)0I!BvE{gP3>$YQ+$GLO>CF&faN=Edtat#z=g3C zD;Bn(9{Va~iidII1G*_9mgi2~xK*YYfGZj9W+S>NCl+P`nGRj#%MG~nBSYDM*`~X= zIF}b_!=2HxfrV+twG3}C>c3Ki3%P-}al}ER0Zzw92g?>p_yOO%PZ!z44)|s**~QEF z6;J$Q#Syu#i*M-*qsoP|aMzcPen{wc-16xT-6RexYrMp0wl zjKX!RiYM8DB29*k`2knHVm(cUGIr;AW@JYhFmHA;iJ>ergP`LOnz5~s^C_D_>F;Rj zj2hyYV?8GKG&@nG99Wp~R?BgRFrRXuoIQAv|7GZ_bg?guKqbRjPLZ&Xm5#tQoXuQ{ zgzk)`4ya*ArH^uY(p9l=Dy9xpEELm=QsoPF#MHX-8T~M|lzheC3lEft{6qIumXT-WrjP~^eK@bE%5ClOG1VIq|4%fpqQz~Zvt^fc4 M07*qoM6N<$f)fDKxBvhE diff --git a/vector/src/main/res/drawable-xxhdpi/sync.png b/vector/src/main/res/drawable-xxhdpi/sync.png new file mode 100644 index 0000000000000000000000000000000000000000..b613b0d7a0da0d8409f7046a1926bfb6431829da GIT binary patch literal 882 zcmV-&1C9KNP)z(gdUwH$Le$KpaF9~Fd5rjxbq)PHW*v|6oA$t^3t)-)sZ=8IzzwV`S4-n`Q+)AbO|IMCyWa20YGb z^EL$oVuO26lmTy~YBxM*dN*f%ifq&wx{SV~y4VO4T^0*Rb((J`A>m@)??k;QcojCs zcj)!sg(55eLx=Ls%`~wqXe+pOqm>oL_L;f8M9)}7jgHKduT~ElLUpniOmh2kmZv8p+jfY%eisoOyfD9h1>19Jgs?4lz~nl7juMRM~o)UKyV!ci4Ob|HCE zE!6JOalRu|dM*lGjwV9M&~Fs=q({P25yf6pz>#MmS9?OG>89XMfq>-s$L(oS@Ki;? zivr>Nrr!Zin6%v#{w@I0nY1b7R0kZnNswn(a)zhh1etDh$3tikf7Gvmq{s;b)Q<#| zJP#?iatJ2J8UO$Q07*qo IM6N<$g6*J*LjV8( literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxxhdpi/ic_settings.png b/vector/src/main/res/drawable-xxxhdpi/ic_settings.png deleted file mode 100755 index 82d679bdc9a626c071238bbe60967f2954d75302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1962 zcmV;b2UYlqP)G0000MZNklqZQF=FIX)+PZstxf?*CPs%$@4$+Iv?|uU~zG z=KEB=Yi&_VDW#NBN-3q3Qc5YMlu}BmBt;bl@HQXEalOqzs*Cc22CN|n>uDw`c6>lk z7K(}$HHZ?NRuUUUAA)p-#71!`LApm`qZmSv=16Q5FA${9B{qur1Zj=LMv+ahBBH#b zEIMSqKIAGZ|_m19_bZ98L-Gjmm7sjCv9WdNGnW7)~7t zCz`N^?b5k}suDArGnuWJlEb64l9*A6>sZEi8FZHLA(hqaE{CV+Bc9QUx3J+ux`=0V zW)@r7-3BU1*l;KNwv-#G15c$|v zz!=I&A!yAcHdDBK4wNuq7Db!KRg{%I(2sOn`JH2A50vJ7zNBdHN_fx~uQoA-=CTcG zjKQ^;l$LF1!YDHE>L>{dz9BYWb0HBLGmkJ>Mhnt7laGkaN=iu>a0Y&4kV#l%vkpIQ z5ci1i7s)D^>29=mMzY#T9-6)UTw93!KaUs9VK8 z%JLWgqcW)>w(&4KscH7(XEr;jsO31y)9g-t;yQk+Q^XER@DqDezY-A}xQKmIciD-b z>)1E7#X=lqFojdI#Vi_7D9JUoOfQZC_2m>gQcU1zY+6Pw2?(`VhD}|>4DO`> zJC;yg0z`F|6pQC$^k)O+t)`{~jGC;*yv>{}W>KGcn6-um?ia!OvWg~R9uXeJoG3lS z5PA~DoY$x*wr~YGnDLMp#3Ptdz};dO9r&Bz{X{7-h*JDO@HWuj%~;Z>1S_8wVi+yS zCs^~SFK$qZ5d>+xSil5=^e7Q=iIdq%F!HD)7EqTwf|0`&;vOwnMG&ToB}^v>ztcg& zfr`9F{5pyybS8eEQcc2x65K-p@kkeoSV??_Q%b^y{%pj*0b&sY+~006fJWrvSMP*b ziq=lTiU@vXlOh(8LN6a4BYVZ}-KHAyUEGJahkVZ}B0b)8to4fu73 zgcWx?s&@Opso}g|>#8?;gjmLr_%%Ypii!BuLoA~weoZQlZ$LM(jPCd~LBfjR3G@?m zL~;BHdN^?wYjYZYJtASn9r$&90>#jKB&@gqzov*~OvSIuB&_I1gOIUGm zQrwK|EMY}QNP^qZM@u+yGFy}4flLmUOW2UYDB`nHEFzuwJW86F<)EU~SuCL|@%x@S z5)O3b@5I`1iVFIuP>`u$e_!|>`5akV@VZzg*ykQn53=rkCdv}2-Z#W$jdWiCd zc9>98ls7~OUZyDTSnD)|Dn54_LKSnIhET=5PD7~T45uMf(ZOj5RaA5wLKW+rhET=cbzLlD-{M3f&?WdLvUaU9p% w45W%Ay(FcSQc5YMlu}A5rIb=iDW#P97v1F?2}4-q5C8xG07*qoM6N<$f(CtqCIA2c diff --git a/vector/src/main/res/drawable-xxxhdpi/sync.png b/vector/src/main/res/drawable-xxxhdpi/sync.png new file mode 100644 index 0000000000000000000000000000000000000000..99adbef69c6490aa22beef47b86388a2341782ee GIT binary patch literal 1210 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoEa{HEjtmSN`?>!lvI4nuN)>ARpPf`^r3bBDoYNuROQjWAtOgVJmOoI)ZkwaIIxzUF? z6)~^8I`=qDvH8sPu;{Pd&aJ!ezMl7aEr0p^zyDv|k4(S+ckS1;_qSiS%3fc4{^b=< zH6|qmV$ngCS+izMTm1NiZB?t(g{mvMJ2XS5t1>*;_d+A<^t+GkhLQ49n>iS^sjtfK zu6g#u_tf6zS)rzG3=8I7+{p9fTi0zDy$K9kwr_XebM$^bi&^}eRSdhgvEC`V|4rL$ zXTv?`D#c%MdLPeJ#%q@IaV*>s%22@dYer*~=f^m~6MwgA^ITG9tdRLy)O6?Rm&wu} zelL!m8uL!~+}_i_*D1YJVAzrO!sgSgyORC|Cc5Iv411X4KK-b5E_S`-$xsn3^kDXD zuk_1B%=fxnenh)o^kn!@JYntay=5H7CvE;2vs<-Xs^GC=0prp;TkQ`1k7if>IdzBJ zV&M$`l_6Zc#+r-=+J46KzpWUhT|p3!c|6}RNrJ2^&^n8~*)`u_Ib zKGN|cSjfOYsX^wt;HUGD6iFb+&+2n34hl49HqV@HLtf>Gd}ZCIA1Zd$86E; z6`Y}qZ%H|+q3TzPs#$k9 zo~ZkGAe+B%%m22?p) z?kh_bG0~szj4TW}_98o^Y#r<;%*;I{>t@R6yP~Uz uli^OCTRMwZ9a~V|Y%6~j7Y8EH1NGRy@jjZzCN2RMMhu>=elF{r5}E+Zsua=y literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable/ic_bell.xml b/vector/src/main/res/drawable/ic_bell.xml new file mode 100644 index 0000000000..cb648585ce --- /dev/null +++ b/vector/src/main/res/drawable/ic_bell.xml @@ -0,0 +1,14 @@ + + + diff --git a/vector/src/main/res/drawable/ic_flair.xml b/vector/src/main/res/drawable/ic_flair.xml new file mode 100644 index 0000000000..8d30334432 --- /dev/null +++ b/vector/src/main/res/drawable/ic_flair.xml @@ -0,0 +1,22 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_lock.xml b/vector/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000000..2929f41225 --- /dev/null +++ b/vector/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,22 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_settings_general.xml b/vector/src/main/res/drawable/ic_settings_general.xml new file mode 100644 index 0000000000..97b060f644 --- /dev/null +++ b/vector/src/main/res/drawable/ic_settings_general.xml @@ -0,0 +1,22 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_settings_lab.xml b/vector/src/main/res/drawable/ic_settings_lab.xml new file mode 100644 index 0000000000..d1f5aca7ff --- /dev/null +++ b/vector/src/main/res/drawable/ic_settings_lab.xml @@ -0,0 +1,14 @@ + + + diff --git a/vector/src/main/res/drawable/ic_settings_x.xml b/vector/src/main/res/drawable/ic_settings_x.xml index 994da00c19..08af7d6539 100644 --- a/vector/src/main/res/drawable/ic_settings_x.xml +++ b/vector/src/main/res/drawable/ic_settings_x.xml @@ -9,7 +9,7 @@ android:strokeWidth="1.2" android:fillColor="#00000000" android:fillType="evenOdd" - android:strokeColor="#FFF" + android:strokeColor="#454545" android:strokeLineCap="round"/> diff --git a/vector/src/main/res/drawable/ic_sliders.xml b/vector/src/main/res/drawable/ic_sliders.xml new file mode 100644 index 0000000000..55ccfcfb3d --- /dev/null +++ b/vector/src/main/res/drawable/ic_sliders.xml @@ -0,0 +1,14 @@ + + + diff --git a/vector/src/main/res/layout/fragment_home_drawer.xml b/vector/src/main/res/layout/fragment_home_drawer.xml index fd60368be1..f12c5f8536 100644 --- a/vector/src/main/res/layout/fragment_home_drawer.xml +++ b/vector/src/main/res/layout/fragment_home_drawer.xml @@ -74,6 +74,7 @@ android:background="?attr/selectableItemBackground" android:padding="16dp" android:src="@drawable/ic_settings_x" + android:tint="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/vector/src/main/res/layout/fragment_settings_pushgateways.xml b/vector/src/main/res/layout/fragment_settings_pushgateways.xml new file mode 100644 index 0000000000..7e5c013776 --- /dev/null +++ b/vector/src/main/res/layout/fragment_settings_pushgateways.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml index 60789cd651..39c654e989 100755 --- a/vector/src/main/res/values/config.xml +++ b/vector/src/main/res/values/config.xml @@ -20,9 +20,12 @@ https://scalar.vector.im/api - - - + + https://matrix.org/_matrix/push/v1/notify + im.vector.app.android matrix.org diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index af56eb605a..7583463fe0 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -11,4 +11,12 @@ You are already viewing this room! Quick Reactions + + + General + Preferences + Security & Privacy + Expert + Push Gateways + \ No newline at end of file diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml index 986ed33d5d..76cd365e99 100644 --- a/vector/src/main/res/values/theme_dark.xml +++ b/vector/src/main/res/values/theme_dark.xml @@ -28,11 +28,14 @@ @color/riotx_fab_label_bg_dark @color/riotx_fab_label_color_dark @color/riotx_touch_guard_bg_dark + @color/riotx_keys_backup_banner_accent_color_dark @drawable/highlighted_message_background_dark + @color/riotx_header_panel_border_mobile_dark + @color/riotx_accent @color/primary_color_dark_light diff --git a/vector/src/main/res/values/theme_light.xml b/vector/src/main/res/values/theme_light.xml index ad487d1d61..f7f9a26051 100644 --- a/vector/src/main/res/values/theme_light.xml +++ b/vector/src/main/res/values/theme_light.xml @@ -33,6 +33,8 @@ @drawable/highlighted_message_background_light + @color/riotx_header_panel_border_mobile_light + @color/riotx_accent diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml new file mode 100644 index 0000000000..0c8900685a --- /dev/null +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_preferences_root.xml b/vector/src/main/res/xml/vector_settings_preferences_root.xml new file mode 100644 index 0000000000..cb3b5840b0 --- /dev/null +++ b/vector/src/main/res/xml/vector_settings_preferences_root.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 2e417a9143f9e7bd61f6909e15226c54a245c427 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 20 Jun 2019 15:22:40 +0200 Subject: [PATCH 02/26] Basic FCM vs fdroid mode --- docs/notifications.md | 281 +++++++++ .../im/vector/matrix/android/api/Matrix.kt | 4 +- .../matrix/android/api/session/Session.kt | 46 +- .../api/session/room/read/ReadService.kt | 1 + .../internal/session/DefaultSession.kt | 64 +- .../internal/session/room/RoomFactory.kt | 6 +- .../internal/session/room/RoomModule.kt | 2 +- .../session/room/read/DefaultReadService.kt | 24 +- .../room/timeline/DefaultTimelineService.kt | 5 + .../internal/session/sync/job/SyncService.kt | 25 +- .../internal/session/sync/job/SyncThread.kt | 69 +-- .../internal/session/sync/job/SyncWorker.kt | 68 +- vector/src/fdroid/AndroidManifest.xml | 12 +- .../push/fcm/{FcmHelper.java => FcmHelper.kt} | 23 +- ...> OnApplicationUpgradeOrRebootReceiver.kt} | 24 +- .../riotredesign/push/fcm/FcmHelper.java | 113 ---- .../vector/riotredesign/push/fcm/FcmHelper.kt | 103 ++++ .../fcm/VectorFirebaseMessagingService.kt | 13 +- vector/src/main/AndroidManifest.xml | 6 - .../vector/riotredesign/VectorApplication.kt | 143 +---- .../vector/riotredesign/core/di/AppModule.kt | 9 +- .../services/AlarmSyncBroadcastReceiver.kt | 74 +++ .../core/services/EventStreamServiceX.kt | 582 ------------------ .../services/HttpLongPoolingSyncService.kt | 102 --- .../core/services/PushSimulatorWorker.kt | 36 -- .../core/services/RestartBroadcastReceiver.kt | 37 -- .../features/login/LoginActivity.kt | 4 +- .../NotificationDrawerManager.kt | 10 +- .../notifications/OutdatedEventDetector.kt | 18 +- .../notifications/PushRuleTriggerListener.kt | 4 +- 30 files changed, 663 insertions(+), 1245 deletions(-) create mode 100644 docs/notifications.md rename vector/src/fdroid/java/im/vector/riotredesign/push/fcm/{FcmHelper.java => FcmHelper.kt} (68%) rename vector/src/fdroid/java/im/vector/riotredesign/receiver/{OnApplicationUpgradeReceiver.java => OnApplicationUpgradeOrRebootReceiver.kt} (53%) delete mode 100755 vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java create mode 100755 vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/AlarmSyncBroadcastReceiver.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/EventStreamServiceX.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/PushSimulatorWorker.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt diff --git a/docs/notifications.md b/docs/notifications.md new file mode 100644 index 0000000000..404f9aac94 --- /dev/null +++ b/docs/notifications.md @@ -0,0 +1,281 @@ +This document aims to describe how Riot X android displays notifications to the end user. It also clarifies notifications and background settings in the app. + +# Table of Contents +1. [Prerequisites Knowledge](#prerequisites-knowledge) + * [How does a matrix client gets a message from a Home Server?](#how-does-a-matrix-client-gets-a-message-from-a-home-server) + * [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification) + * [Push VS Notification](#push-vs-notification) + * [Push in the matrix federated world](#push-in-the-matrix-federated-world) + * [How does the Home Server knows when to notify a client?](#how-does-the-home-server-knows-when-to-notify-a-client) + * [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation) + * [Background processing limitations](#background-processing-limitations) +2. [RiotX Notification implementations](#riotx-notification-implementations) + * [Requirements](#requirements) + * [Foreground sync mode (Gplay & Fdroid)](#foreground-sync-mode-gplay-fdroid) + * [Push (FCM) received in background](#push-fcm-received-in-background) + * [FCM Fallback mode](#fcm-fallback-mode) + * [f-droid background Mode](#f-droid-background-mode) +3. [Application Settings](#application-settings) + + +First let's start with some prerequisite knowledge + +# Prerequisites Knowledge + +## How does a matrix client gets a message from a Home Server? + +In order to get messages from a home server, a matrix client need to perform a ``sync`` operation. + +`To read events, the intended flow of operation is for clients to first call the /sync API without a since parameter. This returns the most recent message events for each room, as well as the state of the room at the start of the returned timeline. ` + +The client need to call the `sync`API periodically in order to get incremental updates of the server state (new messages). +This mechanism is known as **HTTP long pooling**. + +Using the **HTTP Long pooling** mechanism a client polls a server requesting new information. +The server *holds the request open until new data is available*. +Once available, the server responds and sends the new information. +When the client receives the new information, it immediately sends another request, and the operation is repeated. +This effectively emulates a server push feature. + +The HTTP long pooling can be fine tuned in the **SDK** using two parameters: +* timout (Sync request timeout) +* delay (Delay between each sync) + +**timeout** is a server paramter, defined by: +``` +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. +By default, this is 0, so the server will return immediately even if the response is empty. +``` + +**delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync. + +When the Riot X Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0. + +## How does a mobile app receives push notification + +Push notification is used as a way to wake up a mobile application when some important information is available and should be processed. + +Typically in order to get push notification, an application relies on a **Push Notification Service** or **Push Provider**. + +For example iOS uses APNS (Apple Push Notification Service). +Most of android devices relies on Google's Firebase Cloud Messaging (FCM). + > FCM has replaced Google Cloud Messaging (GCM - deprecated April 10 2018) + +FCM will only work on android devices that have Google plays services installed +(In simple terms, Google Play Services is a background service that runs on Android, which in turn helps in integrating Google’s advanced functionalities to other applications) + +De-Googlified devices need to rely on something else in order to stay up to date with a server. +There some cases when devices with google services cannot use FCM (network infrastructure limitations -firewalls- , + privacy and or independency requirement, source code licence) + +## Push VS Notification + +This need some disambiguation, because it is the source of common confusion: + + +*The fact that you see a notification on your screen does not mean that you have successfully configured your PUSH plateform.* + + Technically there is a difference between a push and a notification. A notification is what you see on screen and/or in the notification Menu/Drawer (in the top bar of the phone). + + Notifications are not always triggered by a push (One can display a notification locally triggered by an alarm) + + +## Push in the matrix federated world + +In order to send a push to a mobile, App developers need to have a server that will use the FCM APIs, and these APIs requires authentication! +This server is called a **Push Gateway** in the matrix world + +That means that Riot X Android, a matrix client created by New Vector, is using a **Push Gateway** with the needed credentials (FCM API secret Key) in order to send push to the New Vector client. + +If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app. + +On registration, a matrix client must tell to it's Home Server what Push Gateway to use. + +See [Sygnal](https://github.com/matrix-org/sygnal/) for a reference implementation. +``` + + +--------------------+ +-------------------+ + Matrix HTTP | | | | + Notification Protocol | App Developer | | Device Vendor | + | | | | + +-------------------+ | +----------------+ | | +---------------+ | + | | | | | | | | | | + | Matrix homeserver +-----> Push Gateway +------> Push Provider | | + | | | | | | | | | | + +-^-----------------+ | +----------------+ | | +----+----------+ | + | | | | | | + Matrix | | | | | | +Client/Server API + | | | | | + | | +--------------------+ +-------------------+ + | +--+-+ | + | | <-------------------------------------------+ + +---+ | + | | Provider Push Protocol + +----+ + + Mobile Device or Client +``` + +Recommended reading: + * https://thomask.sdf.org/blog/2016/12/11/riots-magical-push-notifications-in-ios.html +* https://matrix.org/docs/spec/client_server/r0.4.0.html#id128 + + +## How does the Home Server knows when to notify a client? + +This is defined by [**push rules**](https://matrix.org/docs/spec/client_server/r0.4.0.html#push-rules-). + +`A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented (sound / importance).` + +A Home Server can be configured with default rules (for Direct messages, group messages, mentions, etc.. ). + +There are different kind of push rules, it can be per room (each new message on this room should be notified), it can also define a pattern that a message should match (when you are mentioned, or key word based). + +Notifications have 2 'levels' (`highlighted = true/false`). In RiotX these notifications level are reflected as Noisy/Silent. + +**What about encrypted messages?** + +Of course, content patterns matching cannot be used for encrypted messages server side (as the content is encrypted). + +That is why clients are able to **process the push rules client side** to decide what kind of notification should be presented for a given event. + +## Push vs privacy, and mitigation + +As seen previously, App developers don't directly send a push to the end user's device, they use a Push Provider as intermediary. So technically this intermediary is able to read the content of what is sent. + +App developers usually mitigate this by sending a `silent notification`, that is a notification with no identifiable data, or with an encrypted payload. When the push is received the app can then synchronise to it's server in order to generate a local notification. + + +## Background processing limitations + +A mobile applications process live in a managed word, meaning that its process can be limited (e.g no network access), stopped or killed at almost anytime by the Operating System. + +In order to improve the battery life of their devices some constructors started to implement mechanism to drastically limit background execution of applications (e.g MIUI/Xiaomi restrictions, Sony stamina mode). +Then starting android M, android has also put more focus on improving device performances, introducing several IDLE modes, App-Standby, Light Doze, Doze. + +In a nutshell, apps can't do much in background now. + +If the devices is not plugged and stays IDLE for a certain amount of time, radio (mobile connectivity) and CPU can/will be turned off. + +For an application like riot X, where users can receive important information at anytime, the best option is to rely on a push system (Google's Firebase Message a.k.a FCM). FCM high priority push can wake up the device and whitelist an application to perform background task (for a limited but unspecified amount of time). + +Notice that this is still evolving, and in future versions application that has been 'background restricted' by users won't be able to wake up even when a high priority push is received. Also high priority notifications could be rate limited (not defined anywhere) + +It's getting a lot more complicated when you cannot rely on FCM (because: closed sources, network/firewall restrictions, privacy concerns). +The documentation on this subject is vague, and as per our experiments not always exact, also device's behaviour is fragmented. + +It is getting more and more complex to have reliable notifications when FCM is not used. + +# RiotX Notification implementations + +## Requirements + +RiotX Android must work with and without FCM. +* The riotX android app published on fdroid do not rely on FCM (all related dependencies are not present) +* The RiotX android app published on google play rely on FCM, with a fallback mode when FCM registration has failed (e.g outdated or missing Google Play Services) + +## Foreground sync mode (Gplay & Fdroid) + +When in foreground, riotX performs sync continuously with a timeout value set to 10 seconds (see HttpPooling). + +As this mode does not need to live beyond the scope of the application, and as per Google recommendation, riotX uses the internal app resources (Thread and Timers) to perform the syncs. + +This mode is turned on when the app enters foreground, and off when enters background. + +In background, and depending on wether push is available or not, riotX will use different methods to perform the syncs (Workers / Alarms / Service) + +## Push (FCM) received in background + +In order to enable Push, riotX must first get a push token from the firebase SDK, then register a pusher with this token on the HomeServer. + +When a message should be notified to a user, the user's homeserver notifies the registered `push gateway` for riotX, that is [sygnal](https://github.com/matrix-org/sygnal) _- The reference implementation for push gateways -_ hosted by matrix.org. + +This sygnal instance is configured with the required FCM API authentication token, and will then use the FCM API in order to notify the user's device running riotX. + +``` +Homeserver ----> Sygnal (configured for riotX) ----> FCM ----> RiotX +``` + +The push gateway is configured to only send `(eventId,roomId)` in the push payload (for better [privacy](#push-vs-privacy-and-mitigation)). + +RiotX needs then to synchronise with the user's HomeServer, in order to resolve the event and create a notification. + +As per [Google recommendation](https://android-developers.googleblog.com/2018/09/notifying-your-users-with-fcm.html), riotX will then use the WorkManager API in order to trigger a background sync. + +**Google recommendations:** +> We recommend using FCM messages in combination with the WorkManager 1 or JobScheduler API + +> Avoid background services. One common pitfall is using a background service to fetch data in the FCM message handler, since background service will be stopped by the system per recent changes to Google Play Policy + +``` +Homeserver ----> Sygnal ----> FCM ----> RiotX + (Sync) ----> Homeserver + <---- + Display notification +``` + +**Possible outcomes** + +Upon reception of the FCM push, RiotX will perform a sync call to the Home Server, during this process it is possible that: + * Happy path, the sync is performed, the message resolved and displayed in the notification drawer + * The notified message is not in the sync. Can happen if a lot of things did happen since the push (`gappy sync`) + * The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally) + * The sync takes too long and the process is killed before completion, or network is not reliable and the sync fails. + +Riot X implements several strategies in these cases (TODO document) + +## FCM Fallback mode + +It is possible that riotX is not able to get a FCM push token. +Common errors (amoung several others) that can cause that: +* Google Play Services is outdated +* Google Play Service fails in someways with FCM servers (infamous `SERVICE_NOT_AVAILABLE`) + +If riotX is able to detect one of this cases, it will notifies it to the users and when possible help him fix it via a dedicated troubleshoot screen. + +Meanwhile, in order to offer a minimal service, and as per Google's recommendation for background activities, riotX will launch periodic background sync in order to stays in sync with servers. + +The fallback mode is impacted by all the battery life saving mechanism implemented by android. Meaning that if the app is not used for a certain amount of time (`App-Standby`), or the device stays still and unplugged (`Light Doze`) , the sync will become less frequent. + +And if the device stays unplugged and still for too long (`Doze Mode`), no background sync will be perform at all (the system's `Ignore Battery Optimization option` has no effect on that). + + Also the time interval between sync is elastic, controlled by the system to group other apps background sync request and start radio/cpu only once for all. + +Usually in this mode, what happen is when you take back your phone in your hand, you suddenly receive notifications. + +The fallback mode is supposed to be a temporary state waiting for the user to fix issues for FCM, or for App Developers that has done a fork to correctly configure their FCM settings. + +## f-droid background Mode + +The f-droid riotX flavor has no dependencies to FCM, therefore cannot relies on Push. + +Also Google's recommended background processing method cannot be applied. This is because all of these methods are affected by IDLE modes, and will result on the user not being notified at all when the app is in a Doze mode (only in maintenance windows that could happens only after hours). + +Only solution left is to use `AlarmManager`, that offers new API to allow launching some process even if the App is in IDLE modes. + +Notice that these alarms, due to their potential impact on battery life, can still be restricted by the system. Documentation says that they will not be triggered more than every minutes under normal system operation, and when in low power mode about every 15 mn. + +These restrictions can be relaxed by requirering the app to be white listed from battery optimization. + +F-droid version will schedule alarms that will then trigger a Broadcast Receiver, that in turn will launch a Service (in the classic android way), and the reschedule an alarm for next time. + +Depending on the system status (or device make), it is still possible that the app is not given enough time to launch the service, or that the radio is still turned off thus preventing the sync to success (that's why Alarms are not recommended for network related tasks). + +That is why on riotX Fdroid, the broadcast receiver will acquire a temporary WAKE_LOCK for several seconds (thus securing cpu/network), and launch the service in foreground. The service performs the sync. + +Note that foreground services require to put a notification informing the user that the app is doing something even if not launched). + + + +# Application Settings + +**Notifications > Enable notifications for this account** + +Configure Sygnal to send or not notifications to all user devices. + +**Notifications > Enable notifications for this device** + +Disable notifications locally. The push server will continue to send notifications to the device but this one will ignore them. + + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 87f0621303..d6665f7fa6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -61,9 +61,7 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent { currentSession = it it.open() it.setFilter(FilterService.FilterPreset.RiotFilter) - //TODO check if using push or not (should pause if we use push) -// it.shoudPauseOnBackground(false) -// it.startSync() + it.startSync() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 1ad26c2daf..ac41267870 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -60,35 +60,31 @@ interface Session : @MainThread fun open() -// /** -// * This method start the sync thread. -// */ -// @MainThread -// fun startSync() -// -// -// fun isSyncThreadAlice() : Boolean -// fun syncThreadState() : String -// -//// fun pauseSync() -//// fun resumeSync() -// -// fun shoudPauseOnBackground(shouldPause: Boolean) + /** + * Requires a one time background sync + */ + fun requireBackgroundSync() /** - * 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. + * Launches infinite periodic background syncs + * THis does not work in doze mode :/ + * If battery optimization is on it can work in app standby but that's all :/ */ -// fun configureSyncLongPooling(timoutMS : Long, delayMs : Long ) + fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L) -// /** -// * This method stop the sync thread. -// */ -// @MainThread -// fun stopSync() + fun stopAnyBackgroundSync() + + /** + * This method start the sync thread. + */ + @MainThread + fun startSync() + + /** + * This method stop the sync thread. + */ + @MainThread + fun stopSync() /** * This method allows to listen the sync state. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt index ed67c1db4a..ab406b8e54 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt @@ -38,4 +38,5 @@ interface ReadService { */ fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) + fun isEventRead(eventId: String): Boolean } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 20dc4cf79b..cef7e27581 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -73,6 +73,7 @@ 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 import im.vector.matrix.android.internal.session.sync.job.SyncThread +import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.session.user.UserModule import org.koin.core.scope.Scope import org.koin.standalone.inject @@ -136,45 +137,34 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi bingRuleWatcher.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 -// } + override fun requireBackgroundSync() { + SyncWorker.requireBackgroundSync() + } -// override fun resumeSync() { -// assert(isOpen) -// syncThread.restart() -// } -// -// override fun pauseSync() { -// assert(isOpen) -// syncThread.pause() -// } + override fun startAutomaticBackgroundSync(repeatDelay: Long) { + SyncWorker.automaticallyBackgroundSync(0, repeatDelay) + } -// override fun configureSyncLongPooling(timoutMS: Long, delayMs: Long) { -// syncThread.configureLongPoolingSettings(timoutMS, delayMs) -// } -// -// @MainThread -// override fun stopSync() { -// assert(isOpen) -// syncThread.kill() -// } + override fun stopAnyBackgroundSync() { + SyncWorker.stopAnyBackgroundSync() + } + + @MainThread + override fun startSync() { + assert(isOpen) + if (!syncThread.isAlive) { + syncThread.start() + } else { + syncThread.restart() + Timber.w("Attempt to start an already started thread") + } + } + + @MainThread + override fun stopSync() { + assert(isOpen) + syncThread.kill() + } @MainThread override fun close() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index ae1c30003b..42f2299ed7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService @@ -51,7 +52,8 @@ internal class RoomFactory(private val monarchy: Monarchy, private val cryptoService: CryptoService, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val joinRoomTask: JoinRoomTask, - private val leaveRoomTask: LeaveRoomTask) { + private val leaveRoomTask: LeaveRoomTask, + private val sessionParams: SessionParams) { fun instantiate(roomId: String): Room { val roomMemberExtractor = SenderRoomMemberExtractor(roomId) @@ -61,7 +63,7 @@ internal class RoomFactory(private val monarchy: Monarchy, val sendService = DefaultSendService(roomId, eventFactory, cryptoService, monarchy) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) - val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask) + val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, sessionParams) val reactionService = DefaultRelationService(roomId, eventFactory, findReactionEventForUndoTask, monarchy, taskExecutor) return DefaultRoom( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index ead2c8e429..1cefe088a9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -80,7 +80,7 @@ class RoomModule { } scope(DefaultSession.SCOPE) { - RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) + RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index 30bbe30c28..46e0848203 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -18,9 +18,15 @@ package im.vector.matrix.android.internal.session.room.read 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.room.read.ReadService +import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.ReadReceiptEntity +import im.vector.matrix.android.internal.database.query.find +import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.latestEvent +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.fetchCopied @@ -28,7 +34,8 @@ import im.vector.matrix.android.internal.util.fetchCopied internal class DefaultReadService(private val roomId: String, private val monarchy: Monarchy, private val taskExecutor: TaskExecutor, - private val setReadMarkersTask: SetReadMarkersTask) : ReadService { + private val setReadMarkersTask: SetReadMarkersTask, + private val sessionParams: SessionParams) : ReadService { override fun markAllAsRead(callback: MatrixCallback) { val latestEvent = getLatestEvent() @@ -50,5 +57,20 @@ internal class DefaultReadService(private val roomId: String, return monarchy.fetchCopied { EventEntity.latestEvent(it, roomId) } } + override fun isEventRead(eventId: String): Boolean { + var isEventRead = false + monarchy.doWithRealm { + val readReceipt = ReadReceiptEntity.where(it, roomId, sessionParams.credentials.userId).findFirst() + ?: return@doWithRealm + val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) + ?: return@doWithRealm + val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex + ?: Int.MIN_VALUE + val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex + ?: Int.MAX_VALUE + isEventRead = eventToCheckIndex <= readReceiptIndex + } + return isEventRead + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index c8629ecce8..828964d837 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -20,7 +20,11 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineService +import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.ReadReceiptEntity +import im.vector.matrix.android.internal.database.query.find +import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.fetchCopyMap @@ -45,4 +49,5 @@ internal class DefaultTimelineService(private val roomId: String, }) } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 300ae4c5cd..504c621aaf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -72,7 +72,7 @@ open class SyncService : Service(), MatrixKoinComponent { if (cancelableTask == null) { timer.cancel() timer = Timer() - doSync() + doSync(true) } else { //Already syncing ignore Timber.i("Received a start while was already syncking... ignore") @@ -101,7 +101,7 @@ open class SyncService : Service(), MatrixKoinComponent { stopSelf() } - fun doSync() { + fun doSync(once: Boolean = false) { var nextBatch = syncTokenStore.getLastToken() if (!networkConnectivityChecker.isConnected()) { Timber.v("Sync is Paused. Waiting...") @@ -110,7 +110,7 @@ open class SyncService : Service(), MatrixKoinComponent { override fun run() { doSync() } - }, 10_000L) + }, 5_000L) } else { Timber.v("Execute sync request with token $nextBatch and timeout $timeout") val params = SyncTask.Params(nextBatch, timeout) @@ -123,11 +123,16 @@ open class SyncService : Service(), MatrixKoinComponent { nextBatch = data.nextBatch syncTokenStore.saveToken(nextBatch) localBinder.notifySyncFinish() - timer.schedule(object : TimerTask() { - override fun run() { - doSync() - } - }, nextBatchDelay) + if (!once) { + timer.schedule(object : TimerTask() { + override fun run() { + doSync() + } + }, nextBatchDelay) + } else { + //stop + stopMe() + } } override fun onFailure(failure: Throwable) { @@ -141,7 +146,7 @@ open class SyncService : Service(), MatrixKoinComponent { override fun run() { doSync() } - }, 10_000L) + }, 5_000L) } if (failure !is Failure.NetworkConnection @@ -151,7 +156,7 @@ open class SyncService : Service(), MatrixKoinComponent { override fun run() { doSync() } - }, 10_000L) + }, 5_000L) } if (failure is Failure.ServerError diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 6d644ca547..68d01d344d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -40,11 +40,6 @@ 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, private val syncTokenStore: SyncTokenStore, @@ -62,27 +57,6 @@ 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...") @@ -93,30 +67,6 @@ 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...") @@ -148,9 +98,9 @@ internal class SyncThread(private val syncTask: SyncTask, lock.wait() } } else { - Timber.v("Execute sync request with token $nextBatch and timeout $currentLongPoolTimeoutMs") + Timber.v("Execute sync request with token $nextBatch and timeout $DEFAULT_LONG_POOL_TIMEOUT") val latch = CountDownLatch(1) - val params = SyncTask.Params(nextBatch, currentLongPoolTimeoutMs) + val params = SyncTask.Params(nextBatch, DEFAULT_LONG_POOL_TIMEOUT) cancelableTask = syncTask.configureWith(params) .callbackOn(TaskThread.CALLER) .executeOn(TaskThread.CALLER) @@ -193,8 +143,8 @@ internal class SyncThread(private val syncTask: SyncTask, updateStateTo(SyncState.RUNNING(catchingUp = false)) } - Timber.v("Waiting for $currentLongPoolDelayMs delay before new pool...") - if (currentLongPoolDelayMs > 0) sleep(currentLongPoolDelayMs) + Timber.v("Waiting for $DEFAULT_LONG_POOL_DELAY delay before new pool...") + if (DEFAULT_LONG_POOL_DELAY > 0) sleep(DEFAULT_LONG_POOL_DELAY) Timber.v("...Continue") } } @@ -216,20 +166,11 @@ internal class SyncThread(private val syncTask: SyncTask, } override fun onMoveToForeground() { - currentLongPoolTimeoutMs = longPoolTimeoutMs - currentLongPoolDelayMs = longPoolDelayMs restart() } override fun onMoveToBackground() { - if (shouldPauseOnBackground) { - pause() - } else { - Timber.v("Slower sync in background mode") - //we continue but with a slower pace - currentLongPoolTimeoutMs = backgroundedLongPoolTimeoutMs - currentLongPoolDelayMs = backgroundedLongPoolDelayMs - } + pause() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt index 441f7ef770..dd5b91f688 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -17,8 +17,6 @@ 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 @@ -33,19 +31,19 @@ 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, +internal class SyncWorker(context: Context, workerParameters: WorkerParameters ) : CoroutineWorker(context, workerParameters), MatrixKoinComponent { @JsonClass(generateAdapter = true) internal data class Params( - val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT + val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT, + val automaticallyRetry: Boolean = false ) private val syncAPI by inject() @@ -54,7 +52,6 @@ class SyncWorker(context: Context, private val sessionParamsStore by inject() private val syncTokenStore by inject() - val autoMode = false override suspend fun doWork(): Result { Timber.i("Sync work starting") @@ -69,51 +66,56 @@ class SyncWorker(context: Context, return executeRequest { 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() + if (it is Failure.ServerError + && it.error.code == MatrixError.UNKNOWN_TOKEN) { + sessionParamsStore.delete() + Result.failure() } else { - Result.Success() + Timber.i("Sync work failed $it") + Result.retry() } }, { Timber.i("Sync work success next batch ${it.nextBatch}") - syncResponseHandler.handleResponse(it, token, false) - syncTokenStore.saveToken(it.nextBatch) - again() - Result.success() + if (!isStopped) { + syncResponseHandler.handleResponse(it, token, false) + syncTokenStore.saveToken(it.nextBatch) + } + if (params.automaticallyRetry) Result.retry() else Result.success() } ) - } - fun again() { - if (autoMode) { - Timber.i("Sync work Again!!") + companion object { + fun requireBackgroundSync(serverTimeout: Long = 0) { + val data = WorkerParamsFactory.toData(Params(serverTimeout, false)) val workRequest = OneTimeWorkRequestBuilder() - .setInitialDelay(30_000, TimeUnit.MILLISECONDS) + .setInputData(data) .setConstraints(Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build()) - .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) .build() - WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.APPEND, workRequest) - + WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) } + fun automaticallyBackgroundSync(serverTimeout: Long = 0, delay: Long = 30_000) { + val data = WorkerParamsFactory.toData(Params(serverTimeout, true)) + val workRequest = OneTimeWorkRequestBuilder() + .setInputData(data) + .setConstraints(Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build()) + .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) + .build() + WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) + } + + fun stopAnyBackgroundSync() { + WorkManager.getInstance().cancelUniqueWork("BG_SYNCP") + } } } \ No newline at end of file diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 0cc39d63e8..77179c86e1 100644 --- a/vector/src/fdroid/AndroidManifest.xml +++ b/vector/src/fdroid/AndroidManifest.xml @@ -2,14 +2,24 @@ + + + - + + + + + \ No newline at end of file diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.java b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.kt similarity index 68% rename from vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.java rename to vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.kt index 3efc4990e9..11826a7b35 100755 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.java +++ b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.kt @@ -14,24 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotredesign.push.fcm; +package im.vector.riotredesign.push.fcm -import android.app.Activity; -import android.content.Context; +import android.app.Activity +import android.content.Context -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import im.vector.riotredesign.core.pushers.PushersManager -public class FcmHelper { +object FcmHelper { + + fun isPushSupported(): Boolean = false /** * Retrieves the FCM registration token. * * @return the FCM token or null if not received from FCM */ - @Nullable - public static String getFcmToken(Context context) { - return null; + fun getFcmToken(context: Context): String? { + return null } /** @@ -40,8 +40,7 @@ public class FcmHelper { * @param context android context * @param token the token to store */ - public static void storeFcmToken(@NonNull Context context, - @Nullable String token) { + fun storeFcmToken(context: Context, token: String?) { // No op } @@ -50,7 +49,7 @@ public class FcmHelper { * * @param activity the first launch Activity */ - public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) { + fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) { // No op } } diff --git a/vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeReceiver.java b/vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeOrRebootReceiver.kt similarity index 53% rename from vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeReceiver.java rename to vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeOrRebootReceiver.kt index 4cb092b1a7..a13bc71ada 100644 --- a/vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeReceiver.java +++ b/vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeOrRebootReceiver.kt @@ -1,5 +1,6 @@ /* * Copyright 2018 New Vector Ltd + * 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. @@ -14,21 +15,18 @@ * limitations under the License. */ -package im.vector.riotredesign.receiver; +package im.vector.riotredesign.receiver -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver +import timber.log.Timber -import timber.log.Timber; +class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { -public class OnApplicationUpgradeReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - Timber.v("## onReceive() : Application has been upgraded, restart event stream service."); - - // Start Event stream - // TODO EventStreamServiceX.Companion.onApplicationUpgrade(context); + override fun onReceive(context: Context, intent: Intent) { + Timber.v("## onReceive() ${intent.action}") + AlarmSyncBroadcastReceiver.scheduleAlarm(context, 10) } } diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java deleted file mode 100755 index 17bdc909aa..0000000000 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2014 OpenMarket Ltd - * Copyright 2017 Vector Creations Ltd - * Copyright 2018 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.push.fcm; - -import android.app.Activity; -import android.content.Context; -import android.preference.PreferenceManager; -import android.text.TextUtils; -import android.widget.Toast; - -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; - -/** - * This class store the FCM token in SharedPrefs and ensure this token is retrieved. - * It has an alter ego in the fdroid variant. - */ -public class FcmHelper { - private static final String LOG_TAG = FcmHelper.class.getSimpleName(); - - private static final String PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"; - - /** - * Retrieves the FCM registration token. - * - * @return the FCM token or null if not received from FCM - */ - @Nullable - public static String getFcmToken(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getString(PREFS_KEY_FCM_TOKEN, null); - } - - /** - * Store FCM token to the SharedPrefs - * - * @param context android context - * @param token the token to store - */ - public static void storeFcmToken(@NonNull Context context, - @Nullable String token) { - PreferenceManager.getDefaultSharedPreferences(context) - .edit() - .putString(PREFS_KEY_FCM_TOKEN, token) - .apply(); - - } - - /** - * onNewToken may not be called on application upgrade, so ensure my shared pref is set - * - * @param activity the first launch Activity - */ - public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) { -// if (TextUtils.isEmpty(getFcmToken(activity))) { - - - //vfe: according to firebase doc - //'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' - if (checkPlayServices(activity)) { - try { - FirebaseInstanceId.getInstance().getInstanceId() - .addOnSuccessListener(activity, instanceIdResult -> { - storeFcmToken(activity, instanceIdResult.getToken()); - pushersManager.registerPusherWithFcmKey(instanceIdResult.getToken()); - }) - .addOnFailureListener(activity, e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage())); - } catch (Throwable e) { - Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage()); - } - } else { - 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."); - } -// } - } - - /** - * Check the device to make sure it has the Google Play Services APK. If - * it doesn't, display a dialog that allows users to download the APK from - * the Google Play Store or enable it in the device's system settings. - */ - private static boolean checkPlayServices(Activity activity) { - GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); - int resultCode = apiAvailability.isGooglePlayServicesAvailable(activity); - if (resultCode != ConnectionResult.SUCCESS) { - return false; - } - return true; - } -} diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt new file mode 100755 index 0000000000..f6d93d78e3 --- /dev/null +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2014 OpenMarket Ltd + * Copyright 2017 Vector Creations Ltd + * Copyright 2018 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.push.fcm + +import android.app.Activity +import android.content.Context +import android.preference.PreferenceManager +import android.widget.Toast +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 + +/** + * This class store the FCM token in SharedPrefs and ensure this token is retrieved. + * It has an alter ego in the fdroid variant. + */ +object FcmHelper { + private val LOG_TAG = FcmHelper::class.java.simpleName + + private val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" + + + fun isPushSupported(): Boolean = true + + /** + * Retrieves the FCM registration token. + * + * @return the FCM token or null if not received from FCM + */ + fun getFcmToken(context: Context): String? { + return PreferenceManager.getDefaultSharedPreferences(context).getString(PREFS_KEY_FCM_TOKEN, null) + } + + /** + * Store FCM token to the SharedPrefs + * + * @param context android context + * @param token the token to store + */ + fun storeFcmToken(context: Context, + token: String?) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putString(PREFS_KEY_FCM_TOKEN, token) + .apply() + + } + + /** + * onNewToken may not be called on application upgrade, so ensure my shared pref is set + * + * @param activity the first launch Activity + */ + fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) { + // if (TextUtils.isEmpty(getFcmToken(activity))) { + //'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' + if (checkPlayServices(activity)) { + try { + FirebaseInstanceId.getInstance().instanceId + .addOnSuccessListener(activity) { instanceIdResult -> + storeFcmToken(activity, instanceIdResult.token) + pushersManager.registerPusherWithFcmKey(instanceIdResult.token) + } + .addOnFailureListener(activity) { e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.message) } + } catch (e: Throwable) { + Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.message) + } + + } else { + 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.") + } + } + + /** + * Check the device to make sure it has the Google Play Services APK. If + * it doesn't, display a dialog that allows users to download the APK from + * the Google Play Store or enable it in the device's system settings. + */ + private fun checkPlayServices(activity: Activity): Boolean { + val apiAvailability = GoogleApiAvailability.getInstance() + val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity) + return resultCode == ConnectionResult.SUCCESS + } +} diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt index ba942124d1..7ef1973db3 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt @@ -144,14 +144,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { Timber.i("Ignoring push, event already knwown") } else { Timber.v("Requesting background sync") - val workRequest = OneTimeWorkRequestBuilder() - .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) + session.requireBackgroundSync(0L) } } @@ -214,7 +207,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { isPushGatewayEvent = true ) notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent) - notificationDrawerManager.refreshNotificationDrawer(null) + notificationDrawerManager.refreshNotificationDrawer() return } else { @@ -249,7 +242,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { notifiableEvent.isPushGatewayEvent = true notifiableEvent.matrixID = session.sessionParams.credentials.userId notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) - notificationDrawerManager.refreshNotificationDrawer(null) + notificationDrawerManager.refreshNotificationDrawer() } } } diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 18e7eb7727..722460a174 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -15,12 +15,6 @@ android:supportsRtl="true" android:theme="@style/AppTheme.Light" tools:replace="android:allowBackup"> - - - - // intent.action = "NORMAL" -// try { -// startService(intent) -// } catch (e: Throwable) { -// Timber.e("Failed to launch sync service") -// } - bindService(intent, connection, Context.BIND_AUTO_CREATE) - + AlarmSyncBroadcastReceiver.cancelAlarm(appContext) + Matrix.getInstance().currentSession?.also { + it.stopAnyBackgroundSync() } } - 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) - } + if (FcmHelper.isPushSupported()) { + //TODO FCM fallback } 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(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() -// .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); - + //TODO check if notifications are enabled for this device + //We need to use alarm in this mode + AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext,4_000L) Timber.i("Alarm scheduled to restart service") + } } @@ -273,36 +186,4 @@ class VectorApplication : Application(), SyncService.SyncListener { 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() -// } - - } - } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 926a5bc718..f26d2c3eb1 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -37,6 +37,7 @@ 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.OutdatedEventDetector import im.vector.riotredesign.features.notifications.PushRuleTriggerListener import org.koin.dsl.module.module @@ -85,11 +86,15 @@ class AppModule(private val context: Context) { } single { - PushRuleTriggerListener(get(),get()) + PushRuleTriggerListener(get(), get()) } single { - NotificationDrawerManager(context) + OutdatedEventDetector(context) + } + + single { + NotificationDrawerManager(context, get()) } single { diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/AlarmSyncBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/core/services/AlarmSyncBroadcastReceiver.kt new file mode 100644 index 0000000000..990bf15cde --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/services/AlarmSyncBroadcastReceiver.kt @@ -0,0 +1,74 @@ +package im.vector.riotredesign.core.services + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Build.VERSION.SDK_INT +import android.os.PowerManager +import androidx.core.content.ContextCompat +import timber.log.Timber + +class AlarmSyncBroadcastReceiver : BroadcastReceiver() { + + + override fun onReceive(context: Context, intent: Intent) { + + //Aquire a lock to give enough time for the sync :/ + (context.getSystemService(Context.POWER_SERVICE) as PowerManager).run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply { + acquire((10_000).toLong()) + } + } + + + // 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) + } + } + + scheduleAlarm(context,30_000L) + + Timber.i("Alarm scheduled to restart service") + } + + companion object { + const val REQUEST_CODE = 0 + + fun scheduleAlarm(context: Context, delay: Long) { + //Reschedule + val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) + val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE, + intent, PendingIntent.FLAG_UPDATE_CURRENT) + val firstMillis = System.currentTimeMillis() + delay + val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + if (SDK_INT >= Build.VERSION_CODES.M) { + alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pIntent) + } else { + alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pIntent) + } + } + + fun cancelAlarm(context: Context) { + val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) + val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE, + intent, PendingIntent.FLAG_UPDATE_CURRENT) + val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmMgr.cancel(pIntent) + } + } +} diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/EventStreamServiceX.kt b/vector/src/main/java/im/vector/riotredesign/core/services/EventStreamServiceX.kt deleted file mode 100644 index 108b27d5de..0000000000 --- a/vector/src/main/java/im/vector/riotredesign/core/services/EventStreamServiceX.kt +++ /dev/null @@ -1,582 +0,0 @@ -/* - * 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.content.Context -import android.content.Intent -import androidx.core.content.ContextCompat -import androidx.work.Constraints -import androidx.work.NetworkType -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.riotredesign.R -import im.vector.riotredesign.features.notifications.NotifiableEventResolver -import im.vector.riotredesign.features.notifications.NotificationUtils -import org.koin.android.ext.android.inject -import timber.log.Timber -import java.util.concurrent.TimeUnit - -/** - * A service in charge of controlling whether the event stream is running or not. - * - * It manages messages notifications displayed to the end user. - */ -class EventStreamServiceX : VectorService() { - - /** - * Managed session (no multi session for Riot) - */ - private val mSession by inject() - - /** - * Set to true to simulate a push immediately when service is destroyed - */ - private var mSimulatePushImmediate = false - - /** - * The current state. - */ - private var serviceState = ServiceState.INIT - set(newServiceState) { - Timber.i("setServiceState from $field to $newServiceState") - field = newServiceState - } - - /** - * Push manager - */ - // TODO private var mPushManager: PushManager? = null - - private var mNotifiableEventResolver: NotifiableEventResolver? = null - - /** - * Live events listener - */ - /* TODO - private val mEventsListener = object : MXEventListener() { - override fun onBingEvent(event: Event, roomState: RoomState, bingRule: BingRule) { - if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.i("%%%%%%%% MXEventListener: the event $event") - } - - Timber.i("prepareNotification : " + event.eventId + " in " + roomState.roomId) - val session = Matrix.getMXSession(applicationContext, event.matrixId) - - // invalid session ? - // should never happen. - // But it could be triggered because of multi accounts management. - // The dedicated account is removing but some pushes are still received. - if (null == session || !session.isAlive) { - Timber.i("prepareNotification : don't bing - no session") - return - } - - if (EventType.CALL_INVITE == event.getClearType()) { - handleCallInviteEvent(event) - return - } - - - val notifiableEvent = mNotifiableEventResolver!!.resolveEvent(event, roomState, bingRule, session) - if (notifiableEvent != null) { - VectorApp.getInstance().notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) - } - } - - override fun onLiveEventsChunkProcessed(fromToken: String, toToken: String) { - Timber.i("%%%%%%%% MXEventListener: onLiveEventsChunkProcessed[$fromToken->$toToken]") - - VectorApp.getInstance().notificationDrawerManager.refreshNotificationDrawer(OutdatedEventDetector(this@EventStreamServiceX)) - - // do not suspend the application if there is some active calls - if (ServiceState.CATCHUP == serviceState) { - val hasActiveCalls = session?.mCallsManager?.hasActiveCalls() == true - - // if there are some active calls, the catchup should not be stopped. - // because an user could answer to a call from another device. - // there will no push because it is his own message. - // so, the client has no choice to catchup until the ring is shutdown - if (hasActiveCalls) { - Timber.i("onLiveEventsChunkProcessed : Catchup again because there are active calls") - catchup(false) - } else if (ServiceState.CATCHUP == serviceState) { - Timber.i("onLiveEventsChunkProcessed : no Active call") - CallsManager.getSharedInstance().checkDeadCalls() - stop() - } - } - } - } */ - - /** - * Service internal state - */ - private enum class ServiceState { - // Initial state - INIT, - // Service is started for a Catchup. Once the catchup is finished the service will be stopped - CATCHUP, - // Service is started, and session is monitored - STARTED - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - // Cancel any previous worker - cancelAnySimulatedPushSchedule() - - // no intent : restarted by Android - if (null == intent) { - // Cannot happen anymore - Timber.e("onStartCommand : null intent") - myStopSelf() - return START_NOT_STICKY - } - - val action = intent.action - - Timber.i("onStartCommand with action : $action (current state $serviceState)") - - // Manage foreground notification - when (action) { - ACTION_BOOT_COMPLETE, - ACTION_APPLICATION_UPGRADE, - ACTION_SIMULATED_PUSH_RECEIVED -> { - // Display foreground notification - Timber.i("startForeground") - val notification = NotificationUtils.buildForegroundServiceNotification(this, R.string.notification_sync_in_progress) - startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) - } - ACTION_GO_TO_FOREGROUND -> { - // Stop foreground notification display - Timber.i("stopForeground") - stopForeground(true) - } - } - - if (null == mSession) { - Timber.e("onStartCommand : no sessions") - myStopSelf() - return START_NOT_STICKY - } - - when (action) { - ACTION_START, - ACTION_GO_TO_FOREGROUND -> - when (serviceState) { - ServiceState.INIT -> - start(false) - ServiceState.CATCHUP -> - // A push has been received before, just change state, to avoid stopping the service when catchup is over - serviceState = ServiceState.STARTED - ServiceState.STARTED -> { - // Nothing to do - } - } - ACTION_STOP, - ACTION_GO_TO_BACKGROUND, - ACTION_LOGOUT -> - stop() - ACTION_PUSH_RECEIVED, - ACTION_SIMULATED_PUSH_RECEIVED -> - when (serviceState) { - ServiceState.INIT -> - start(true) - ServiceState.CATCHUP -> - catchup(true) - ServiceState.STARTED -> - // Nothing to do - Unit - } - ACTION_PUSH_UPDATE -> pushStatusUpdate() - ACTION_BOOT_COMPLETE -> { - // No FCM only - mSimulatePushImmediate = true - stop() - } - ACTION_APPLICATION_UPGRADE -> { - // FDroid only - catchup(true) - } - else -> { - // Should not happen - } - } - - // We don't want the service to be restarted automatically by the System - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - - // Schedule worker? - scheduleSimulatedPushIfNeeded() - } - - /** - * Tell the WorkManager to cancel any schedule of push simulation - */ - private fun cancelAnySimulatedPushSchedule() { - WorkManager.getInstance().cancelAllWorkByTag(PUSH_SIMULATOR_REQUEST_TAG) - } - - /** - * Configure the WorkManager to schedule a simulated push, if necessary - */ - private fun scheduleSimulatedPushIfNeeded() { - if (shouldISimulatePush()) { - val delay = if (mSimulatePushImmediate) 0 else 60_000 // TODO mPushManager?.backgroundSyncDelay ?: let { 60_000 } - Timber.i("## service is schedule to restart in $delay millis, if network is connected") - - val pushSimulatorRequest = OneTimeWorkRequestBuilder() - .setInitialDelay(delay.toLong(), TimeUnit.MILLISECONDS) - .setConstraints(Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build()) - .addTag(PUSH_SIMULATOR_REQUEST_TAG) - .build() - - WorkManager.getInstance().let { - // Cancel any previous worker - it.cancelAllWorkByTag(PUSH_SIMULATOR_REQUEST_TAG) - it.enqueue(pushSimulatorRequest) - } - } - } - - /** - * Start the even stream. - * - * @param session the session - */ - private fun startEventStream(session: Session) { - /* TODO - // resume if it was only suspended - if (null != session.currentSyncToken) { - session.resumeEventStream() - } else { - session.startEventStream(store?.eventStreamToken) - } - */ - } - - /** - * Monitor the provided session. - * - * @param session the session - */ - private fun monitorSession(session: Session) { - /* TODO - session.dataHandler.addListener(mEventsListener) - CallsManager.getSharedInstance().addSession(session) - - val store = session.dataHandler.store - - // the store is ready (no data loading in progress...) - if (store!!.isReady) { - startEventStream(session, store) - } else { - // wait that the store is ready before starting the events stream - store.addMXStoreListener(object : MXStoreListener() { - override fun onStoreReady(accountId: String) { - startEventStream(session, store) - - store.removeMXStoreListener(this) - } - - override fun onStoreCorrupted(accountId: String, description: String) { - // start a new initial sync - if (null == store.eventStreamToken) { - startEventStream(session, store) - } else { - // the data are out of sync - Matrix.getInstance(applicationContext)!!.reloadSessions(applicationContext) - } - - store.removeMXStoreListener(this) - } - - override fun onStoreOOM(accountId: String, description: String) { - val uiHandler = Handler(mainLooper) - - uiHandler.post { - Toast.makeText(applicationContext, "$accountId : $description", Toast.LENGTH_LONG).show() - Matrix.getInstance(applicationContext)!!.reloadSessions(applicationContext) - } - } - }) - - store.open() - } - */ - } - - /** - * internal start. - */ - private fun start(forPush: Boolean) { - val applicationContext = applicationContext - // TODO mPushManager = Matrix.getInstance(applicationContext)!!.pushManager - mNotifiableEventResolver = NotifiableEventResolver(applicationContext) - - monitorSession(mSession!!) - - serviceState = if (forPush) { - ServiceState.CATCHUP - } else { - ServiceState.STARTED - } - } - - /** - * internal stop. - */ - private fun stop() { - Timber.i("## stop(): the service is stopped") - - /* TODO - if (null != session && session!!.isAlive) { - session!!.stopEventStream() - session!!.dataHandler.removeListener(mEventsListener) - CallsManager.getSharedInstance().removeSession(session) - } - session = null - */ - - // Stop the service - myStopSelf() - } - - /** - * internal catchup method. - * - * @param checkState true to check if the current state allow to perform a catchup - */ - private fun catchup(checkState: Boolean) { - var canCatchup = true - - if (!checkState) { - Timber.i("catchup without checking serviceState ") - } else { - Timber.i("catchup with serviceState " + serviceState + " CurrentActivity ") // TODO + VectorApp.getCurrentActivity()) - - /* TODO - // the catchup should only be done - // 1- the serviceState is in catchup : the event stream might have gone to sleep between two catchups - // 2- the thread is suspended - // 3- the application has been launched by a push so there is no displayed activity - canCatchup = (serviceState == ServiceState.CATCHUP - //|| (serviceState == ServiceState.PAUSE) - || ServiceState.STARTED == serviceState && null == VectorApp.getCurrentActivity()) - */ - } - - if (canCatchup) { - if (mSession != null) { - // TODO session!!.catchupEventStream() - } else { - Timber.i("catchup no session") - } - - serviceState = ServiceState.CATCHUP - } else { - Timber.i("No catchup is triggered because there is already a running event thread") - } - } - - /** - * The push status has been updated (i.e disabled or enabled). - * TODO Useless now? - */ - private fun pushStatusUpdate() { - Timber.i("## pushStatusUpdate") - } - - /* ========================================================================================== - * Push simulator - * ========================================================================================== */ - - /** - * @return true if the FCM is disable or not setup, user allowed background sync, user wants notification - */ - private fun shouldISimulatePush(): Boolean { - return false - - /* TODO - - if (Matrix.getInstance(applicationContext)?.defaultSession == null) { - Timber.i("## shouldISimulatePush: NO: no session") - - return false - } - - mPushManager?.let { pushManager -> - if (pushManager.useFcm() - && !TextUtils.isEmpty(pushManager.currentRegistrationToken) - && pushManager.isServerRegistered) { - // FCM is ok - Timber.i("## shouldISimulatePush: NO: FCM is up") - return false - } - - if (!pushManager.isBackgroundSyncAllowed) { - // User has disabled background sync - Timber.i("## shouldISimulatePush: NO: background sync not allowed") - return false - } - - if (!pushManager.areDeviceNotificationsAllowed()) { - // User does not want notifications - Timber.i("## shouldISimulatePush: NO: user does not want notification") - return false - } - } - - // Lets simulate push - Timber.i("## shouldISimulatePush: YES") - return true - */ - } - - - //================================================================================ - // Call management - //================================================================================ - - private fun handleCallInviteEvent(event: Event) { - /* - TODO - val session = Matrix.getMXSession(applicationContext, event.matrixId) - - // invalid session ? - // should never happen. - // But it could be triggered because of multi accounts management. - // The dedicated account is removing but some pushes are still received. - if (null == session || !session.isAlive) { - Timber.v("prepareCallNotification : don't bing - no session") - return - } - - val room: Room? = session.dataHandler.getRoom(event.roomId) - - // invalid room ? - if (null == room) { - Timber.i("prepareCallNotification : don't bing - the room does not exist") - return - } - - var callId: String? = null - var isVideo = false - - try { - callId = event.contentAsJsonObject?.get("call_id")?.asString - - // Check if it is a video call - val offer = event.contentAsJsonObject?.get("offer")?.asJsonObject - val sdp = offer?.get("sdp") - val sdpValue = sdp?.asString - - isVideo = sdpValue?.contains("m=video") == true - } catch (e: Exception) { - Timber.e(e, "prepareNotification : getContentAsJsonObject") - } - - if (!TextUtils.isEmpty(callId)) { - CallService.onIncomingCall(this, - isVideo, - room.getRoomDisplayName(this), - room.roomId, - session.myUserId!!, - callId!!) - } - */ - } - - companion object { - private const val PUSH_SIMULATOR_REQUEST_TAG = "PUSH_SIMULATOR_REQUEST_TAG" - - private const val ACTION_START = "im.vector.riotredesign.core.services.EventStreamServiceX.START" - private const val ACTION_LOGOUT = "im.vector.riotredesign.core.services.EventStreamServiceX.LOGOUT" - private const val ACTION_GO_TO_FOREGROUND = "im.vector.riotredesign.core.services.EventStreamServiceX.GO_TO_FOREGROUND" - private const val ACTION_GO_TO_BACKGROUND = "im.vector.riotredesign.core.services.EventStreamServiceX.GO_TO_BACKGROUND" - private const val ACTION_PUSH_UPDATE = "im.vector.riotredesign.core.services.EventStreamServiceX.PUSH_UPDATE" - private const val ACTION_PUSH_RECEIVED = "im.vector.riotredesign.core.services.EventStreamServiceX.PUSH_RECEIVED" - private const val ACTION_SIMULATED_PUSH_RECEIVED = "im.vector.riotredesign.core.services.EventStreamServiceX.SIMULATED_PUSH_RECEIVED" - private const val ACTION_STOP = "im.vector.riotredesign.core.services.EventStreamServiceX.STOP" - private const val ACTION_BOOT_COMPLETE = "im.vector.riotredesign.core.services.EventStreamServiceX.BOOT_COMPLETE" - private const val ACTION_APPLICATION_UPGRADE = "im.vector.riotredesign.core.services.EventStreamServiceX.APPLICATION_UPGRADE" - - /* ========================================================================================== - * Events sent to the service - * ========================================================================================== */ - - fun onApplicationStarted(context: Context) { - sendAction(context, ACTION_START) - } - - fun onLogout(context: Context) { - sendAction(context, ACTION_LOGOUT) - } - - fun onAppGoingToForeground(context: Context) { - sendAction(context, ACTION_GO_TO_FOREGROUND) - } - - fun onAppGoingToBackground(context: Context) { - sendAction(context, ACTION_GO_TO_BACKGROUND) - } - - fun onPushUpdate(context: Context) { - sendAction(context, ACTION_PUSH_UPDATE) - } - - fun onPushReceived(context: Context) { - sendAction(context, ACTION_PUSH_RECEIVED) - } - - fun onSimulatedPushReceived(context: Context) { - sendAction(context, ACTION_SIMULATED_PUSH_RECEIVED, true) - } - - fun onApplicationStopped(context: Context) { - sendAction(context, ACTION_STOP) - } - - fun onBootComplete(context: Context) { - sendAction(context, ACTION_BOOT_COMPLETE, true) - } - - fun onApplicationUpgrade(context: Context) { - sendAction(context, ACTION_APPLICATION_UPGRADE, true) - } - - private fun sendAction(context: Context, action: String, foreground: Boolean = false) { - Timber.i("sendAction $action") - - val intent = Intent(context, EventStreamServiceX::class.java) - intent.action = action - - if (foreground) { - ContextCompat.startForegroundService(context, intent) - } else { - context.startService(intent) - } - } - } -} diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt b/vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt deleted file mode 100644 index 501a2c7763..0000000000 --- a/vector/src/main/java/im/vector/riotredesign/core/services/HttpLongPoolingSyncService.kt +++ /dev/null @@ -1,102 +0,0 @@ -//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() -// 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) -// } -// } -// } -//} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/PushSimulatorWorker.kt b/vector/src/main/java/im/vector/riotredesign/core/services/PushSimulatorWorker.kt deleted file mode 100644 index d3f93f3280..0000000000 --- a/vector/src/main/java/im/vector/riotredesign/core/services/PushSimulatorWorker.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.content.Context -import androidx.work.Worker -import androidx.work.WorkerParameters - -/** - * This class simulate push event when FCM is not working/disabled - */ -class PushSimulatorWorker(val context: Context, - workerParams: WorkerParameters) : Worker(context, workerParams) { - - override fun doWork(): Result { - // Simulate a Push - EventStreamServiceX.onSimulatedPushReceived(context) - - // Indicate whether the task finished successfully with the Result - return Result.success() - } -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt deleted file mode 100644 index 1834a994ed..0000000000 --- a/vector/src/main/java/im/vector/riotredesign/core/services/RestartBroadcastReceiver.kt +++ /dev/null @@ -1,37 +0,0 @@ -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 - } -} diff --git a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt index eba8a85e75..8c0be58256 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt @@ -76,9 +76,7 @@ class LoginActivity : VectorBaseActivity() { Matrix.getInstance().currentSession = data data.open() data.setFilter(FilterService.FilterPreset.RiotFilter) - //TODO sync -// data.shoudPauseOnBackground(false) -// data.startSync() + data.startSync() get().startWithSession(data) goToHome() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index 15a3ef8bac..1a352864ca 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -36,7 +36,7 @@ import java.io.FileOutputStream * organise them in order to display them in the notification drawer. * Events can be grouped into the same notification, old (already read) events can be removed to do some cleaning. */ -class NotificationDrawerManager(val context: Context) { +class NotificationDrawerManager(val context: Context, private val outdatedDetector: OutdatedEventDetector?) { //The first time the notification drawer is refreshed, we force re-render of all notifications private var firstTime = true @@ -53,7 +53,7 @@ class NotificationDrawerManager(val context: Context) { object : IconLoader.IconLoaderListener { override fun onIconsLoaded() { // Force refresh - refreshNotificationDrawer(null) + refreshNotificationDrawer() } }) @@ -123,7 +123,7 @@ class NotificationDrawerManager(val context: Context) { synchronized(eventList) { eventList.clear() } - refreshNotificationDrawer(null) + refreshNotificationDrawer() } /** Clear all known message events for this room and refresh the notification drawer */ @@ -139,7 +139,7 @@ class NotificationDrawerManager(val context: Context) { } NotificationUtils.cancelNotificationMessage(context, roomId, ROOM_MESSAGES_NOTIFICATION_ID) } - refreshNotificationDrawer(null) + refreshNotificationDrawer() } /** @@ -177,7 +177,7 @@ class NotificationDrawerManager(val context: Context) { } - fun refreshNotificationDrawer(outdatedDetector: OutdatedEventDetector?) { + fun refreshNotificationDrawer() { if (myUserDisplayName.isBlank()) { // TODO // initWithSession(Matrix.getInstance(context).defaultSession) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt index 5ee0cccdd9..4ebbe68651 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt @@ -16,6 +16,7 @@ package im.vector.riotredesign.features.notifications import android.content.Context +import im.vector.matrix.android.api.Matrix class OutdatedEventDetector(val context: Context) { @@ -28,20 +29,9 @@ class OutdatedEventDetector(val context: Context) { if (notifiableEvent is NotifiableMessageEvent) { val eventID = notifiableEvent.eventId val roomID = notifiableEvent.roomId - /* - TODO - Matrix.getMXSession(context.applicationContext, notifiableEvent.matrixID)?.let { session -> - //find the room - if (session.isAlive) { - session.dataHandler.getRoom(roomID)?.let { room -> - if (room.isEventRead(eventID)) { - Timber.v("Notifiable Event $eventID is read, and should be removed") - return true - } - } - } - } - */ + val session = Matrix.getInstance().currentSession ?: return false + val room = session.getRoom(roomID) ?: return false + return room.isEventRead(eventID) } return false } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt index b94cb7f6e7..260899170f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt @@ -26,7 +26,7 @@ class PushRuleTriggerListener( } override fun batchFinish() { - drawerManager.refreshNotificationDrawer(null) + drawerManager.refreshNotificationDrawer() } fun startWithSession(session: Session) { @@ -41,6 +41,6 @@ class PushRuleTriggerListener( session?.removePushRuleListener(this) session = null drawerManager.clearAllEvents() - drawerManager.refreshNotificationDrawer(null) + drawerManager.refreshNotificationDrawer() } } \ No newline at end of file From 0584fc36666c73476c192feb1d922002d9b7a370 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 21 Jun 2019 18:26:35 +0200 Subject: [PATCH 03/26] Get real push rules from server and evaluate them --- .../matrix/android/api/pushrules/Action.kt | 84 ++++---- .../matrix/android/api/pushrules/Condition.kt | 25 +-- ...hRulesProvider.kt => ConditionResolver.kt} | 15 +- .../pushrules/ContainsDisplayNameCondition.kt | 80 ++++++++ .../api/pushrules/EventMatchCondition.kt | 13 +- .../android/api/pushrules/PushRuleService.kt | 9 + .../api/pushrules/RoomMemberCountCondition.kt | 71 +++++++ .../SenderNotificationPermissionCondition.kt | 36 ++++ .../api/pushrules/rest/PushCondition.kt | 27 ++- .../android/api/pushrules/rest/PushRule.kt | 2 +- ...shruleResponse.kt => PushrulesResponse.kt} | 2 +- .../session/room/members/MembershipService.kt | 2 + .../database/mapper/PushConditionMapper.kt | 26 +++ .../database/mapper/PushRulesMapper.kt | 105 ++++++++++ .../database/model/PushRulesEntity.kt | 2 +- .../internal/database/query/PushersQueries.kt | 12 +- .../internal/session/DefaultSession.kt | 11 + .../android/internal/session/SessionModule.kt | 24 ++- .../session/notification/BingRuleWatcher.kt | 38 +++- .../notification/DefaultPushRuleService.kt | 188 ++++++++++++++++++ .../notification/MockPushRuleProvider.kt | 41 ---- .../session/notification/PushRulesManager.kt | 85 -------- .../pushers/DefaultConditionResolver.kt | 55 +++++ .../session/pushers/GetPushrulesTask.kt | 33 +++ .../internal/session/pushers/PushrulesApi.kt | 72 +++++++ .../membership/DefaultMembershipService.kt | 8 + .../api/pushrules/PushrulesConditionTest.kt | 188 ++++++++++++++++++ .../vector/riotredesign/VectorApplication.kt | 7 +- .../core/preference/BingRulePreference.kt | 2 +- .../core/ui/list/GenericFooterItem.kt | 62 ++++++ .../features/login/LoginActivity.kt | 1 + .../notifications/NotifiableEventResolver.kt | 12 +- .../notifications/NotificationAction.kt | 41 ++++ .../NotificationDrawerManager.kt | 22 -- .../notifications/PushRuleTriggerListener.kt | 17 +- .../VectorSettingsPreferencesFragment.kt | 12 +- .../settings/push/PushGatewaysFragment.kt | 36 +++- .../features/settings/push/PushRuleItem.kt | 71 +++++++ .../settings/push/PushRulesFragment.kt | 80 ++++++++ .../settings/push/PushRulesViewModel.kt | 43 ++++ .../res/drawable/ic_action_dont_notify.xml | 25 +++ .../res/drawable/ic_action_notify_noisy.xml | 18 ++ .../res/drawable/ic_action_notify_silent.xml | 24 +++ ...ml => fragment_generic_recycler_epoxy.xml} | 0 .../main/res/layout/item_generic_footer.xml | 16 ++ .../res/layout/item_pushgateway.xml | 0 .../src/main/res/layout/item_pushrule_raw.xml | 43 ++++ vector/src/main/res/values/strings_riotX.xml | 4 +- .../res/xml/vector_settings_notifications.xml | 19 +- 49 files changed, 1535 insertions(+), 274 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/{PushRulesProvider.kt => ConditionResolver.kt} (52%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ContainsDisplayNameCondition.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/{PushruleResponse.kt => PushrulesResponse.kt} (97%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushConditionMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/core/ui/list/GenericFooterItem.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationAction.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt create mode 100644 vector/src/main/res/drawable/ic_action_dont_notify.xml create mode 100644 vector/src/main/res/drawable/ic_action_notify_noisy.xml create mode 100644 vector/src/main/res/drawable/ic_action_notify_silent.xml rename vector/src/main/res/layout/{fragment_settings_pushgateways.xml => fragment_generic_recycler_epoxy.xml} (100%) create mode 100644 vector/src/main/res/layout/item_generic_footer.xml rename vector/src/{debug => main}/res/layout/item_pushgateway.xml (100%) create mode 100644 vector/src/main/res/layout/item_pushrule_raw.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt index a7789aaaae..797830f491 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt @@ -43,51 +43,53 @@ class Action(val type: Type) { var stringValue: String? = null var boolValue: Boolean? = null -} - -fun PushRule.domainActions(): List? { - val actions = ArrayList() - 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) + companion object { + fun mapFrom(pushRule: PushRule): List? { + val actions = ArrayList() + pushRule.actions.forEach { actionStrOrObj -> + if (actionStrOrObj is String) { + 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}") } } - } - "highlight" -> { - (actionStrOrObj["value"] as? Boolean)?.let { boolValue -> - Action(Action.Type.SET_TWEAK).also { - it.tweak_action = "highlight" - it.boolValue = boolValue - actions.add(it) - } - } - } - else -> { + } else { Timber.w("Unsupported action type ${actionStrOrObj}") + return null } } - } else { - Timber.w("Unsupported action type ${actionStrOrObj}") - return null + return if (actions.isEmpty()) null else actions } } - return if (actions.isEmpty()) null else actions -} \ No newline at end of file +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt index d20f969263..c0bb4f16f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt @@ -15,25 +15,23 @@ */ 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"), + 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 + "event_match" -> event_match + "contains_display_name" -> contains_display_name + "room_member_count" -> room_member_count + "sender_notification_permission" -> sender_notification_permission else -> UNRECOGNIZE } } @@ -42,10 +40,9 @@ abstract class Condition(val kind: Kind) { } - abstract fun isSatisfied(event: Event): Boolean + abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean - companion object { - //TODO factory methods? + open fun technicalDescription(): String { + return "Kind: $kind" } - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt similarity index 52% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt index 75520f9a22..4d15d5deec 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRulesProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt @@ -15,11 +15,14 @@ */ package im.vector.matrix.android.api.pushrules -import im.vector.matrix.android.api.pushrules.rest.PushRule +/** + * Acts like a visitor on Conditions. + * This class as all required context needed to evaluate rules + */ +interface ConditionResolver { -interface PushRulesProvider { - - fun getOrderedPushrules(): List - - fun onRulesUpdate(newRules: List) + fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean + fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean + fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean + fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ContainsDisplayNameCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ContainsDisplayNameCondition.kt new file mode 100644 index 0000000000..ce9f88e377 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ContainsDisplayNameCondition.kt @@ -0,0 +1,80 @@ +/* + * 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 android.text.TextUtils +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.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import timber.log.Timber +import java.util.regex.Pattern + +class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) { + + override fun isSatisfied(conditionResolver: ConditionResolver): Boolean { + return conditionResolver.resolveContainsDisplayNameCondition(this) + } + + override fun technicalDescription(): String { + return "User is mentioned" + } + + fun isSatisfied(event: Event, displayName: String): Boolean { + //TODO the spec says: + // Matches any message whose content is unencrypted and contains the user's current display name + var message = when (event.type) { + EventType.MESSAGE -> { + event.content.toModel() + } +// EventType.ENCRYPTED -> { +// event.root.getClearContent()?.toModel() +// } + else -> null + } ?: return false + + return caseInsensitiveFind(displayName, message.body) + } + + + companion object { + /** + * Returns whether a string contains an occurrence of another, as a standalone word, regardless of case. + * + * @param subString the string to search for + * @param longString the string to search in + * @return whether a match was found + */ + fun caseInsensitiveFind(subString: String, longString: String): Boolean { + // add sanity checks + if (TextUtils.isEmpty(subString) || TextUtils.isEmpty(longString)) { + return false + } + + var res = false + + try { + val pattern = Pattern.compile("(\\W|^)" + Pattern.quote(subString) + "(\\W|$)", Pattern.CASE_INSENSITIVE) + res = pattern.matcher(longString).find() + } catch (e: Exception) { + Timber.e(e, "## caseInsensitiveFind() : failed") + } + + return res + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt index 0c729799bc..7325abba2a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt @@ -19,9 +19,18 @@ 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) { +class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.event_match) { - override fun isSatisfied(event: Event): Boolean { + override fun isSatisfied(conditionResolver: ConditionResolver) : Boolean { + return conditionResolver.resolveEventMatchCondition(this) + } + + override fun technicalDescription(): String { + return "'$key' Matches '$pattern'" + } + + + fun isSatisfied(event: Event): Boolean { //TODO encrypted events? val rawJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJsonValue(event) as? Map<*, *> ?: return false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt index 47328d1755..fc71d16d90 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt @@ -15,12 +15,19 @@ */ package im.vector.matrix.android.api.pushrules +import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.session.events.model.Event interface PushRuleService { + /** + * Fetch the push rules from the server + */ + fun fetchPushRules(scope: String = "global") + //TODO get push rule set + fun getPushrules(scope: String = "global"): List //TODO update rule @@ -28,6 +35,8 @@ interface PushRuleService { fun removePushRuleListener(listener: PushRuleListener) +// fun fulfilledBingRule(event: Event, rules: List): PushRule? + interface PushRuleListener { fun onMatchRule(event: Event, actions: List) fun batchFinish() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt new file mode 100644 index 0000000000..b2bed4c230 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt @@ -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.matrix.android.api.pushrules + +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.RoomService +import timber.log.Timber +import java.util.regex.Pattern + +private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$") + +class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) { + + override fun isSatisfied(conditionResolver: ConditionResolver): Boolean { + return conditionResolver.resolveRoomMemberCountCondition(this) + } + + override fun technicalDescription(): String { + return "Room member count is $`is`" + } + + fun isSatisfied(event: Event, session: RoomService?): Boolean { + // sanity check^ + val roomId = event.roomId ?: return false + val room = session?.getRoom(roomId) ?: return false + + // Parse the is field into prefix and number the first time + val (prefix, count) = parseIsField() ?: return false + + val numMembers = room.getNumberOfJoinedMembers() + + return when (prefix) { + "<" -> numMembers < count + ">" -> numMembers > count + "<=" -> numMembers <= count + ">=" -> numMembers >= count + else -> numMembers == count + } + } + + /** + * Parse the is field to extract meaningful information. + */ + private fun parseIsField(): Pair? { + try { + val match = regex.matcher(`is`) + if (match.find()) { + val prefix = match.group(1) + val count = match.group(2).toInt() + return prefix to count + } + } catch (t: Throwable) { + Timber.d(t) + } + return null + + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt new file mode 100644 index 0000000000..3a67ff933c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt @@ -0,0 +1,36 @@ +package im.vector.matrix.android.api.pushrules + +/* + * 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. + */ +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.PowerLevels + + +class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) { + + override fun isSatisfied(conditionResolver: ConditionResolver): Boolean { + return conditionResolver.resolveSenderNotificationPermissionCondition(this) + } + + override fun technicalDescription(): String { + return "User power level <$key>" + } + + + fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean { + return event.sender != null && powerLevels.getUserPowerLevel(event.sender) >= powerLevels.notificationLevel(key) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt index 72fc2b242c..fac0fc5182 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt @@ -17,8 +17,7 @@ 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 im.vector.matrix.android.api.pushrules.* import timber.log.Timber @JsonClass(generateAdapter = true) @@ -37,7 +36,7 @@ data class PushCondition( fun asExecutableCondition(): Condition? { return when (Condition.Kind.fromString(this.kind)) { - Condition.Kind.EVENT_MATCH -> { + Condition.Kind.event_match -> { if (this.key != null && this.pattern != null) { EventMatchCondition(key, pattern) } else { @@ -45,10 +44,24 @@ data class PushCondition( null } } - Condition.Kind.CONTAINS_DISPLAY_NAME -> TODO() - Condition.Kind.ROOM_MEMBER_COUNT -> TODO() - Condition.Kind.SENDER_NOTIFICATION_PERMISSION -> TODO() - Condition.Kind.UNRECOGNIZE -> null + Condition.Kind.contains_display_name -> { + ContainsDisplayNameCondition() + } + Condition.Kind.room_member_count -> { + if (this.iz.isNullOrBlank()) { + Timber.e("Malformed ROOM_MEMBER_COUNT condition") + null + } else { + RoomMemberCountCondition(this.iz) + } + } + Condition.Kind.sender_notification_permission -> { + this.key?.let { SenderNotificationPermissionCondition(it) } + } + Condition.Kind.UNRECOGNIZE -> { + Timber.e("Unknwon kind $kind") + null + } } } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt index 58d36cfcdb..2e090a2487 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt @@ -25,7 +25,7 @@ data class PushRule( //Required. The domainActions to perform when this rule is matched. val actions: List, //Required. Whether this is a default rule, or has been set explicitly. - val default: Boolean, + val default: Boolean? = false, //Required. Whether the push rule is enabled or not. val enabled: Boolean, //Required. The ID of this rule. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt similarity index 97% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt index 5642bf820c..75ac3aee9d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushruleResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.api.pushrules.rest.Ruleset * All push rulesets for a user. */ @JsonClass(generateAdapter = true) -data class PushruleResponse( +data class PushrulesResponse( //Global rules, account level applying to all devices val global: Ruleset, //Device specific rules, apply only to current device diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index 59054dc184..7c21695ec6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -49,6 +49,8 @@ interface MembershipService { */ fun getRoomMemberIdsLive(): LiveData> + fun getNumberOfJoinedMembers() : Int + /** * Invite a user in the room */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushConditionMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushConditionMapper.kt new file mode 100644 index 0000000000..eef122a575 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushConditionMapper.kt @@ -0,0 +1,26 @@ +package im.vector.matrix.android.internal.database.mapper + +import im.vector.matrix.android.api.pushrules.rest.PushCondition +import im.vector.matrix.android.internal.database.model.PushConditionEntity + + +internal object PushConditionMapper { + + fun map(entity: PushConditionEntity): PushCondition { + return PushCondition( + kind = entity.kind, + iz = entity.iz, + key = entity.key, + pattern = entity.pattern + ) + } + + fun map(domain: PushCondition): PushConditionEntity { + return PushConditionEntity( + kind = domain.kind, + iz = domain.iz, + key = domain.key, + pattern = domain.pattern + ) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt new file mode 100644 index 0000000000..1ed2151e68 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt @@ -0,0 +1,105 @@ +/* + * 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 com.squareup.moshi.Types +import im.vector.matrix.android.api.pushrules.Condition +import im.vector.matrix.android.api.pushrules.rest.PushCondition +import im.vector.matrix.android.api.pushrules.rest.PushRule +import im.vector.matrix.android.internal.database.model.PushRuleEntity +import im.vector.matrix.android.internal.di.MoshiProvider +import io.realm.RealmList +import timber.log.Timber + + +internal object PushRulesMapper { + + private val moshiActionsAdapter = MoshiProvider.providesMoshi().adapter>(Types.newParameterizedType(List::class.java, Any::class.java)) + +// private val listOfAnyAdapter: JsonAdapter> = +// moshi.adapter>(Types.newParameterizedType(List::class.java, Any::class.java), kotlin.collections.emptySet(), "actions") + + fun mapContentRule(pushrule: PushRuleEntity): PushRule { + return PushRule( + actions = fromActionStr(pushrule.actionsStr), + default = pushrule.default, + enabled = pushrule.enabled, + ruleId = pushrule.ruleId, + conditions = listOf( + PushCondition(Condition.Kind.event_match.name, "content.body", pushrule.pattern) + ) + ) + } + + private fun fromActionStr(actionsStr: String?): List { + try { + return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList() + } catch (e: Throwable) { + Timber.e(e, "## failed to map push rule actions <$actionsStr>") + return emptyList() + } + } + + + fun mapRoomRule(pushrule: PushRuleEntity): PushRule { + return PushRule( + actions = fromActionStr(pushrule.actionsStr), + default = pushrule.default, + enabled = pushrule.enabled, + ruleId = pushrule.ruleId, + conditions = listOf( + PushCondition(Condition.Kind.event_match.name, "room_id", pushrule.ruleId) + ) + ) + } + + fun mapSenderRule(pushrule: PushRuleEntity): PushRule { + return PushRule( + actions = fromActionStr(pushrule.actionsStr), + default = pushrule.default, + enabled = pushrule.enabled, + ruleId = pushrule.ruleId, + conditions = listOf( + PushCondition(Condition.Kind.event_match.name, "user_id", pushrule.ruleId) + ) + ) + } + + + fun map(pushrule: PushRuleEntity): PushRule { + return PushRule( + actions = fromActionStr(pushrule.actionsStr), + default = pushrule.default, + enabled = pushrule.enabled, + ruleId = pushrule.ruleId, + conditions = pushrule.conditions?.map { PushConditionMapper.map(it) } + ) + } + + fun map(pushRule: PushRule): PushRuleEntity { + return PushRuleEntity( + actionsStr = moshiActionsAdapter.toJson(pushRule.actions), + default = pushRule.default ?: false, + enabled = pushRule.enabled, + ruleId = pushRule.ruleId, + pattern = pushRule.pattern, + conditions = pushRule.conditions?.let { + RealmList(*pushRule.conditions.map { PushConditionMapper.map(it) }.toTypedArray()) + } ?: RealmList() + ) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt index fc8d66b599..1f1681d839 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt @@ -24,7 +24,7 @@ internal open class PushRulesEntity( @Index var userId: String = "", var scope: String = "", var rulesetKey: String = "", - var pushRules: RealmList = RealmList() + var pushRules: RealmList = RealmList() ) : RealmObject() { companion object } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt index f5e9a6d00e..ff91665f14 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt @@ -15,8 +15,10 @@ */ package im.vector.matrix.android.internal.database.query +import im.vector.matrix.android.api.pushrules.rest.PushRule +import im.vector.matrix.android.internal.database.model.* +import im.vector.matrix.android.internal.database.model.PushRulesEntity 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 @@ -29,5 +31,11 @@ internal fun PusherEntity.Companion.where(realm: Realm, userId: String, pushKey: equalTo(PusherEntityFields.PUSH_KEY, pushKey) } } - +} + +internal fun PushRulesEntity.Companion.where(realm: Realm, userId: String, scope: String, rulesetKey: String) : RealmQuery { + return realm.where() + .equalTo(PushRulesEntityFields.USER_ID,userId) + .equalTo(PushRulesEntityFields.SCOPE,scope) + .equalTo(PushRulesEntityFields.RULESET_KEY,rulesetKey) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index cef7e27581..4931809694 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -25,6 +25,7 @@ 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.pushrules.rest.PushRule 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 @@ -82,6 +83,7 @@ import java.util.* internal class DefaultSession(override val sessionParams: SessionParams) : Session, MatrixKoinComponent { + companion object { const val SCOPE: String = "session" } @@ -497,4 +499,13 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi return pushersService.livePushers() } + override fun getPushrules(scope: String): List { + return pushRuleService.getPushrules(scope) + } + + override fun fetchPushRules(scope: String) { + pushRuleService.fetchPushRules(scope) + } + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 9d24ae54c2..97b7bc376f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -20,7 +20,6 @@ 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 @@ -38,11 +37,11 @@ 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.notification.DefaultPushRuleService 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.* @@ -175,18 +174,25 @@ internal class SessionModule(private val sessionParams: SessionParams) { } scope(DefaultSession.SCOPE) { - MockPushRuleProvider() as PushRulesProvider + val retrofit: Retrofit = get() + retrofit.create(PushrulesApi::class.java) } scope(DefaultSession.SCOPE) { - get() as PushRuleService - } - scope(DefaultSession.SCOPE) { - PushRulesManager(get(), get()) + get() as PushRuleService } scope(DefaultSession.SCOPE) { - BingRuleWatcher(get(), get(), get(), get()) + DefaultPushRuleService(get(), get(), get(), get()) + } + + scope(DefaultSession.SCOPE) { + DefaultGetPushrulesTask(get()) as GetPushRulesTask + } + + + scope(DefaultSession.SCOPE) { + BingRuleWatcher(get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt index 371c7f2ce0..0c12b3e5bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -16,19 +16,19 @@ 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.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.task.TaskExecutor +import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver +import timber.log.Timber internal class BingRuleWatcher(monarchy: Monarchy, - private val credentials: Credentials, - private val taskExecutor: TaskExecutor, - private val pushRulesManager: PushRulesManager) : + private val defaultPushRuleService: DefaultPushRuleService) : RealmLiveEntityObserver(monarchy) { override val query = Monarchy.Query { @@ -41,9 +41,33 @@ internal class BingRuleWatcher(monarchy: Monarchy, override fun processChanges(inserted: List, updated: List, deleted: List) { //TODO task - inserted.map { it.asDomain() }.let { - pushRulesManager.processEvents(it) + 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) + } + } } + defaultPushRuleService.dispatchFinish() + } + + private fun fulfilledBingRule(event: Event, rules: List): 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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt new file mode 100644 index 0000000000..b2a6b9b13f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -0,0 +1,188 @@ +/* + * 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.MatrixCallback +import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.pushrules.Action +import im.vector.matrix.android.api.pushrules.PushRuleService +import im.vector.matrix.android.api.pushrules.rest.PushRule +import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.internal.database.mapper.PushRulesMapper +import im.vector.matrix.android.internal.database.model.PushRulesEntity +import im.vector.matrix.android.internal.database.model.PusherEntityFields +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith + + +internal class DefaultPushRuleService( + private val sessionParams: SessionParams, + private val pushRulesTask: GetPushRulesTask, + private val taskExecutor: TaskExecutor, + private val monarchy: Monarchy +) : PushRuleService { + + + private var listeners = ArrayList() + + + override fun fetchPushRules(scope: String) { + pushRulesTask + .configureWith(Unit) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: PushrulesResponse) { + monarchy.runTransactionSync { realm -> + //clear existings? + //TODO + realm.where(PushRulesEntity::class.java) + .equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId) + .findAll().deleteAllFromRealm() + + var content = PushRulesEntity(sessionParams.credentials.userId, scope, "content") + data.global.content?.forEach { rule -> + PushRulesMapper.map(rule).also { + content.pushRules.add(it) + } + } + realm.insertOrUpdate(content) + + var override = PushRulesEntity(sessionParams.credentials.userId, scope, "override") + data.global.override?.forEach { rule -> + PushRulesMapper.map(rule).also { + override.pushRules.add(it) + } + } + realm.insertOrUpdate(override) + + var rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room") + data.global.room?.forEach { rule -> + PushRulesMapper.map(rule).also { + rooms.pushRules.add(it) + } + } + realm.insertOrUpdate(rooms) + + var senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender") + data.global.sender?.forEach { rule -> + PushRulesMapper.map(rule).also { + senders.pushRules.add(it) + } + } + realm.insertOrUpdate(senders) + + var underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride") + data.global.underride?.forEach { rule -> + PushRulesMapper.map(rule).also { + underrides.pushRules.add(it) + } + } + realm.insertOrUpdate(underrides) + + + } + } + }) + .executeBy(taskExecutor) + } + + override fun getPushrules(scope: String): List { + + var contentRules: List = emptyList() + monarchy.doWithRealm { realm -> + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "content").findFirst()?.let { re -> + contentRules = re.pushRules.map { PushRulesMapper.mapContentRule(it) } + } + } + + var overrideRules: List = emptyList() + monarchy.doWithRealm { realm -> + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "override").findFirst()?.let { re -> + overrideRules = re.pushRules.map { PushRulesMapper.map(it) } + } + } + + var roomRules: List = emptyList() + monarchy.doWithRealm { realm -> + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "room").findFirst()?.let { re -> + roomRules = re.pushRules.map { PushRulesMapper.mapRoomRule(it) } + } + } + + var senderRules: List = emptyList() + monarchy.doWithRealm { realm -> + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "sender").findFirst()?.let { re -> + senderRules = re.pushRules.map { PushRulesMapper.mapSenderRule(it) } + } + } + + var underrideRules: List = emptyList() + monarchy.doWithRealm { realm -> + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "underride").findFirst()?.let { re -> + underrideRules = re.pushRules.map { PushRulesMapper.map(it) } + } + } + + return contentRules + overrideRules + roomRules + senderRules + underrideRules + } + + 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) { +// 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, Action.mapFrom(rule) ?: emptyList()) + } + } catch (e: Throwable) { + + } + } + + fun dispatchFinish() { + try { + listeners.forEach { + it.batchFinish() + } + } catch (e: Throwable) { + + } + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt deleted file mode 100644 index e4329c6574..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/MockPushRuleProvider.kt +++ /dev/null @@ -1,41 +0,0 @@ -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 { - 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::class.java).fromJson(raw) - - return listOf( - pushRule!! - ) - } - - override fun onRulesUpdate(newRules: List) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt deleted file mode 100644 index 5c5a7daba2..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/PushRulesManager.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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() - - - 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) { - 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 - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt new file mode 100644 index 0000000000..e85f43470f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt @@ -0,0 +1,55 @@ +/* + * 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.api.auth.data.SessionParams +import im.vector.matrix.android.api.pushrules.* +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.RoomService +import im.vector.matrix.android.internal.di.MatrixKoinComponent +import org.koin.standalone.inject +import timber.log.Timber + +internal class DefaultConditionResolver(val event: Event) : ConditionResolver, MatrixKoinComponent { + + private val roomService by inject() + + private val sessionParams by inject() + + override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean { + return eventMatchCondition.isSatisfied(event) + } + + override fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean { + return roomMemberCountCondition.isSatisfied(event, roomService) + } + + override fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean { +// val roomId = event.roomId ?: return false +// val room = roomService.getRoom(roomId) ?: return false + //TODO RoomState not yet managed + Timber.e("POWER LEVELS STATE NOT YET MANAGED BY RIOTX") + return false //senderNotificationPermissionCondition.isSatisfied(event, ) + } + + override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean { + val roomId = event.roomId ?: return false + val room = roomService.getRoom(roomId) ?: return false + val myDisplayName = room.getRoomMember(sessionParams.credentials.userId)?.displayName + ?: return false + return containsDisplayNameCondition.isSatisfied(event, myDisplayName) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt new file mode 100644 index 0000000000..1ce8c21ef9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt @@ -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.session.pushers + +import arrow.core.Try +import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task + + +internal interface GetPushRulesTask : Task + +internal class DefaultGetPushrulesTask(private val pushrulesApi: PushrulesApi) : GetPushRulesTask { + + override suspend fun execute(params: Unit): Try { + return executeRequest { + apiCall = pushrulesApi.getAllRules() + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt new file mode 100644 index 0000000000..cdec3543bc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt @@ -0,0 +1,72 @@ +/* + * 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.api.pushrules.rest.PushRule +import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse +import im.vector.matrix.android.internal.network.NetworkConstants +import retrofit2.Call +import retrofit2.http.* + + +internal interface PushrulesApi { + /** + * Get all push rules + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/") + fun getAllRules(): Call + + /** + * Update the ruleID enable status + * + * @param kind the notification kind (sender, room...) + * @param ruleId the ruleId + * @param enable the new enable status + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled") + fun updateEnableRuleStatus(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body enable: Boolean?): Call + + + /** + * Update the ruleID enable status + * + * @param kind the notification kind (sender, room...) + * @param ruleId the ruleId + * @param actions the actions + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions") + fun updateRuleActions(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body actions: Any): Call + + + /** + * Update the ruleID enable status + * + * @param kind the notification kind (sender, room...) + * @param ruleId the ruleId + */ + @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") + fun deleteRule(@Path("kind") kind: String, @Path("ruleId") ruleId: String): Call + + /** + * Add the ruleID enable status + * + * @param kind the notification kind (sender, room...) + * @param ruleId the ruleId. + * @param rule the rule to add. + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") + fun addRule(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body rule: PushRule): Call +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt index a9b48ea69a..a3c2fc1392 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt @@ -66,6 +66,14 @@ internal class DefaultMembershipService(private val roomId: String, ) } + override fun getNumberOfJoinedMembers(): Int { + var result = 0 + monarchy.runTransactionSync { + result = RoomMembers(it, roomId).getNumberOfJoinedMembers() + } + return result + } + override fun invite(userId: String, callback: MatrixCallback) { val params = InviteTask.Params(roomId, userId) inviteTask.configureWith(params) diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index 2c7e29b227..d9b81917b2 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -1,10 +1,23 @@ package im.vector.matrix.android.api.pushrules +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.content.ContentAttachmentData 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.Room +import im.vector.matrix.android.api.session.room.RoomService +import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary 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.RoomSummary +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.util.Cancelable +import org.junit.Assert import org.junit.Test class PushrulesConditionTest { @@ -100,4 +113,179 @@ class PushrulesConditionTest { assert(condition.isSatisfied(simpleTextEvent)) } + + + @Test + fun test_roommember_condition() { + + + val conditionEqual3 = RoomMemberCountCondition("3") + val conditionEqual3Bis = RoomMemberCountCondition("==3") + val conditionLessThan3 = RoomMemberCountCondition("<3") + + val session = MockRoomService() + + Event( + type = "m.room.message", + eventId = "mx0", + content = MessageTextContent("m.text", "A").toContent(), + originServerTs = 0, + roomId = "2joined").also { + Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, session)) + Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, session)) + Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, session)) + } + + Event( + type = "m.room.message", + eventId = "mx0", + content = MessageTextContent("m.text", "A").toContent(), + originServerTs = 0, + roomId = "3joined").also { + Assert.assertTrue("This room has 3 members",conditionEqual3.isSatisfied(it, session)) + Assert.assertTrue("This room has 3 members",conditionEqual3Bis.isSatisfied(it, session)) + Assert.assertFalse("This room has more than 3 members",conditionLessThan3.isSatisfied(it, session)) + } + } + + + class MockRoomService() : RoomService { + override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback) { + + } + + override fun getRoom(roomId: String): Room? { + return when (roomId) { + "2joined" -> MockRoom(roomId, 2) + "3joined" -> MockRoom(roomId, 3) + else -> null + } + } + + override fun liveRoomSummaries(): LiveData> { + return MutableLiveData() + } + + } + + class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room { + + + override fun getNumberOfJoinedMembers(): Int { + return _numberOfJoinedMembers + } + + override val roomSummary: LiveData + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + + override fun createTimeline(eventId: String?, allowedTypes: List?): Timeline { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun getTimeLineEvent(eventId: String): TimelineEvent? { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun sendMedia(attachment: ContentAttachmentData): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun sendMedias(attachments: List): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun redactEvent(event: Event, reason: String?): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun markAllAsRead(callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun setReadReceipt(eventId: String, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun isEventRead(eventId: String): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun loadRoomMembersIfNeeded(): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun getRoomMember(userId: String): RoomMember? { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun getRoomMemberIdsLive(): LiveData> { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun invite(userId: String, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun join(callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun leave(callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun updateTopic(topic: String, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun sendReaction(reaction: String, targetEventId: String): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun undoReaction(reaction: String, targetEventId: String, myUserId: String) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun editTextMessage(targetEventId: String, newBodyText: String, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String): Cancelable { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun getEventSummaryLive(eventId: String): LiveData> { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun isEncrypted(): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun encryptionAlgorithm(): String? { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun shouldEncryptForInvitedMembers(): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + } + } diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index ed98d3df70..93e34a782f 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -137,8 +137,10 @@ class VectorApplication : Application() { } else { //TODO check if notifications are enabled for this device //We need to use alarm in this mode - AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext,4_000L) - Timber.i("Alarm scheduled to restart service") + if (Matrix.getInstance().currentSession != null) { + AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext, 4_000L) + Timber.i("Alarm scheduled to restart service") + } } } @@ -150,6 +152,7 @@ class VectorApplication : Application() { it.refreshPushers() //bind to the sync service get().startWithSession(it) + it.fetchPushRules() } } diff --git a/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt b/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt index bd10f70941..9a17390bb9 100755 --- a/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/preference/BingRulePreference.kt @@ -25,7 +25,7 @@ import android.widget.TextView import androidx.preference.PreferenceViewHolder import im.vector.riotredesign.R -// TODO Replace by real Bingrule class +// TODO Replace by real Bingrule class, then delete class BingRule(rule: BingRule) { fun shouldNotNotify() = false fun shouldNotify() = false diff --git a/vector/src/main/java/im/vector/riotredesign/core/ui/list/GenericFooterItem.kt b/vector/src/main/java/im/vector/riotredesign/core/ui/list/GenericFooterItem.kt new file mode 100644 index 0000000000..30cd69a4cd --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/ui/list/GenericFooterItem.kt @@ -0,0 +1,62 @@ +/* + * 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.ui.list + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder +import im.vector.riotredesign.core.epoxy.VectorEpoxyModel +import im.vector.riotredesign.core.extensions.setTextOrHide + +/** + * A generic list item. + * Displays an item with a title, and optional description. + * Can display an accessory on the right, that can be an image or an indeterminate progress. + * If provided with an action, will display a button at the bottom of the list item. + */ +@EpoxyModelClass(layout = R.layout.item_generic_footer) +abstract class GenericFooterItem : VectorEpoxyModel() { + + + @EpoxyAttribute + var text: String? = null + + @EpoxyAttribute + var style: GenericItem.STYLE = GenericItem.STYLE.NORMAL_TEXT + + @EpoxyAttribute + var itemClickAction: GenericItem.Action? = null + + override fun bind(holder: Holder) { + + holder.text.setTextOrHide(text) + when (style) { + GenericItem.STYLE.BIG_TEXT -> holder.text.textSize = 18f + GenericItem.STYLE.NORMAL_TEXT -> holder.text.textSize = 14f + } + + + holder.view.setOnClickListener { + itemClickAction?.perform?.run() + } + } + + class Holder : VectorEpoxyHolder() { + val text by bind(R.id.itemGenericFooterText) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt index 8c0be58256..a3c4d426b1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/login/LoginActivity.kt @@ -78,6 +78,7 @@ class LoginActivity : VectorBaseActivity() { data.setFilter(FilterService.FilterPreset.RiotFilter) data.startSync() get().startWithSession(data) + data.fetchPushRules() goToHome() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt index befff24a58..1854ccd098 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt @@ -43,7 +43,7 @@ class NotifiableEventResolver(val context: Context) { //private val eventDisplay = RiotEventDisplay(context) - fun resolveEvent(event: Event/*, roomState: RoomState?*/, bingRule: PushRule?, session: Session): NotifiableEvent? { + fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session): NotifiableEvent? { // val store = session.dataHandler.store @@ -55,7 +55,7 @@ class NotifiableEventResolver(val context: Context) { when (event.getClearType()) { EventType.MESSAGE -> { - return resolveMessageEvent(event, bingRule, session) + return resolveMessageEvent(event, session) } // EventType.ENCRYPTED -> { // val messageEvent = resolveMessageEvent(event, bingRule, session, store) @@ -88,12 +88,10 @@ class NotifiableEventResolver(val context: Context) { } - private fun resolveMessageEvent(event: Event, pushRule: PushRule?, session: Session): NotifiableEvent? { + private fun resolveMessageEvent(event: Event, 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 = 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 = session.getRoom(event.roomId!! /*roomID cannot be null (see Matrix SDK code)*/) @@ -109,7 +107,7 @@ class NotifiableEventResolver(val context: Context) { val notifiableEvent = NotifiableMessageEvent( eventId = event.eventId ?: "", timestamp = event.originServerTs ?: 0, - noisy = noisy, + noisy = false,//will be updated senderName = senderDisplayName, senderId = event.senderId, body = body, @@ -130,7 +128,7 @@ class NotifiableEventResolver(val context: Context) { val notifiableEvent = NotifiableMessageEvent( eventId = event.eventId!!, timestamp = event.originServerTs ?: 0, - noisy = noisy, + noisy = false,//will be updated senderName = senderDisplayName, senderId = event.senderId, body = body, diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationAction.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationAction.kt new file mode 100644 index 0000000000..a1eac94610 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationAction.kt @@ -0,0 +1,41 @@ +/* + * 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.notifications + +import im.vector.matrix.android.api.pushrules.Action + +data class NotificationAction( + val shouldNotify: Boolean, + val highlight: Boolean = false, + val soundName: String? = null +) { + companion object { + fun extractFrom(ruleActions: List): NotificationAction { + var shouldNotify = false + var highlight = false + var sound: String? = null + ruleActions.forEach { + if (it.type == Action.Type.NOTIFY) shouldNotify = true + if (it.type == Action.Type.DONT_NOTIFY) shouldNotify = false + if (it.type == Action.Type.SET_TWEAK) { + if (it.tweak_action == "highlight") highlight = it.boolValue ?: false + if (it.tweak_action == "sound") sound = it.stringValue + } + } + return NotificationAction(shouldNotify, highlight, sound) + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index 1a352864ca..5bf7caa7d0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -57,28 +57,6 @@ class NotificationDrawerManager(val context: Context, private val outdatedDetect } }) - /** - * No multi session support for now - */ - private fun initWithSession(session: Session?) { - session?.let { - /* - myUserDisplayName = it.myUser?.displayname ?: it.myUserId - - // User Avatar - it.myUser?.avatarUrl?.let { avatarUrl -> - val userAvatarUrlPath = it.mediaCache?.thumbnailCacheFile(avatarUrl, avatarSize) - if (userAvatarUrlPath != null) { - myUserAvatarUrl = userAvatarUrlPath.path - } else { - // prepare for the next time - session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), avatarUrl, avatarSize) - } - } - */ - } - } - /** Should be called as soon as a new event is ready to be displayed. The notification corresponding to this event will not be displayed until diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt index 260899170f..458db906cb 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt @@ -16,12 +16,24 @@ class PushRuleTriggerListener( var session: Session? = null override fun onMatchRule(event: Event, actions: List) { + Timber.v("Push rule match for event ${event.eventId}") if (session == null) { Timber.e("Called without active session") return } - resolver.resolveEvent(event,null,session!!)?.let { - drawerManager.onNotifiableEventReceived(it) + val notificationAction = NotificationAction.extractFrom(actions) + if (notificationAction.shouldNotify) { + val resolveEvent = resolver.resolveEvent(event, session!!) + if (resolveEvent == null) { + Timber.v("## Failed to resolve event") + //TODO + } else { + resolveEvent.noisy = !notificationAction.soundName.isNullOrBlank() + Timber.v("New event to notify $resolveEvent tweaks:$notificationAction") + drawerManager.onNotifiableEventReceived(resolveEvent) + } + } else { + Timber.v("Matched push rule is set to not notify") } } @@ -43,4 +55,5 @@ class PushRuleTriggerListener( drawerManager.clearAllEvents() drawerManager.refreshNotificationDrawer() } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt index d073d56066..6ce235f6b3 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt @@ -1671,15 +1671,15 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref var index = 0 - for (pusher in mDisplayedPushers) { - if (null != pusher.lang) { - val isThisDeviceTarget = TextUtils.equals(pushManager.currentRegistrationToken, pusher.pushkey) + for (pushRule in mDisplayedPushers) { + if (null != pushRule.lang) { + val isThisDeviceTarget = TextUtils.equals(pushManager.currentRegistrationToken, pushRule.pushkey) val preference = VectorPreference(activity).apply { mTypeface = if (isThisDeviceTarget) Typeface.BOLD else Typeface.NORMAL } - preference.title = pusher.deviceDisplayName - preference.summary = pusher.appDisplayName + preference.title = pushRule.deviceDisplayName + preference.summary = pushRule.appDisplayName preference.key = PUSHER_PREFERENCE_KEY_BASE + index index++ mPushersSettingsCategory.addPreference(preference) @@ -1694,7 +1694,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref .setPositiveButton(R.string.remove) { _, _ -> displayLoadingView() - pushManager.unregister(session, pusher, object : MatrixCallback { + pushManager.unregister(session, pushRule, object : MatrixCallback { override fun onSuccess(info: Void?) { refreshPushersList() onCommonDone(null) diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt index bdb29e8358..c468955f06 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt @@ -17,7 +17,6 @@ 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 @@ -27,19 +26,21 @@ 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.* +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.core.ui.list.genericFooterItem +import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* class PushGatewaysFragment : VectorBaseFragment() { - override fun getLayoutResId(): Int = R.layout.fragment_settings_pushgateways + override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy private val viewModel: PushGatewaysViewModel by fragmentViewModel(PushGatewaysViewModel::class) - private val epoxyController by lazy { PushGateWayController() } + private val epoxyController by lazy { PushGateWayController(StringProvider(requireContext().resources)) } override fun onResume() { super.onResume() - (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_gateways) + (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notifications_targets) } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -56,13 +57,26 @@ class PushGatewaysFragment : VectorBaseFragment() { epoxyController.setData(it) } - class PushGateWayController : TypedEpoxyController() { + class PushGateWayController(private val stringProvider: StringProvider) : TypedEpoxyController() { override fun buildModels(data: PushGatewayViewState?) { - val pushers = data?.pushgateways?.invoke() ?: return - pushers.forEach { - pushGatewayItem { - id("${it.pushKey}_${it.appId}") - pusher(it) + data?.pushgateways?.invoke()?.let { pushers -> + if (pushers.isEmpty()) { + genericFooterItem { + id("footer") + text(stringProvider.getString(R.string.settings_push_gateway_no_pushers)) + } + } else { + pushers.forEach { + pushGatewayItem { + id("${it.pushKey}_${it.appId}") + pusher(it) + } + } + } + } ?: run { + genericFooterItem { + id("footer") + text(stringProvider.getString(R.string.loading)) } } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt new file mode 100644 index 0000000000..47f41e9171 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt @@ -0,0 +1,71 @@ +package im.vector.riotredesign.features.settings.push + +import android.annotation.SuppressLint +import android.graphics.Color +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.airbnb.epoxy.EpoxyModelWithHolder +import im.vector.matrix.android.api.pushrules.Action +import im.vector.matrix.android.api.pushrules.rest.PushRule +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder +import im.vector.riotredesign.features.notifications.NotificationAction + + +@EpoxyModelClass(layout = R.layout.item_pushrule_raw) +abstract class PushRuleItem : EpoxyModelWithHolder() { + + @EpoxyAttribute + lateinit var pushRule: PushRule + + @SuppressLint("SetTextI18n") + override fun bind(holder: Holder) { + val context = holder.view.context + if (pushRule.enabled) { + holder.view.setBackgroundColor(Color.TRANSPARENT) + holder.ruleId.text = pushRule.ruleId + } else { + holder.view.setBackgroundColor(ContextCompat.getColor(context, R.color.vector_silver_color)) + holder.ruleId.text = "[Disabled] ${pushRule.ruleId}" + } + val actions = Action.mapFrom(pushRule) + if (actions.isNullOrEmpty()) { + holder.actionIcon.isInvisible = true + } else { + holder.actionIcon.isVisible = true + val notifAction = NotificationAction.extractFrom(actions) + + if (notifAction.shouldNotify && !notifAction.soundName.isNullOrBlank()) { + holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_notify_noisy)) + } else if (notifAction.shouldNotify) { + holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_notify_silent)) + } else { + holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_dont_notify)) + } + + var description = StringBuffer() + pushRule.conditions?.forEachIndexed { i, condition -> + if (i > 0) description.append("\n") + description.append(condition.asExecutableCondition()?.technicalDescription() + ?: "UNSUPPORTED") + } + if (description.isBlank()) { + holder.description.text = "No Conditions" + } else { + holder.description.text = description + } + + } + } + + class Holder : VectorEpoxyHolder() { + val ruleId by bind(R.id.pushRuleId) + val description by bind(R.id.pushRuleDescription) + val actionIcon by bind(R.id.pushRuleActionIcon) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt new file mode 100644 index 0000000000..7f73f893a3 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt @@ -0,0 +1,80 @@ +/* + * 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 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 im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.core.ui.list.genericFooterItem +import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* + + +class PushRulesFragment : VectorBaseFragment() { + + override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy + + private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class) + + private val epoxyController by lazy { PushRulesFragment.PushRulesController(StringProvider(requireContext().resources)) } + + + override fun onResume() { + super.onResume() + (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_rules) + } + + 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 PushRulesController(private val stringProvider: StringProvider) : TypedEpoxyController() { + + override fun buildModels(data: PushRulesViewState?) { + data?.let { + it.rules.forEach { + pushRuleItem { + id(it.ruleId) + pushRule(it) + } + } + } ?: run { + genericFooterItem { + id("footer") + text(stringProvider.getString(R.string.settings_push_rules_no_rules)) + } + } + } + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt new file mode 100644 index 0000000000..05109d7e7f --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt @@ -0,0 +1,43 @@ +/* + * 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 com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import im.vector.matrix.android.api.pushrules.rest.PushRule +import im.vector.matrix.android.api.session.Session +import im.vector.riotredesign.core.platform.VectorViewModel +import org.koin.android.ext.android.get + +data class PushRulesViewState( + val rules: List = emptyList()) + : MvRxState + + +class PushRulesViewModel(initialState: PushRulesViewState) : VectorViewModel(initialState) { + + companion object : MvRxViewModelFactory { + + override fun initialState(viewModelContext: ViewModelContext): PushRulesViewState? { + val session = viewModelContext.activity.get() + val rules = session.getPushrules() + + return PushRulesViewState(rules) + } + + } +} \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_action_dont_notify.xml b/vector/src/main/res/drawable/ic_action_dont_notify.xml new file mode 100644 index 0000000000..b199ce0493 --- /dev/null +++ b/vector/src/main/res/drawable/ic_action_dont_notify.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/vector/src/main/res/drawable/ic_action_notify_noisy.xml b/vector/src/main/res/drawable/ic_action_notify_noisy.xml new file mode 100644 index 0000000000..fb07819881 --- /dev/null +++ b/vector/src/main/res/drawable/ic_action_notify_noisy.xml @@ -0,0 +1,18 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_action_notify_silent.xml b/vector/src/main/res/drawable/ic_action_notify_silent.xml new file mode 100644 index 0000000000..c2d325e1fd --- /dev/null +++ b/vector/src/main/res/drawable/ic_action_notify_silent.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/vector/src/main/res/layout/fragment_settings_pushgateways.xml b/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml similarity index 100% rename from vector/src/main/res/layout/fragment_settings_pushgateways.xml rename to vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml diff --git a/vector/src/main/res/layout/item_generic_footer.xml b/vector/src/main/res/layout/item_generic_footer.xml new file mode 100644 index 0000000000..c5c4e7fcd5 --- /dev/null +++ b/vector/src/main/res/layout/item_generic_footer.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/debug/res/layout/item_pushgateway.xml b/vector/src/main/res/layout/item_pushgateway.xml similarity index 100% rename from vector/src/debug/res/layout/item_pushgateway.xml rename to vector/src/main/res/layout/item_pushgateway.xml diff --git a/vector/src/main/res/layout/item_pushrule_raw.xml b/vector/src/main/res/layout/item_pushrule_raw.xml new file mode 100644 index 0000000000..0d2ba360bd --- /dev/null +++ b/vector/src/main/res/layout/item_pushrule_raw.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 7583463fe0..987afcd86d 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -17,6 +17,8 @@ Preferences Security & Privacy Expert - Push Gateways + Push Rules + No push rules defined + No registered push gateways \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml index 0c8900685a..4f110e8a46 100644 --- a/vector/src/main/res/xml/vector_settings_notifications.xml +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -2,14 +2,15 @@ - + + + + @@ -27,6 +28,7 @@ android:persistent="false" android:summary="@string/settings_notification_advanced_summary" android:title="@string/settings_notification_advanced" + android:enabled="false" app:fragment="im.vector.fragments.VectorSettingsNotificationsAdvancedFragment" /> + + \ No newline at end of file From 9ae9830de459edf4c7ecdd4fa78c7ae3b72af585 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 21 Jun 2019 18:32:17 +0200 Subject: [PATCH 04/26] Fix / compilation in gplay flavor --- .../push/fcm/VectorFirebaseMessagingService.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt index 7ef1973db3..c6da01a3b3 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt @@ -24,13 +24,11 @@ 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 @@ -42,7 +40,6 @@ 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. @@ -144,7 +141,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { Timber.i("Ignoring push, event already knwown") } else { Timber.v("Requesting background sync") - session.requireBackgroundSync(0L) + session.requireBackgroundSync() } } @@ -219,7 +216,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { return } else { - var notifiableEvent = notifiableEventResolver.resolveEvent(event, null /* TODO session.fulfillRule(event) */, session) + var notifiableEvent = notifiableEventResolver.resolveEvent(event, session) if (notifiableEvent == null) { Timber.e("Unsupported notifiable event ${eventId}") From 288ebe48fd5ca5476df570f463a9317939d19cdf Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 21 Jun 2019 18:38:05 +0200 Subject: [PATCH 05/26] Doc / quick fixes --- docs/notifications.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/notifications.md b/docs/notifications.md index 404f9aac94..5f68d07fe7 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -50,7 +50,7 @@ By default, this is 0, so the server will return immediately even if the respons **delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync. -When the Riot X Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0. +When the Riot X Android app is open (i.e in foreground state), the default timeout is 10 seconds, and delay is 0. ## How does a mobile app receives push notification @@ -132,7 +132,7 @@ A Home Server can be configured with default rules (for Direct messages, group m There are different kind of push rules, it can be per room (each new message on this room should be notified), it can also define a pattern that a message should match (when you are mentioned, or key word based). -Notifications have 2 'levels' (`highlighted = true/false`). In RiotX these notifications level are reflected as Noisy/Silent. +Notifications have 2 'levels' (`highlighted = true/false sound = default/custom`). In RiotX these notifications level are reflected as Noisy/Silent. **What about encrypted messages?** From 6743dc6273dc2a3d5f05e7a0a8f9ab9bb4834071 Mon Sep 17 00:00:00 2001 From: Valere Date: Sat, 22 Jun 2019 12:23:30 +0200 Subject: [PATCH 06/26] Set sync timeout to 30s when in foreground --- docs/notifications.md | 2 +- .../matrix/android/internal/session/sync/job/SyncThread.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/notifications.md b/docs/notifications.md index 5f68d07fe7..ad8a1c8430 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -50,7 +50,7 @@ By default, this is 0, so the server will return immediately even if the respons **delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync. -When the Riot X Android app is open (i.e in foreground state), the default timeout is 10 seconds, and delay is 0. +When the Riot X Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0. ## How does a mobile app receives push notification diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 68d01d344d..abb27a8a11 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -37,7 +37,7 @@ 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_TIMEOUT = 30_000L private const val DEFAULT_LONG_POOL_DELAY = 0L internal class SyncThread(private val syncTask: SyncTask, From 4e6b34b9d150a13cce1a7d14b902a9f69b85b9c5 Mon Sep 17 00:00:00 2001 From: Valere Date: Sat, 22 Jun 2019 17:08:45 +0200 Subject: [PATCH 07/26] Fix issues on Notification Event resolver --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 2 +- .../matrix/android/api/session/room/Room.kt | 4 +- .../room/model/call/CallInviteContent.kt | 2 + .../internal/session/room/DefaultRoom.kt | 11 +- .../api/pushrules/PushrulesConditionTest.kt | 5 +- .../fcm/VectorFirebaseMessagingService.kt | 62 ++++---- .../vector/riotredesign/core/di/AppModule.kt | 4 +- .../timeline/format/NoticeEventFormatter.kt | 16 ++ .../notifications/NotifiableEventResolver.kt | 150 +++++++++--------- .../NotificationDrawerManager.kt | 23 ++- .../notifications/OutdatedEventDetector.kt | 3 +- 11 files changed, 151 insertions(+), 131 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index c9dbbdd068..6c5724be29 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -23,7 +23,7 @@ import io.reactivex.Observable class RxRoom(private val room: Room) { fun liveRoomSummary(): Observable { - return room.roomSummary.asObservable() + return room.liveRoomSummary.asObservable() } fun liveRoomMemberIds(): Observable> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index fe6a5443e9..3e893a091d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -47,6 +47,8 @@ interface Room : * A live [RoomSummary] associated with the room * You can observe this summary to get dynamic data from this room. */ - val roomSummary: LiveData + val liveRoomSummary: LiveData + + val roomSummary: RoomSummary? } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/call/CallInviteContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/call/CallInviteContent.kt index b47e7ba291..2ec393a0cc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/call/CallInviteContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/call/CallInviteContent.kt @@ -39,4 +39,6 @@ data class CallInviteContent( } } + + fun isVideo(): Boolean = offer.sdp.contains(Offer.SDP_VIDEO) } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 5c37fa7d87..68f5f89255 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -52,7 +52,7 @@ internal class DefaultRoom( RelationService by relationService, MembershipService by roomMembersService { - override val roomSummary: LiveData by lazy { + override val liveRoomSummary: LiveData by lazy { val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) } @@ -68,6 +68,15 @@ internal class DefaultRoom( } } + override val roomSummary: RoomSummary? + get() { + var sum: RoomSummaryEntity? = null + monarchy.runTransactionSync { + sum = RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() + } + return sum?.asDomain() + } + override fun isEncrypted(): Boolean { return cryptoService.isRoomEncrypted(roomId) } diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index d9b81917b2..11c2be1db9 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -175,7 +175,10 @@ class PushrulesConditionTest { return _numberOfJoinedMembers } - override val roomSummary: LiveData + override val liveRoomSummary: LiveData + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + + override val roomSummary: RoomSummary? get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. override fun createTimeline(eventId: String?, allowedTypes: List?): Timeline { diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt index c6da01a3b3..14fed48dbd 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt @@ -49,10 +49,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { private val notificationDrawerManager by inject() private val pusherManager by inject() - private val notifiableEventResolver by lazy { - NotifiableEventResolver(this) - } - + private val notifiableEventResolver by inject() // UI handler private val mUIHandler by lazy { Handler(Looper.getMainLooper()) @@ -69,8 +66,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { return } if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.i("## onMessageReceived()" + message.data.toString()) - Timber.i("## onMessageReceived() from FCM with priority " + message.priority) + Timber.i("## onMessageReceived() %s", message.data.toString()) + Timber.i("## onMessageReceived() from FCM with priority %s", message.priority) } mUIHandler.post { if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { @@ -168,7 +165,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { 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) + Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined") } } @@ -209,44 +206,39 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { return } else { - val event = parseEvent(data) - if (event?.roomId == null) { - //unsupported event - Timber.e("Received an event with no room id") - return + val event = parseEvent(data) ?: return + + val notifiableEvent = notifiableEventResolver.resolveEvent(event, session) + + if (notifiableEvent == null) { + Timber.e("Unsupported notifiable event ${eventId}") + if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { + Timber.e("--> ${event}") + } } else { - var notifiableEvent = notifiableEventResolver.resolveEvent(event, session) - if (notifiableEvent == null) { - Timber.e("Unsupported notifiable event ${eventId}") - if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.e("--> ${event}") + if (notifiableEvent is NotifiableMessageEvent) { + if (TextUtils.isEmpty(notifiableEvent.senderName)) { + notifiableEvent.senderName = data["sender_display_name"] + ?: data["sender"] ?: "" } - } else { - - - if (notifiableEvent is NotifiableMessageEvent) { - if (TextUtils.isEmpty(notifiableEvent.senderName)) { - notifiableEvent.senderName = data["sender_display_name"] - ?: data["sender"] ?: "" - } - if (TextUtils.isEmpty(notifiableEvent.roomName)) { - notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: "" - } + if (TextUtils.isEmpty(notifiableEvent.roomName)) { + notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: "" } - - notifiableEvent.isPushGatewayEvent = true - notifiableEvent.matrixID = session.sessionParams.credentials.userId - notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) - notificationDrawerManager.refreshNotificationDrawer() } + + notifiableEvent.isPushGatewayEvent = true + notifiableEvent.matrixID = session.sessionParams.credentials.userId + notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) + notificationDrawerManager.refreshNotificationDrawer() } } + } private fun findRoomNameBestEffort(data: Map, session: Session?): String? { - var roomName: String? = data["room_name"] + val roomName: String? = data["room_name"] val roomId = data["room_id"] if (null == roomName && null != roomId) { // Try to get the room name from our store @@ -281,7 +273,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { // TODO content = data.getValue("content"), originServerTs = System.currentTimeMillis()) } catch (e: Exception) { - Timber.e(e, "buildEvent fails " + e.localizedMessage) + Timber.e(e, "buildEvent fails ") } return null diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index f26d2c3eb1..85f0d3a584 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -90,7 +90,7 @@ class AppModule(private val context: Context) { } single { - OutdatedEventDetector(context) + OutdatedEventDetector() } single { @@ -98,7 +98,7 @@ class AppModule(private val context: Context) { } single { - NotifiableEventResolver(context) + NotifiableEventResolver(get(), get()) } factory { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 595fa766c3..cf2dc4786b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -46,6 +46,22 @@ class NoticeEventFormatter(private val stringProvider: StringProvider) { } } + fun format(event: Event, senderName: String?): CharSequence? { + return when (val type = event.getClearType()) { + EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName) + EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName) + EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName) + EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName) + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER -> formatCallEvent(event, senderName) + else -> { + Timber.v("Type $type not handled by this formatter") + null + } + } + } + private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return if (!TextUtils.isEmpty(content.name)) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt index 1854ccd098..650a62f8e1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt @@ -15,16 +15,19 @@ */ package im.vector.riotredesign.features.notifications -import android.content.Context -import im.vector.matrix.android.api.pushrules.rest.PushRule +import androidx.core.app.NotificationCompat 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.Membership +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.R -import im.vector.riotredesign.core.preference.BingRule +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import timber.log.Timber // TODO Remove @@ -39,46 +42,44 @@ class RoomState { * The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that, * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. */ -class NotifiableEventResolver(val context: Context) { +class NotifiableEventResolver(val stringProvider: StringProvider, + val noticeEventFormatter: NoticeEventFormatter) { //private val eventDisplay = RiotEventDisplay(context) 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 -// } + val roomID = event.roomId ?: return null + val eventId = event.eventId ?: return null + val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null when (event.getClearType()) { EventType.MESSAGE -> { - return resolveMessageEvent(event, session) + return resolveMessageEvent(timelineEvent, 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 -> { + EventType.ENCRYPTED -> { + val messageEvent = resolveMessageEvent(timelineEvent, session) + messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE + return messageEvent + } + EventType.STATE_ROOM_MEMBER -> { + return resolveStateRoomEvent(event, session) + } + 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.type) -// } + //TODO Better event text display + val bodyPreview = event.type + + return SimpleNotifiableEvent( + session.sessionParams.credentials.userId, + eventId = event.eventId!!, + noisy = false,//will be updated + timestamp = event.originServerTs ?: System.currentTimeMillis(), + description = bodyPreview, + title = stringProvider.getString(R.string.notification_unknown_new_event), + soundName = null, + type = event.type) + //Unsupported event Timber.w("NotifiableEventResolver Received an unsupported event matching a bing rule") @@ -88,57 +89,55 @@ class NotifiableEventResolver(val context: Context) { } - private fun resolveMessageEvent(event: Event, 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 = pushRule?.notificationSound + private fun resolveMessageEvent(event: TimelineEvent, session: Session): NotifiableEvent? { //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 = session.getRoom(event.roomId!! /*roomID cannot be null (see Matrix SDK code)*/) + val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/) if (room == null) { - Timber.e("## Unable to resolve room for eventId [${event.eventId}] and roomID [${event.roomId}]") + Timber.e("## Unable to resolve room for eventId [${event}]") // Ok room is not known in store, but we can still display something - val body = event.content?.toModel()?.body - ?: context.getString(R.string.notification_unknown_new_event) - val roomName = context.getString(R.string.notification_unknown_room_name) - val senderDisplayName = event.senderId ?: "" + val body = + event.annotations?.editSummary?.aggregatedContent?.toModel()?.body + ?: event.root.getClearContent().toModel()?.body + ?: stringProvider.getString(R.string.notification_unknown_new_event) + val roomName = stringProvider.getString(R.string.notification_unknown_room_name) + val senderDisplayName = event.senderName val notifiableEvent = NotifiableMessageEvent( - eventId = event.eventId ?: "", - timestamp = event.originServerTs ?: 0, + eventId = event.root.eventId!!, + timestamp = event.root.originServerTs ?: 0, noisy = false,//will be updated senderName = senderDisplayName, - senderId = event.senderId, + senderId = event.root.senderId, body = body, - roomId = event.roomId ?: "", + roomId = event.root.roomId!!, roomName = roomName) notifiableEvent.matrixID = session.sessionParams.credentials.userId -// notifiableEvent.soundName = soundName - return notifiableEvent } else { - val tEvent = room.getTimeLineEvent(event.eventId!!) - val body = event.content?.toModel()?.body - ?: context.getString(R.string.notification_unknown_new_event) - val roomName = event.roomId ?: "room" - val senderDisplayName = tEvent?.senderName ?: "?" + val body = event.root.getClearContent().toModel()?.body + ?: stringProvider.getString(R.string.notification_unknown_new_event) + val roomName = room.roomSummary?.displayName ?: "" + val senderDisplayName = event.senderName ?: "" val notifiableEvent = NotifiableMessageEvent( - eventId = event.eventId!!, - timestamp = event.originServerTs ?: 0, + eventId = event.root.eventId!!, + timestamp = event.root.originServerTs ?: 0, noisy = false,//will be updated senderName = senderDisplayName, - senderId = event.senderId, + senderId = event.root.senderId, body = body, - roomId = event.roomId ?: "00", + roomId = event.root.roomId!!, roomName = roomName, - roomIsDirect = true) + roomIsDirect = room.roomSummary?.isDirect ?: false) notifiableEvent.matrixID = session.sessionParams.credentials.userId notifiableEvent.soundName = null + //TODO get the avatar? // val roomAvatarPath = session.mediaCache?.thumbnailCacheFile(room.avatarUrl, 50) // if (roomAvatarPath != null) { @@ -162,21 +161,24 @@ class NotifiableEventResolver(val context: Context) { 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)*/) - val body = eventDisplay.getTextualDisplay(event, room.state)?.toString() - ?: context.getString(R.string.notification_new_invitation) + + + private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? { + val content = event.content?.toModel() ?: return null + val roomId = event.roomId ?: return null + val dName = event.senderId?.let { session.getUser(it)?.displayName } + if (Membership.INVITE == content.membership) { + val body = noticeEventFormatter.format(event, dName) + ?: stringProvider.getString(R.string.notification_new_invitation) return InviteNotifiableEvent( - session.myUserId, - eventId = event.eventId, - roomId = event.roomId, - timestamp = event.originServerTs, - noisy = bingRule?.notificationSound != null, - title = context.getString(R.string.notification_new_invitation), - description = body, - soundName = bingRule?.notificationSound, + session.sessionParams.credentials.userId, + eventId = event.eventId!!, + roomId = roomId, + timestamp = event.originServerTs ?: 0, + noisy = false,//will be set later + title = stringProvider.getString(R.string.notification_new_invitation), + description = body.toString(), + soundName = null, //will be set later type = event.getClearType(), isPushGatewayEvent = false) } else { @@ -187,6 +189,6 @@ class NotifiableEventResolver(val context: Context) { //TODO generic handling? } return null - } */ + } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index 5bf7caa7d0..d1ff76f77a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -22,7 +22,8 @@ import android.graphics.BitmapFactory import android.text.TextUtils import androidx.core.app.NotificationCompat import androidx.core.app.Person -import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.R import im.vector.riotredesign.core.utils.SecretStoringUtils @@ -36,14 +37,14 @@ import java.io.FileOutputStream * organise them in order to display them in the notification drawer. * Events can be grouped into the same notification, old (already read) events can be removed to do some cleaning. */ -class NotificationDrawerManager(val context: Context, private val outdatedDetector: OutdatedEventDetector?) { +class NotificationDrawerManager(val context: Context, + private val outdatedDetector: OutdatedEventDetector?) { + //The first time the notification drawer is refreshed, we force re-render of all notifications private var firstTime = true private var eventList = loadEventInfo() - private var myUserDisplayName: String = "Todo" - private var myUserAvatarUrl: String = "" private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size) @@ -156,17 +157,11 @@ class NotificationDrawerManager(val context: Context, private val outdatedDetect fun refreshNotificationDrawer() { - if (myUserDisplayName.isBlank()) { - // TODO - // initWithSession(Matrix.getInstance(context).defaultSession) - } - - if (myUserDisplayName.isBlank()) { - // Should not happen, but myUserDisplayName cannot be blank if used to create a Person - //TODO -// return - } + val session = Matrix.getInstance().currentSession ?: return + val user = session.getUser(session.sessionParams.credentials.userId) + val myUserDisplayName = user?.displayName ?: "" + val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE) synchronized(eventList) { Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER ") diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt index 4ebbe68651..067ab9e77c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt @@ -15,10 +15,9 @@ */ package im.vector.riotredesign.features.notifications -import android.content.Context import im.vector.matrix.android.api.Matrix -class OutdatedEventDetector(val context: Context) { +class OutdatedEventDetector() { /** * Returns true if the given event is outdated. From 74099be31689888a9d1e6a7e911f7e85c533bb69 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 Jun 2019 09:57:40 +0200 Subject: [PATCH 08/26] Remove / Add pusher from enable notif preference +Added Retrofit/Moshi null serializer for pusher kind --- .../android/api/session/pushers/Pusher.kt | 7 ++- .../api/session/pushers/PushersService.kt | 4 ++ .../internal/database/mapper/PushersMapper.kt | 4 +- .../internal/database/model/PusherEntity.kt | 8 +-- .../android/internal/di/MoshiProvider.kt | 3 + .../android/internal/di/SerializeNulls.kt | 23 +++++++ .../internal/session/DefaultSession.kt | 4 ++ .../android/internal/session/SessionModule.kt | 18 +++--- .../session/notification/BingRuleWatcher.kt | 37 +++--------- .../notification/ProcessEventForPushTask.kt | 51 ++++++++++++++++ .../session/pushers/AddHttpPusherWorker.kt | 6 +- .../session/pushers/DefaultPusherService.kt | 10 ++++ .../internal/session/pushers/JsonPusher.kt | 11 ++-- .../internal/session/pushers/PushersAPI.kt | 2 +- .../session/pushers/RemovePusherTask.kt | 60 +++++++++++++++++++ .../core/pushers/PushersManager.kt | 4 ++ .../features/settings/PreferencesManager.java | 4 ++ .../settings/VectorSettingsNotification.kt | 17 ------ .../VectorSettingsNotificationFragment.kt | 56 +++++++++++++++++ .../res/xml/vector_settings_notifications.xml | 3 +- 20 files changed, 259 insertions(+), 73 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/SerializeNulls.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt index f0f9453f80..eb71b954bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt @@ -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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt index c28f831373..21b2464dc2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt @@ -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) + companion object { const val EVENT_ID_ONLY = "event_id_only" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt index c1df3b2e29..8cac50a468 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt @@ -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) ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt index 117aea5725..553a3e7273 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index acd3353b93..3849a2f749 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -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 { } } + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/SerializeNulls.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/SerializeNulls.kt new file mode 100644 index 0000000000..b758a4de92 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/SerializeNulls.kt @@ -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, moshi: Moshi): JsonAdapter<*>? { + val nextAnnotations = Types.nextAnnotations(annotations, SerializeNulls::class.java) + ?: return null + return moshi.nextAdapter(this, type, nextAnnotations).serializeNulls() + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 4931809694..bdc365f466 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -495,6 +495,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi ) } + override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback) { + pushersService.removeHttpPusher(pushkey, appId, callback) + } + override fun livePushers(): LiveData> { return pushersService.livePushers() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 97b7bc376f..b517fade9a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -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().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 } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt index 0c12b3e5bc..af5689cf5f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -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(monarchy) { override val query = Monarchy.Query { @@ -40,34 +40,11 @@ internal class BingRuleWatcher(monarchy: Monarchy, } override fun processChanges(inserted: List, updated: List, deleted: List) { - //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? { - 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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt new file mode 100644 index 0000000000..36b7f7206f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt @@ -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 { + data class Params( + val events: List, + val rules: List + ) +} + +internal class DefaultProcessEventForPushTask( + private val defaultPushRuleService: DefaultPushRuleService +) : ProcessEventForPushTask { + + + override suspend fun execute(params: ProcessEventForPushTask.Params): Try { + 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? { + 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 + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt index 0eb6344f12..1a3577ee92 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt @@ -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> { + val result = executeRequest { 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 { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt index 40b7fe42b2..40ddb1eb3b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -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) { + val params = RemovePusherTask.Params(sessionParam.credentials.userId,pushkey,appId) + removePusherTask + .configureWith(params) + .dispatchTo(callback) + //.enableRetry() ?? + .executeBy(taskExecutor) + } + override fun livePushers(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> PusherEntity.where(realm, sessionParam.credentials.userId) }, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt index 7616be492a..e16bd78ecc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt index 972515130b..632b4ed750 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt @@ -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> + fun setPusher(@Body jsonPusher: JsonPusher): Call } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt new file mode 100644 index 0000000000..edd2fdaeb3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt @@ -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 { + 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 { + 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 { + 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() + } + } + } + + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt index 7bf5a4a2c0..1182efb104 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt @@ -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) { + currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id),callback) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java b/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java index 2d0000c2c0..c57e0fa2fb 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java @@ -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. * diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt deleted file mode 100644 index d1ce1e77dc..0000000000 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotification.kt +++ /dev/null @@ -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) - } - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt new file mode 100644 index 0000000000..de2aa10b41 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt @@ -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 { + override fun onSuccess(data: Unit) { + super.onSuccess(data) + } + + override fun onFailure(failure: Throwable) { + super.onFailure(failure) + } + }) + } + } + } + return super.onPreferenceTreeClick(preference) + } +} \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml index 4f110e8a46..ed20daf6ac 100644 --- a/vector/src/main/res/xml/vector_settings_notifications.xml +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -7,8 +7,8 @@ @@ -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" From c01af6ac78f9e9263eee7a84cc2e93cc5770f1fe Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 Jun 2019 10:04:03 +0200 Subject: [PATCH 09/26] Stop notification using preference when disabled --- .../push/fcm/VectorFirebaseMessagingService.kt | 10 +++++++++- .../java/im/vector/riotredesign/VectorApplication.kt | 10 ++++++---- .../notifications/NotificationDrawerManager.kt | 5 +++++ .../settings/VectorSettingsNotificationFragment.kt | 4 +++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt index 14fed48dbd..0dacb9399c 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt @@ -38,6 +38,7 @@ import im.vector.riotredesign.features.notifications.NotifiableEventResolver import im.vector.riotredesign.features.notifications.NotifiableMessageEvent import im.vector.riotredesign.features.notifications.NotificationDrawerManager import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent +import im.vector.riotredesign.features.settings.PreferencesManager import org.koin.android.ext.android.inject import timber.log.Timber @@ -61,6 +62,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * @param message the message */ override fun onMessageReceived(message: RemoteMessage?) { + if (!PreferencesManager.areNotificationEnabledForDevice(applicationContext)) { + Timber.i("Notification are disabled for this device") + return + } + if (message == null || message.data == null) { Timber.e("## onMessageReceived() : received a null message or message with no data") return @@ -91,7 +97,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { if (refreshedToken == null) { Timber.w("onNewToken:received null token") } else { - pusherManager.registerPusherWithFcmKey(refreshedToken) + if (PreferencesManager.areNotificationEnabledForDevice(applicationContext)) { + pusherManager.registerPusherWithFcmKey(refreshedToken) + } } } diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index 93e34a782f..6f8127536d 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -54,6 +54,7 @@ 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.settings.PreferencesManager import im.vector.riotredesign.features.version.getVersion import im.vector.riotredesign.push.fcm.FcmHelper import org.koin.android.ext.android.get @@ -137,11 +138,12 @@ class VectorApplication : Application() { } else { //TODO check if notifications are enabled for this device //We need to use alarm in this mode - if (Matrix.getInstance().currentSession != null) { - AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext, 4_000L) - Timber.i("Alarm scheduled to restart service") + if (PreferencesManager.areNotificationEnabledForDevice(applicationContext)) { + if (Matrix.getInstance().currentSession != null) { + AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext, 4_000L) + Timber.i("Alarm scheduled to restart service") + } } - } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index d1ff76f77a..3e0875b025 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.R import im.vector.riotredesign.core.utils.SecretStoringUtils +import im.vector.riotredesign.features.settings.PreferencesManager import timber.log.Timber import java.io.File import java.io.FileInputStream @@ -65,6 +66,10 @@ class NotificationDrawerManager(val context: Context, Events might be grouped and there might not be one notification per event! */ fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) { + if (!PreferencesManager.areNotificationEnabledForDevice(context)) { + Timber.i("Notification are disabled for this device") + return + } //If we support multi session, event list should be per userId //Currently only manage single session if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt index de2aa10b41..6cff9f2d6c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt @@ -35,7 +35,9 @@ class VectorSettingsNotificationPreferenceFragment : VectorPreferenceFragment() val switchPref = preference as SwitchPreference if (switchPref.isChecked) { FcmHelper.getFcmToken(requireContext())?.let { - pushManager.registerPusherWithFcmKey(it) + if (PreferencesManager.areNotificationEnabledForDevice(requireContext())) { + pushManager.registerPusherWithFcmKey(it) + } } } else { FcmHelper.getFcmToken(requireContext())?.let { From 7821ca12fdb599422a4c43aa16e4f6451f129834 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 Jun 2019 10:12:11 +0200 Subject: [PATCH 10/26] Fix / fetch from realm without copy (proxy error) --- .../internal/session/notification/DefaultPushRuleService.kt | 3 ++- .../matrix/android/internal/session/room/DefaultRoom.kt | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt index b2a6b9b13f..eb828de233 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith +import timber.log.Timber internal class DefaultPushRuleService( @@ -170,7 +171,7 @@ internal class DefaultPushRuleService( it.onMatchRule(event, Action.mapFrom(rule) ?: emptyList()) } } catch (e: Throwable) { - + Timber.e(e, "Error while dispatching bing") } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 68f5f89255..3715e37007 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -33,6 +33,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.util.fetchCopied internal class DefaultRoom( override val roomId: String, @@ -70,10 +71,7 @@ internal class DefaultRoom( override val roomSummary: RoomSummary? get() { - var sum: RoomSummaryEntity? = null - monarchy.runTransactionSync { - sum = RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() - } + var sum: RoomSummaryEntity? = monarchy.fetchCopied { RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() } return sum?.asDomain() } From 090ee1d4e927be6a3bc85798499b343398cf1ea0 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 Jun 2019 14:35:12 +0200 Subject: [PATCH 11/26] Fix / ignore message sent by me in push rules --- .../matrix/android/internal/session/SessionModule.kt | 2 +- .../internal/session/notification/BingRuleWatcher.kt | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index b517fade9a..a3a08976e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -192,7 +192,7 @@ internal class SessionModule(private val sessionParams: SessionParams) { } scope(DefaultSession.SCOPE) { - BingRuleWatcher(get(), get(), get(), get()) + BingRuleWatcher(get(), get(), get(), get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt index af5689cf5f..b584fd8e5d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.session.notification import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.auth.data.SessionParams 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 @@ -28,6 +29,7 @@ import im.vector.matrix.android.internal.task.configureWith internal class BingRuleWatcher(monarchy: Monarchy, private val task: ProcessEventForPushTask, private val defaultPushRuleService: DefaultPushRuleService, + private val sessionParams: SessionParams, private val taskExecutor: TaskExecutor) : RealmLiveEntityObserver(monarchy) { @@ -41,10 +43,12 @@ internal class BingRuleWatcher(monarchy: Monarchy, override fun processChanges(inserted: List, updated: List, deleted: List) { val rules = defaultPushRuleService.getPushrules("global") - inserted.map { it.asDomain() }.let { events -> - task.configureWith(ProcessEventForPushTask.Params(events, rules)) - .executeBy(taskExecutor) - } + inserted.map { it.asDomain() } + .filter { it.senderId != sessionParams.credentials.userId } + .let { events -> + task.configureWith(ProcessEventForPushTask.Params(events, rules)) + .executeBy(taskExecutor) + } } From 1feb1f9c3f2daf0fb4427ed63bb8edd3f2bb9323 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 Jun 2019 14:35:24 +0200 Subject: [PATCH 12/26] Fix test --- .../api/pushrules/PushRuleActionsTest.kt | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt index eeef3f5c66..eb337ab3e1 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt @@ -32,7 +32,7 @@ class PushRuleActionsTest { "pattern": "[the user's Matrix ID]" } ], - "domainActions": [ + "actions": [ "notify", { "set_tweak": "sound", @@ -50,23 +50,25 @@ class PushRuleActionsTest { val pushRule = MoshiProvider.providesMoshi().adapter(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.assertNotNull("Failed to parse domainActions", Action.mapFrom(pushRule!!)) + + val actions = Action.mapFrom(pushRule) + Assert.assertEquals(3, actions!!.size) - Assert.assertEquals("First action should be notify", Action.Type.NOTIFY, pushRule!!.domainActions()!![0].type) + Assert.assertEquals("First action should be notify", Action.Type.NOTIFY, actions[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("Second action should be tweak", Action.Type.SET_TWEAK, actions[1].type) + Assert.assertEquals("Second action tweak key should be sound", "sound", actions[1].tweak_action) + Assert.assertEquals("Second action should have default as stringValue", "default", actions[1].stringValue) + Assert.assertNull("Second action boolValue should be null", actions[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) + Assert.assertEquals("Third action should be tweak", Action.Type.SET_TWEAK, actions[2].type) + Assert.assertEquals("Third action tweak key should be highlight", "highlight", actions[2].tweak_action) + Assert.assertEquals("Third action tweak param should be false", false, actions[2].boolValue) + Assert.assertNull("Third action stringValue should be null", actions[2].stringValue) } } \ No newline at end of file From abb1c3f3c425b07010be5453f5bdcfbfc4b29297 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 Jun 2019 14:35:43 +0200 Subject: [PATCH 13/26] Fix / try to get edited message content first --- .../features/notifications/NotifiableEventResolver.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt index 650a62f8e1..7ef7a720c4 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt @@ -118,7 +118,8 @@ class NotifiableEventResolver(val stringProvider: StringProvider, notifiableEvent.matrixID = session.sessionParams.credentials.userId return notifiableEvent } else { - val body = event.root.getClearContent().toModel()?.body + val body = event.annotations?.editSummary?.aggregatedContent?.toModel()?.body + ?: event.root.getClearContent().toModel()?.body ?: stringProvider.getString(R.string.notification_unknown_new_event) val roomName = room.roomSummary?.displayName ?: "" val senderDisplayName = event.senderName ?: "" From ab0141a5c6771b8f4c2d40514196e184e422a160 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 12:10:34 +0200 Subject: [PATCH 14/26] Fix issue after rebase --- .../api/pushrules/SenderNotificationPermissionCondition.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt index 3a67ff933c..5f553f7828 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt @@ -31,6 +31,6 @@ class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.se fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean { - return event.sender != null && powerLevels.getUserPowerLevel(event.sender) >= powerLevels.notificationLevel(key) + return event.senderId != null && powerLevels.getUserPowerLevel(event.senderId) >= powerLevels.notificationLevel(key) } } \ No newline at end of file From 71ae99012bfef67190834ed182793a4621d26a23 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 14:45:13 +0200 Subject: [PATCH 15/26] Code review and cleanup --- .../matrix/android/api/pushrules/PushExt.kt | 2 - ...lesResponse.kt => GetPushRulesResponse.kt} | 15 ++-- .../api/pushrules/rest/PushCondition.kt | 25 +++++-- .../android/api/pushrules/rest/PushRule.kt | 24 +++++-- .../android/api/session/pushers/Pusher.kt | 2 +- .../internal/database/model/PushRuleEntity.kt | 2 +- .../internal/database/model/PusherEntity.kt | 4 +- .../internal/database/query/PushersQueries.kt | 21 +++--- .../android/internal/session/SessionModule.kt | 4 +- .../notification/DefaultPushRuleService.kt | 39 ++++------- .../session/pushers/DefaultPusherService.kt | 7 +- ...shersResponse.kt => GetPushersResponse.kt} | 5 +- .../session/pushers/GetPushersTask.kt | 4 +- .../session/pushers/GetPushrulesTask.kt | 10 +-- .../internal/session/pushers/JsonPusher.kt | 68 ++++++++++++++----- .../session/pushers/JsonPusherData.kt | 15 ++-- .../{PushrulesApi.kt => PushRulesApi.kt} | 29 +++++--- .../internal/session/pushers/PushersAPI.kt | 6 +- .../session/pushers/RemovePusherTask.kt | 11 +-- .../android/internal/session/sync/SyncTask.kt | 2 +- .../api/pushrules/PushRuleActionsTest.kt | 2 +- .../vector/riotredesign/push/fcm/FcmHelper.kt | 3 +- .../features/home/HomeActivity.kt | 2 +- .../home/room/detail/RoomDetailViewState.kt | 2 +- .../detail/timeline/action/ActionsHandler.kt | 2 +- .../action/MessageActionsBottomSheet.kt | 4 +- .../timeline/action/MessageMenuViewModel.kt | 2 +- .../notifications/NotifiableEventResolver.kt | 6 -- .../NotificationBroadcastReceiver.kt | 2 +- .../notifications/NotificationUtils.kt | 4 +- .../notifications/OutdatedEventDetector.kt | 2 +- .../notifications/PushRuleTriggerListener.kt | 16 +++++ .../VectorSettingsNotificationFragment.kt | 20 +++++- .../VectorSettingsPreferencesFragmentV2.kt | 16 +++++ .../features/settings/push/PushGatewayItem.kt | 17 +++++ .../settings/push/PushGatewaysFragment.kt | 9 +-- .../settings/push/PushGatewaysViewModel.kt | 5 +- .../features/settings/push/PushRuleItem.kt | 3 +- .../settings/push/PushRulesFragment.kt | 10 +-- .../settings/push/PushRulesViewModel.kt | 4 +- .../fragment_generic_recycler_epoxy.xml | 15 ++-- 41 files changed, 283 insertions(+), 158 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/{PushrulesResponse.kt => GetPushRulesResponse.kt} (73%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/{PushersResponse.kt => GetPushersResponse.kt} (86%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/{PushrulesApi.kt => PushRulesApi.kt} (69%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt deleted file mode 100644 index fd9a95bea2..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushExt.kt +++ /dev/null @@ -1,2 +0,0 @@ -package im.vector.matrix.android.api.pushrules - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/GetPushRulesResponse.kt similarity index 73% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/GetPushRulesResponse.kt index 75ac3aee9d..8d567f9464 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushrulesResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/GetPushRulesResponse.kt @@ -15,16 +15,23 @@ */ 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.rest.Ruleset /** * All push rulesets for a user. */ @JsonClass(generateAdapter = true) -data class PushrulesResponse( - //Global rules, account level applying to all devices +data class GetPushRulesResponse( + /** + * Global rules, account level applying to all devices + */ + @Json(name = "global") val global: Ruleset, - //Device specific rules, apply only to current device + + /** + * Device specific rules, apply only to current device + */ + @Json(name = "device") val device: Ruleset? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt index fac0fc5182..8e63e8640d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt @@ -22,15 +22,26 @@ import timber.log.Timber @JsonClass(generateAdapter = true) data class PushCondition( - //Required. The kind of condition to apply. + /** + * Required. The kind of condition to apply. + */ val kind: String, - //Required for event_match conditions. The dot- separated field of the event to match. + + /** + * Required for event_match conditions. The dot- separated field of the event to match. + */ + val key: String? = null, - //Required for event_match conditions. + /** + *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 ==. + /** + * 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 ) { @@ -58,7 +69,7 @@ data class PushCondition( Condition.Kind.sender_notification_permission -> { this.key?.let { SenderNotificationPermissionCondition(it) } } - Condition.Kind.UNRECOGNIZE -> { + Condition.Kind.UNRECOGNIZE -> { Timber.e("Unknwon kind $kind") null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt index 2e090a2487..1e36a0d867 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.kt @@ -22,17 +22,29 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class PushRule( - //Required. The domainActions to perform when this rule is matched. + /** + * Required. The actions to perform when this rule is matched. + */ val actions: List, - //Required. Whether this is a default rule, or has been set explicitly. + /** + * Required. Whether this is a default rule, or has been set explicitly. + */ val default: Boolean? = false, - //Required. Whether the push rule is enabled or not. + /** + * Required. Whether the push rule is enabled or not. + */ val enabled: Boolean, - //Required. The ID of this rule. + /** + * 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 + /** + * The conditions that must hold true for an event in order for a rule to be applied to an event + */ val conditions: List? = null, - //The glob-style pattern to match against. Only applicable to content rules. + /** + * The glob-style pattern to match against. Only applicable to content rules. + */ val pattern: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt index eb71b954bd..7b1fa3bb52 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt @@ -32,7 +32,7 @@ data class Pusher( ) enum class PusherState { - UNREGISTRED, + UNREGISTERED, REGISTERING, UNREGISTERING, REGISTERED, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt index 30ef90a817..dd6cc79022 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRuleEntity.kt @@ -19,7 +19,7 @@ import io.realm.RealmList import io.realm.RealmObject internal open class PushRuleEntity( - //Required. The domainActions to perform when this rule is matched. + //Required. The actions 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, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt index 553a3e7273..6e567c0a2d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherEntity.kt @@ -38,7 +38,7 @@ internal open class PusherEntity( var lang: String? = null, var data: PusherDataEntity? = null ) : RealmObject() { - private var stateStr: String = PusherState.UNREGISTRED.name + private var stateStr: String = PusherState.UNREGISTERED.name var state: PusherState get() { @@ -46,7 +46,7 @@ internal open class PusherEntity( return PusherState.valueOf(stateStr) } catch (e: Exception) { //can this happen? - return PusherState.UNREGISTRED + return PusherState.UNREGISTERED } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt index ff91665f14..b7f54f17ea 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt @@ -15,15 +15,17 @@ */ package im.vector.matrix.android.internal.database.query -import im.vector.matrix.android.api.pushrules.rest.PushRule -import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.PushRulesEntity +import im.vector.matrix.android.internal.database.model.PushRulesEntityFields 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 { +internal fun PusherEntity.Companion.where(realm: Realm, + userId: String, + pushKey: String? = null): RealmQuery { return realm.where() .equalTo(PusherEntityFields.USER_ID, userId) .apply { @@ -33,9 +35,12 @@ internal fun PusherEntity.Companion.where(realm: Realm, userId: String, pushKey: } } -internal fun PushRulesEntity.Companion.where(realm: Realm, userId: String, scope: String, rulesetKey: String) : RealmQuery { - return realm.where() - .equalTo(PushRulesEntityFields.USER_ID,userId) - .equalTo(PushRulesEntityFields.SCOPE,scope) - .equalTo(PushRulesEntityFields.RULESET_KEY,rulesetKey) +internal fun PushRulesEntity.Companion.where(realm: Realm, + userId: String, + scope: String, + ruleSetKey: String): RealmQuery { + return realm.where() + .equalTo(PushRulesEntityFields.USER_ID, userId) + .equalTo(PushRulesEntityFields.SCOPE, scope) + .equalTo(PushRulesEntityFields.RULESET_KEY, ruleSetKey) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index a3a08976e1..ef2fe85791 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -172,7 +172,7 @@ internal class SessionModule(private val sessionParams: SessionParams) { scope(DefaultSession.SCOPE) { val retrofit: Retrofit = get() - retrofit.create(PushrulesApi::class.java) + retrofit.create(PushRulesApi::class.java) } scope(DefaultSession.SCOPE) { @@ -184,7 +184,7 @@ internal class SessionModule(private val sessionParams: SessionParams) { } scope(DefaultSession.SCOPE) { - DefaultGetPushrulesTask(get()) as GetPushRulesTask + DefaultGetPushRulesTask(get()) as GetPushRulesTask } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt index eb828de233..afaab4862f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -20,8 +20,8 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.pushrules.Action import im.vector.matrix.android.api.pushrules.PushRuleService +import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.api.pushrules.rest.PushRule -import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.database.mapper.PushRulesMapper import im.vector.matrix.android.internal.database.model.PushRulesEntity @@ -47,8 +47,8 @@ internal class DefaultPushRuleService( override fun fetchPushRules(scope: String) { pushRulesTask .configureWith(Unit) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: PushrulesResponse) { + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: GetPushRulesResponse) { monarchy.runTransactionSync { realm -> //clear existings? //TODO @@ -56,7 +56,7 @@ internal class DefaultPushRuleService( .equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId) .findAll().deleteAllFromRealm() - var content = PushRulesEntity(sessionParams.credentials.userId, scope, "content") + val content = PushRulesEntity(sessionParams.credentials.userId, scope, "content") data.global.content?.forEach { rule -> PushRulesMapper.map(rule).also { content.pushRules.add(it) @@ -64,7 +64,7 @@ internal class DefaultPushRuleService( } realm.insertOrUpdate(content) - var override = PushRulesEntity(sessionParams.credentials.userId, scope, "override") + val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override") data.global.override?.forEach { rule -> PushRulesMapper.map(rule).also { override.pushRules.add(it) @@ -72,7 +72,7 @@ internal class DefaultPushRuleService( } realm.insertOrUpdate(override) - var rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room") + val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room") data.global.room?.forEach { rule -> PushRulesMapper.map(rule).also { rooms.pushRules.add(it) @@ -80,7 +80,7 @@ internal class DefaultPushRuleService( } realm.insertOrUpdate(rooms) - var senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender") + val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender") data.global.sender?.forEach { rule -> PushRulesMapper.map(rule).also { senders.pushRules.add(it) @@ -88,7 +88,7 @@ internal class DefaultPushRuleService( } realm.insertOrUpdate(senders) - var underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride") + val underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride") data.global.underride?.forEach { rule -> PushRulesMapper.map(rule).also { underrides.pushRules.add(it) @@ -106,35 +106,24 @@ internal class DefaultPushRuleService( override fun getPushrules(scope: String): List { var contentRules: List = emptyList() + var overrideRules: List = emptyList() + var roomRules: List = emptyList() + var senderRules: List = emptyList() + var underrideRules: List = emptyList() + monarchy.doWithRealm { realm -> PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "content").findFirst()?.let { re -> contentRules = re.pushRules.map { PushRulesMapper.mapContentRule(it) } } - } - - var overrideRules: List = emptyList() - monarchy.doWithRealm { realm -> PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "override").findFirst()?.let { re -> overrideRules = re.pushRules.map { PushRulesMapper.map(it) } } - } - - var roomRules: List = emptyList() - monarchy.doWithRealm { realm -> PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "room").findFirst()?.let { re -> roomRules = re.pushRules.map { PushRulesMapper.mapRoomRule(it) } } - } - - var senderRules: List = emptyList() - monarchy.doWithRealm { realm -> PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "sender").findFirst()?.let { re -> senderRules = re.pushRules.map { PushRulesMapper.mapSenderRule(it) } } - } - - var underrideRules: List = emptyList() - monarchy.doWithRealm { realm -> PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "underride").findFirst()?.let { re -> underrideRules = re.pushRules.map { PushRulesMapper.map(it) } } @@ -184,6 +173,4 @@ internal class DefaultPushRuleService( } } - - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt index 40ddb1eb3b..522387758b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -47,8 +47,8 @@ internal class DefaultPusherService( override fun refreshPushers() { getPusherTask .configureWith(Unit) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: PushersResponse) { + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: GetPushersResponse) { monarchy.runTransactionSync { realm -> //clear existings? realm.where(PusherEntity::class.java) @@ -66,9 +66,6 @@ internal class DefaultPusherService( .executeBy(taskExecutor) } - /** - * - */ override fun addHttpPusher(pushkey: String, appId: String, profileTag: String, lang: String, appDisplayName: String, deviceDisplayName: String, url: String, append: Boolean, withEventIdOnly: Boolean) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersResponse.kt similarity index 86% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersResponse.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersResponse.kt index 426b5c9e0e..4f4cac9302 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersResponse.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -internal class PushersResponse( - @Json(name = "pushers") val pushers: List? = null +internal class GetPushersResponse( + @Json(name = "pushers") + val pushers: List? = null ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt index 5c730d14b5..8587ab8576 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt @@ -19,11 +19,11 @@ import arrow.core.Try import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task -internal interface GetPushersTask : Task +internal interface GetPushersTask : Task internal class DefaultGetPusherTask(private val pushersAPI: PushersAPI) : GetPushersTask { - override suspend fun execute(params: Unit): Try { + override suspend fun execute(params: Unit): Try { return executeRequest { apiCall = pushersAPI.getPushers() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt index 1ce8c21ef9..b2c04842de 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt @@ -16,18 +16,18 @@ package im.vector.matrix.android.internal.session.pushers import arrow.core.Try -import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse +import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task -internal interface GetPushRulesTask : Task +internal interface GetPushRulesTask : Task -internal class DefaultGetPushrulesTask(private val pushrulesApi: PushrulesApi) : GetPushRulesTask { +internal class DefaultGetPushRulesTask(private val pushRulesApi: PushRulesApi) : GetPushRulesTask { - override suspend fun execute(params: Unit): Try { + override suspend fun execute(params: Unit): Try { return executeRequest { - apiCall = pushrulesApi.getAllRules() + apiCall = pushRulesApi.getAllRules() } } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt index e16bd78ecc..d262eeb745 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt @@ -20,14 +20,7 @@ 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. - * 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. + * Example: * * * { @@ -50,20 +43,61 @@ import im.vector.matrix.android.internal.di.SerializeNulls @JsonClass(generateAdapter = true) internal data class JsonPusher( - @Json(name = "pushkey") val pushKey: String, - @Json(name = "kind") @SerializeNulls val kind: String?, - @Json(name = "app_id") val appId: 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? = null, - @Json(name = "data") val data: JsonPusherData? = null, + /** + * Required. This is a unique identifier for this pusher. See /set for more detail. Max length, 512 bytes. + */ + @Json(name = "pushkey") + val pushKey: String, + + /** + * Required. The kind of pusher. "http" is a pusher that sends HTTP pokes. + */ + @SerializeNulls + @Json(name = "kind") + val kind: String?, + + /** + * Required. This is a reverse-DNS style identifier for the application. Max length, 64 chars. + */ + @Json(name = "app_id") + val appId: String, + + /** + * Required. A string that will allow the user to identify what application owns this pusher. + */ + @Json(name = "app_display_name") + val appDisplayName: String? = null, + + /** + * Required. A string that will allow the user to identify what device owns this pusher. + */ + @Json(name = "device_display_name") + val deviceDisplayName: String? = null, + + /** + * This string determines which set of device specific rules this pusher executes. + */ + @Json(name = "profile_tag") + val profileTag: String? = null, + + /** + * Required. The preferred language for receiving notifications (e.g. 'en' or 'en-US') + */ + @Json(name = "lang") + val lang: String? = null, + + /** + * Required. A dictionary of information for the pusher implementation itself. + */ + @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 // 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 + @Json(name = "append") + val append: Boolean? = false ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt index 0f0a55ee31..95898acae2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt @@ -20,8 +20,15 @@ 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 + /** + * 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 ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushRulesApi.kt similarity index 69% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushRulesApi.kt index cdec3543bc..eed08622db 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushrulesApi.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushRulesApi.kt @@ -16,18 +16,18 @@ package im.vector.matrix.android.internal.session.pushers import im.vector.matrix.android.api.pushrules.rest.PushRule -import im.vector.matrix.android.api.pushrules.rest.PushrulesResponse +import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.internal.network.NetworkConstants import retrofit2.Call import retrofit2.http.* -internal interface PushrulesApi { +internal interface PushRulesApi { /** * Get all push rules */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/") - fun getAllRules(): Call + fun getAllRules(): Call /** * Update the ruleID enable status @@ -37,28 +37,36 @@ internal interface PushrulesApi { * @param enable the new enable status */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled") - fun updateEnableRuleStatus(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body enable: Boolean?): Call + fun updateEnableRuleStatus(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body enable: Boolean?) + : Call /** - * Update the ruleID enable status + * Update the ruleID action * * @param kind the notification kind (sender, room...) * @param ruleId the ruleId * @param actions the actions */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions") - fun updateRuleActions(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body actions: Any): Call + fun updateRuleActions(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body actions: Any) + : Call /** - * Update the ruleID enable status + * Delete a rule * * @param kind the notification kind (sender, room...) * @param ruleId the ruleId */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - fun deleteRule(@Path("kind") kind: String, @Path("ruleId") ruleId: String): Call + fun deleteRule(@Path("kind") kind: String, + @Path("ruleId") ruleId: String) + : Call /** * Add the ruleID enable status @@ -68,5 +76,8 @@ internal interface PushrulesApi { * @param rule the rule to add. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - fun addRule(@Path("kind") kind: String, @Path("ruleId") ruleId: String, @Body rule: PushRule): Call + fun addRule(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body rule: PushRule) + : Call } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt index 632b4ed750..7516e48a0c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt @@ -27,14 +27,16 @@ 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 + * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers") - fun getPushers(): Call + fun getPushers(): Call /** * 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. + * + * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set") fun setPusher(@Body jsonPusher: JsonPusher): Call diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt index edd2fdaeb3..3548afb526 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt @@ -12,7 +12,9 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync internal interface RemovePusherTask : Task { - data class Params(val userId: String, val pushKey: String, val pushAppId: String) + data class Params(val userId: String, + val pushKey: String, + val pushAppId: String) } internal class DefaultRemovePusherTask( @@ -35,10 +37,11 @@ internal class DefaultRemovePusherTask( } }.flatMap { executeRequest { - val deleteRequest = JsonPusher( + val deleteBody = JsonPusher( pushKey = params.pushKey, appId = params.pushAppId, - kind = null, //null deletes the pusher + // kind null deletes the pusher + kind = null, appDisplayName = it.appDisplayName ?: "", deviceDisplayName = it.deviceDisplayName ?: "", profileTag = it.profileTag ?: "", @@ -46,7 +49,7 @@ internal class DefaultRemovePusherTask( data = JsonPusherData(it.data.url, it.data.format), append = false ) - apiCall = pushersAPI.setPusher(deleteRequest) + apiCall = pushersAPI.setPusher(deleteBody) } }.flatMap { monarchy.tryTransactionSync { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 63bc3f43f4..01df21281c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -29,7 +29,7 @@ import im.vector.matrix.android.internal.task.Task internal interface SyncTask : Task { - data class Params(val token: String?, var timeout: Long = 30_1000L) + data class Params(val token: String?, var timeout: Long = 30_000L) } diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt index eb337ab3e1..9c0e4e611f 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt @@ -50,7 +50,7 @@ class PushRuleActionsTest { val pushRule = MoshiProvider.providesMoshi().adapter(PushRule::class.java).fromJson(rawPushRule) Assert.assertNotNull("Should have parsed the rule", pushRule) - Assert.assertNotNull("Failed to parse domainActions", Action.mapFrom(pushRule!!)) + Assert.assertNotNull("Failed to parse actions", Action.mapFrom(pushRule!!)) val actions = Action.mapFrom(pushRule) Assert.assertEquals(3, actions!!.size) diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt index f6d93d78e3..25f380f42f 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt @@ -33,8 +33,6 @@ import timber.log.Timber * It has an alter ego in the fdroid variant. */ object FcmHelper { - private val LOG_TAG = FcmHelper::class.java.simpleName - private val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" @@ -51,6 +49,7 @@ object FcmHelper { /** * Store FCM token to the SharedPrefs + * TODO Store in realm * * @param context android context * @param token the token to store diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index ee63d806ec..b01d389183 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -54,7 +54,7 @@ import im.vector.riotredesign.features.workers.signout.SignOutViewModel class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { - // Supported navigation domainActions for this Activity + // Supported navigation actions for this Activity sealed class Navigation { object OpenDrawer : Navigation() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt index 2cf4134948..927bbba11b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt @@ -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/domainActions...) + * Depending on the state the bottom toolbar will change (icons/preview/actions...) */ enum class SendMode { REGULAR, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt index 85b9012923..84cfc40f0f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ActionsHandler.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.ViewModel import im.vector.riotredesign.core.utils.LiveEvent /** - * Activity shared view model to handle message domainActions + * Activity shared view model to handle message actions */ class ActionsHandler : ViewModel() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index f80a62f8bd..95777b8d7e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -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 domainActions - * (Includes fragments for quick reactions and list of domainActions) + * Bottom sheet fragment that shows a message preview with list of contextual actions + * (Includes fragments for quick reactions and list of actions) */ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index f523a77798..1d9c33f6fa 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -37,7 +37,7 @@ data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, data class MessageMenuState(val actions: List = emptyList()) : MvRxState /** - * Manages list domainActions for a given message (copy / paste / forward...) + * Manages list actions for a given message (copy / paste / forward...) */ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel(initialState) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt index 7ef7a720c4..1d7d5e214d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt @@ -30,12 +30,6 @@ import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import timber.log.Timber -// TODO Remove -class RoomState { - -} - - /** * The notifiable event resolver is able to create a NotifiableEvent (view model for notifications) from an sdk Event. * It is used as a bridge between the Event Thread and the NotificationDrawerManager. diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt index 574d267965..36692b7def 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt @@ -27,7 +27,7 @@ import org.koin.standalone.inject import timber.log.Timber /** - * Receives domainActions broadcast by notification (on click, on dismiss, inline replies, etc.) + * Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.) */ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt index aed05d2589..c8d478db3e 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt @@ -64,7 +64,7 @@ object NotificationUtils { const val NOTIFICATION_ID_FOREGROUND_SERVICE = 61 /* ========================================================================================== - * IDs for domainActions + * IDs for actions * ========================================================================================== */ private const val JOIN_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.JOIN_ACTION" @@ -426,7 +426,7 @@ object NotificationUtils { priority = NotificationCompat.PRIORITY_LOW } - //Add domainActions and notification intents + //Add actions and notification intents // Mark room as read val markRoomReadIntent = Intent(context, NotificationBroadcastReceiver::class.java) markRoomReadIntent.action = MARK_ROOM_READ_ACTION diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt index 067ab9e77c..a93f5dfedd 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/OutdatedEventDetector.kt @@ -17,7 +17,7 @@ package im.vector.riotredesign.features.notifications import im.vector.matrix.android.api.Matrix -class OutdatedEventDetector() { +class OutdatedEventDetector { /** * Returns true if the given event is outdated. diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt index 458db906cb..a392746250 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt @@ -1,3 +1,19 @@ +/* + * 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.notifications import im.vector.matrix.android.api.pushrules.Action diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt index 6cff9f2d6c..1a967db072 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt @@ -1,3 +1,19 @@ +/* + * 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 import android.os.Bundle @@ -5,15 +21,13 @@ 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 - +// Referenced in vector_settings_preferences_root.xml class VectorSettingsNotificationPreferenceFragment : VectorPreferenceFragment() { override var titleRes: Int = R.string.settings_notifications diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt index 0ef9d93a65..b29db44015 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragmentV2.kt @@ -1,3 +1,19 @@ +/* + * 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 import android.os.Bundle diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt index f57d76b853..4e3e116647 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewayItem.kt @@ -1,3 +1,19 @@ +/* + * 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.widget.TextView @@ -17,6 +33,7 @@ abstract class PushGatewayItem : EpoxyModelWithHolder() override fun bind(holder: Holder) { holder.kind.text = when (pusher.kind) { + // TODO Create const "http" -> "Http Pusher" "mail" -> "Email Pusher" else -> pusher.kind diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt index c468955f06..1ea57264da 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysFragment.kt @@ -30,6 +30,7 @@ import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.ui.list.genericFooterItem import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* +// Referenced in vector_settings_notifications.xml class PushGatewaysFragment : VectorBaseFragment() { override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy @@ -50,16 +51,16 @@ class PushGatewaysFragment : VectorBaseFragment() { val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, lmgr.orientation) epoxyRecyclerView.addItemDecoration(dividerItemDecoration) - epoxyRecyclerView.adapter = epoxyController.adapter + epoxyRecyclerView.setController(epoxyController) } - override fun invalidate() = withState(viewModel) { - epoxyController.setData(it) + override fun invalidate() = withState(viewModel) { state -> + epoxyController.setData(state) } class PushGateWayController(private val stringProvider: StringProvider) : TypedEpoxyController() { override fun buildModels(data: PushGatewayViewState?) { - data?.pushgateways?.invoke()?.let { pushers -> + data?.pushGateways?.invoke()?.let { pushers -> if (pushers.isEmpty()) { genericFooterItem { id("footer") diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt index 69c0b9c45b..edb032a0e9 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushGatewaysViewModel.kt @@ -25,12 +25,11 @@ import org.koin.android.ext.android.get data class PushGatewayViewState( - val pushgateways: Async> = Uninitialized) - : MvRxState + val pushGateways: Async> = Uninitialized +) : MvRxState class PushGatewaysViewModel(initialState: PushGatewayViewState) : VectorViewModel(initialState) { - companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? { diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt index 47f41e9171..c723f4ef87 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRuleItem.kt @@ -23,6 +23,7 @@ abstract class PushRuleItem : EpoxyModelWithHolder() { @EpoxyAttribute lateinit var pushRule: PushRule + // TODO i18n @SuppressLint("SetTextI18n") override fun bind(holder: Holder) { val context = holder.view.context @@ -48,7 +49,7 @@ abstract class PushRuleItem : EpoxyModelWithHolder() { holder.actionIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_action_dont_notify)) } - var description = StringBuffer() + val description = StringBuffer() pushRule.conditions?.forEachIndexed { i, condition -> if (i > 0) description.append("\n") description.append(condition.asExecutableCondition()?.technicalDescription() diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt index 7f73f893a3..3d5881db22 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesFragment.kt @@ -29,14 +29,14 @@ import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.ui.list.genericFooterItem import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* - +// Referenced in vector_settings_notifications.xml class PushRulesFragment : VectorBaseFragment() { override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class) - private val epoxyController by lazy { PushRulesFragment.PushRulesController(StringProvider(requireContext().resources)) } + private val epoxyController by lazy { PushRulesController(StringProvider(requireContext().resources)) } override fun onResume() { @@ -51,11 +51,11 @@ class PushRulesFragment : VectorBaseFragment() { val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, lmgr.orientation) epoxyRecyclerView.addItemDecoration(dividerItemDecoration) - epoxyRecyclerView.adapter = epoxyController.adapter + epoxyRecyclerView.setController(epoxyController) } - override fun invalidate() = withState(viewModel) { - epoxyController.setData(it) + override fun invalidate() = withState(viewModel) { state -> + epoxyController.setData(state) } class PushRulesController(private val stringProvider: StringProvider) : TypedEpoxyController() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt index 05109d7e7f..f950d0efa5 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt @@ -24,8 +24,8 @@ import im.vector.riotredesign.core.platform.VectorViewModel import org.koin.android.ext.android.get data class PushRulesViewState( - val rules: List = emptyList()) - : MvRxState + val rules: List = emptyList() +) : MvRxState class PushRulesViewModel(initialState: PushRulesViewState) : VectorViewModel(initialState) { diff --git a/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml b/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml index 7e5c013776..c794e8a5fa 100644 --- a/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml +++ b/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml @@ -1,16 +1,9 @@ - - - - - \ No newline at end of file + app:itemSpacing="1dp" + tools:listitem="@layout/item_pushgateway" /> From ce3242c748678ea94b84d5037c1614e01162810e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 15:03:32 +0200 Subject: [PATCH 16/26] Code quality: preference --- tools/check/forbidden_strings_in_resources.txt | 6 +++--- .../core/preference/UserAvatarPreference.kt | 3 ++- .../preference/VectorEditTextPreference.kt | 3 ++- .../core/preference/VectorListPreference.kt | 3 ++- .../core/preference/VectorPreference.kt | 7 ++----- .../preference/VectorPreferenceCategory.kt | 7 +++++-- .../core/preference/VectorSwitchPreference.kt | 3 ++- .../services/AlarmSyncBroadcastReceiver.kt | 7 +++---- .../core/services/VectorSyncService.kt | 4 +--- ...tings_notification_advanced_preferences.xml | 4 ++-- .../res/xml/vector_settings_notifications.xml | 18 +++++++++--------- .../xml/vector_settings_preferences_root.xml | 18 +++++++++--------- 12 files changed, 42 insertions(+), 41 deletions(-) diff --git a/tools/check/forbidden_strings_in_resources.txt b/tools/check/forbidden_strings_in_resources.txt index 26d7725591..120438efde 100644 --- a/tools/check/forbidden_strings_in_resources.txt +++ b/tools/check/forbidden_strings_in_resources.txt @@ -70,13 +70,13 @@ DO NOT COMMIT layout_constraintRight_ layout_constraintLeft_ -### Use Preference from v7 library (android.support.v7.preference.PreferenceScreen) +### Use Preference from androidx library (androidx.preference.PreferenceScreen) = Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ContextCompat.startForegroundService(context, intent) } else { context.startService(intent) @@ -41,7 +40,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { } } - scheduleAlarm(context,30_000L) + scheduleAlarm(context, 30_000L) Timber.i("Alarm scheduled to restart service") } @@ -56,7 +55,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { intent, PendingIntent.FLAG_UPDATE_CURRENT) val firstMillis = System.currentTimeMillis() + delay val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - if (SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pIntent) } else { alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pIntent) diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt index e56399b917..6f105c94fd 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt @@ -19,13 +19,11 @@ 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() { @@ -51,7 +49,7 @@ class VectorSyncService : SyncService() { */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Timber.v("VectorSyncService - onStartCommand ") - if (SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.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) } diff --git a/vector/src/main/res/xml/vector_settings_notification_advanced_preferences.xml b/vector/src/main/res/xml/vector_settings_notification_advanced_preferences.xml index 5ff42694ee..bb5de919c5 100644 --- a/vector/src/main/res/xml/vector_settings_notification_advanced_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_notification_advanced_preferences.xml @@ -1,7 +1,7 @@ - @@ -34,7 +34,7 @@ android:key="SETTINGS_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY_2" android:title="@string/settings_messages_sent_by_bot" /> - + diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml index ed20daf6ac..325c5fe11e 100644 --- a/vector/src/main/res/xml/vector_settings_notifications.xml +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -1,12 +1,12 @@ - - + - @@ -17,12 +17,12 @@ - + - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_preferences_root.xml b/vector/src/main/res/xml/vector_settings_preferences_root.xml index cb3b5840b0..077293706d 100644 --- a/vector/src/main/res/xml/vector_settings_preferences_root.xml +++ b/vector/src/main/res/xml/vector_settings_preferences_root.xml @@ -1,21 +1,21 @@ - - - - - - - - - \ No newline at end of file + \ No newline at end of file From 328f09072328131bf90d633661b6cc7fa4da6c87 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 15:08:33 +0200 Subject: [PATCH 17/26] Code quality: i18n --- vector/src/main/res/layout/item_pushgateway.xml | 12 ++++++------ vector/src/main/res/values/strings_riotX.xml | 9 +++++++++ .../res/xml/vector_settings_preferences_root.xml | 8 +------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/vector/src/main/res/layout/item_pushgateway.xml b/vector/src/main/res/layout/item_pushgateway.xml index e50a001507..c4a01be169 100644 --- a/vector/src/main/res/layout/item_pushgateway.xml +++ b/vector/src/main/res/layout/item_pushgateway.xml @@ -21,7 +21,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" - android:text="app_id" + android:text="@string/push_gateway_item_app_id" android:textStyle="bold" /> @@ -38,7 +38,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" - android:text="push_key:" + android:text="@string/push_gateway_item_push_key" android:textStyle="bold" /> @@ -55,7 +55,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" - android:text="app_display_name" + android:text="@string/push_gateway_item_app_display_name" android:textStyle="bold" /> @@ -72,7 +72,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" - android:text="device_name:" + android:text="@string/push_gateway_item_device_name" android:textStyle="bold" /> @@ -90,7 +90,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" - android:text="Url:" + android:text="@string/push_gateway_item_url" android:textStyle="bold" /> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 987afcd86d..7ff0148911 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -21,4 +21,13 @@ No push rules defined No registered push gateways + app_id: + push_key: + app_display_name: + device_name: + Url: + Format: + + Legacy + \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_preferences_root.xml b/vector/src/main/res/xml/vector_settings_preferences_root.xml index 077293706d..968aa68c6b 100644 --- a/vector/src/main/res/xml/vector_settings_preferences_root.xml +++ b/vector/src/main/res/xml/vector_settings_preferences_root.xml @@ -8,7 +8,6 @@ android:title="@string/settings_general_title" app:fragment="com.example.SyncFragment" /> - - - - - - \ No newline at end of file From 785f33177d9d9edc633559a1b3349aa4e337859d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 15:39:51 +0200 Subject: [PATCH 18/26] Notification: open room and clear drawer - Smart reply - Mark as read - dismiss all --- vector/src/main/AndroidManifest.xml | 12 +++ .../features/home/HomeActivity.kt | 32 ++++++-- .../home/room/detail/RoomDetailFragment.kt | 16 +++- .../NotificationBroadcastReceiver.kt | 78 +++++++++---------- .../NotificationDrawerManager.kt | 4 +- .../notifications/NotificationUtils.kt | 14 ++-- 6 files changed, 97 insertions(+), 59 deletions(-) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 722460a174..aaae3edb6b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -63,6 +63,8 @@ + + @@ -76,6 +78,16 @@ + + + + + + + () private val pushManager by inject() + private val notificationDrawerManager by inject() + // TODO Move this elsewhere private val incomingVerificationRequestHandler by inject() // TODO Move this elsewhere @@ -115,6 +117,20 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { incomingVerificationRequestHandler.ensureStarted() keyRequestHandler.ensureStarted() + + if (intent.hasExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION)) { + notificationDrawerManager.clearAllEvents() + intent.removeExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION) + } + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + + if (intent?.hasExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION) == true) { + notificationDrawerManager.clearAllEvents() + intent.removeExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION) + } } override fun onDestroy() { @@ -189,10 +205,14 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { companion object { - fun newIntent(context: Context): Intent { - return Intent(context, HomeActivity::class.java) - } + private const val EXTRA_CLEAR_EXISTING_NOTIFICATION = "EXTRA_CLEAR_EXISTING_NOTIFICATION" + fun newIntent(context: Context, clearNotification: Boolean = false): Intent { + return Intent(context, HomeActivity::class.java) + .apply { + putExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION, clearNotification) + } + } } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 9f5a748c6f..f8f6f297f1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -43,7 +43,6 @@ import butterknife.BindView import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState import com.github.piasy.biv.BigImageViewer import com.github.piasy.biv.loader.ImageLoader import com.google.android.material.snackbar.Snackbar @@ -92,6 +91,7 @@ import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageMediaViewerActivity import im.vector.riotredesign.features.media.VideoContentRenderer import im.vector.riotredesign.features.media.VideoMediaViewerActivity +import im.vector.riotredesign.features.notifications.NotificationDrawerManager import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity import im.vector.riotredesign.features.settings.PreferencesManager import kotlinx.android.parcel.Parcelize @@ -168,6 +168,8 @@ class RoomDetailFragment : private val autocompleteUserPresenter: AutocompleteUserPresenter by inject { parametersOf(this) } private val permalinkHandler: PermalinkHandler by inject() + private val notificationDrawerManager by inject() + private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback @@ -277,6 +279,18 @@ class RoomDetailFragment : } } + override fun onResume() { + super.onResume() + + notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId) + } + + override fun onPause() { + super.onPause() + + notificationDrawerManager.setCurrentRoom(null) + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == RESULT_OK && data != null) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt index 36692b7def..48f8ef43c7 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt @@ -20,11 +20,15 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.core.app.RemoteInput +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.Room +import im.vector.riotredesign.R import org.koin.standalone.KoinComponent import org.koin.standalone.inject import timber.log.Timber +import java.util.* /** * Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.) @@ -39,15 +43,15 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent { Timber.v("ReplyNotificationBroadcastReceiver received : $intent") when (intent.action) { - NotificationUtils.SMART_REPLY_ACTION -> + NotificationUtils.SMART_REPLY_ACTION -> handleSmartReply(intent, context) NotificationUtils.DISMISS_ROOM_NOTIF_ACTION -> intent.getStringExtra(KEY_ROOM_ID)?.let { notificationDrawerManager.clearMessageEventOfRoom(it) } - NotificationUtils.DISMISS_SUMMARY_ACTION -> + NotificationUtils.DISMISS_SUMMARY_ACTION -> notificationDrawerManager.clearAllEvents() - NotificationUtils.MARK_ROOM_READ_ACTION -> + NotificationUtils.MARK_ROOM_READ_ACTION -> intent.getStringExtra(KEY_ROOM_ID)?.let { notificationDrawerManager.clearMessageEventOfRoom(it) handleMarkAsRead(context, it) @@ -56,67 +60,59 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent { } private fun handleMarkAsRead(context: Context, roomId: String) { - /* - TODO - Matrix.getInstance(context)?.defaultSession?.let { session -> - session.dataHandler - ?.getRoom(roomId) - ?.markAllAsRead(object : SimpleApiCallback() { - override fun onSuccess(void: Void?) { - // Ignore - } - }) + Matrix.getInstance().currentSession?.let { session -> + session.getRoom(roomId) + ?.markAllAsRead(object : MatrixCallback {}) } - */ } private fun handleSmartReply(intent: Intent, context: Context) { - /* - TODO val message = getReplyMessage(intent) val roomId = intent.getStringExtra(KEY_ROOM_ID) - if (TextUtils.isEmpty(message) || TextUtils.isEmpty(roomId)) { + if (message.isNullOrBlank() || roomId.isBlank()) { //ignore this event //Can this happen? should we update notification? return } val matrixId = intent.getStringExtra(EXTRA_MATRIX_ID) - Matrix.getInstance(context)?.getSession(matrixId)?.let { session -> - session.dataHandler?.getRoom(roomId)?.let { room -> - sendMatrixEvent(message!!, session, roomId!!, room, context) + Matrix.getInstance().currentSession?.let { session -> + session.getRoom(roomId)?.let { room -> + sendMatrixEvent(message, session, room, context) } } - */ } - private fun sendMatrixEvent(message: String, session: Session, roomId: String, room: Room, context: Context?) { - /* - TODO + private fun sendMatrixEvent(message: String, session: Session, room: Room, context: Context?) { - val mxMessage = Message() - mxMessage.msgtype = Message.MSGTYPE_TEXT - mxMessage.body = message + room.sendTextMessage(message) + + // Create a new event to be displayed in the notification drawer, right now + + val notifiableMessageEvent = NotifiableMessageEvent( + Random().nextInt().toString(),// TODO event.eventId, + false, + System.currentTimeMillis(), + session.getUser(session.sessionParams.credentials.userId)?.displayName + ?: context?.getString(R.string.notification_sender_me), + session.sessionParams.credentials.userId, + message, + room.roomId, + "Room name", // TODO room.getRoomDisplayName(context), + false // TODO room.isDirect + ) + notifiableMessageEvent.outGoingMessage = true + + notificationDrawerManager.onNotifiableEventReceived(notifiableMessageEvent) + notificationDrawerManager.refreshNotificationDrawer() + + /* val event = Event(mxMessage, session.credentials.userId, roomId) room.storeOutgoingEvent(event) room.sendEvent(event, object : MatrixCallback { override fun onSuccess(info: Void?) { Timber.v("Send message : onSuccess ") - val notifiableMessageEvent = NotifiableMessageEvent( - event.eventId, - false, - System.currentTimeMillis(), - session.myUser?.displayname - ?: context?.getString(R.string.notification_sender_me), - session.myUserId, - message, - roomId, - room.getRoomDisplayName(context), - room.isDirect) - notifiableMessageEvent.outGoingMessage = true - VectorApp.getInstance().notificationDrawerManager.onNotifiableEventReceived(notifiableMessageEvent) - VectorApp.getInstance().notificationDrawerManager.refreshNotificationDrawer(null) } override fun onNetworkError(e: Exception) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index 3e0875b025..6d5d29427f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -127,8 +127,8 @@ class NotificationDrawerManager(val context: Context, } /** - Should be called when the application is currently opened and showing timeline for the given roomId. - Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room. + * Should be called when the application is currently opened and showing timeline for the given roomId. + * Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room. */ fun setCurrentRoom(roomId: String?) { var hasChanged: Boolean diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt index c8d478db3e..67e30c57da 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt @@ -39,6 +39,8 @@ import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.R import im.vector.riotredesign.core.utils.startNotificationChannelSettingsIntent import im.vector.riotredesign.features.home.HomeActivity +import im.vector.riotredesign.features.home.room.detail.RoomDetailActivity +import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs import im.vector.riotredesign.features.settings.PreferencesManager import timber.log.Timber import java.util.* @@ -545,27 +547,21 @@ object NotificationUtils { } private fun buildOpenRoomIntent(context: Context, roomId: String): PendingIntent? { - // TODO - return null - /* - val roomIntentTap = Intent(context, VectorRoomActivity::class.java) - roomIntentTap.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId) + val roomIntentTap = RoomDetailActivity.newIntent(context, RoomDetailArgs(roomId)) roomIntentTap.action = TAP_TO_VIEW_ACTION //pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that roomIntentTap.data = Uri.parse("foobar://openRoom?$roomId") // Recreate the back stack return TaskStackBuilder.create(context) - .addNextIntentWithParentStack(Intent(context, VectorHomeActivity::class.java)) + .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java)) .addNextIntent(roomIntentTap) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) - */ } private fun buildOpenHomePendingIntentForSummary(context: Context): PendingIntent { - val intent = Intent(context, HomeActivity::class.java) + val intent = HomeActivity.newIntent(context, clearNotification = true) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - // TODO intent.putExtra(VectorHomeActivity.EXTRA_CLEAR_EXISTING_NOTIFICATION, true) intent.data = Uri.parse("foobar://tapSummary") return PendingIntent.getActivity(context, Random().nextInt(1000), intent, PendingIntent.FLAG_UPDATE_CURRENT) } From f6c500d1206e684b6994df83f036674c96ca8f7b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 16:14:25 +0200 Subject: [PATCH 19/26] Notification: dismiss all on sign out --- .../im/vector/riotredesign/features/home/HomeActivity.kt | 3 ++- .../features/workers/signout/SignOutUiWorker.kt | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 2958434fdd..572d865e17 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -166,7 +166,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.sliding_menu_sign_out -> { - SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!) + SignOutUiWorker(this, notificationDrawerManager) + .perform(Matrix.getInstance().currentSession!!) return true } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt index 81f80b9f02..3257685186 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt @@ -21,8 +21,10 @@ import im.vector.matrix.android.api.session.Session import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.features.MainActivity +import im.vector.riotredesign.features.notifications.NotificationDrawerManager -class SignOutUiWorker(val activity: VectorBaseActivity) { +class SignOutUiWorker(private val activity: VectorBaseActivity, + private val notificationDrawerManager: NotificationDrawerManager) { fun perform(session: Session) { if (SignOutViewModel.doYouNeedToBeDisplayed(session)) { @@ -45,6 +47,9 @@ class SignOutUiWorker(val activity: VectorBaseActivity) { } private fun doSignOut() { + // Dismiss all notifications + notificationDrawerManager.clearAllEvents() + MainActivity.restartApp(activity, clearCache = true, clearCredentials = true) } } From 4f0ed402bf16dbcdd34c4f0b1f843c11955a95ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 16:19:57 +0200 Subject: [PATCH 20/26] Notification: cleanup --- .../notifications/NotificationBroadcastReceiver.kt | 8 +++++--- .../features/notifications/NotificationDrawerManager.kt | 1 - .../features/notifications/PushRuleTriggerListener.kt | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt index 48f8ef43c7..499ea68ec3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt @@ -90,7 +90,8 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent { // Create a new event to be displayed in the notification drawer, right now val notifiableMessageEvent = NotifiableMessageEvent( - Random().nextInt().toString(),// TODO event.eventId, + // Generate a Fake event id + UUID.randomUUID().toString(), false, System.currentTimeMillis(), session.getUser(session.sessionParams.credentials.userId)?.displayName @@ -98,8 +99,8 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent { session.sessionParams.credentials.userId, message, room.roomId, - "Room name", // TODO room.getRoomDisplayName(context), - false // TODO room.isDirect + room.roomSummary?.displayName ?: room.roomId, + room.roomSummary?.isDirect == true ) notifiableMessageEvent.outGoingMessage = true @@ -107,6 +108,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent { notificationDrawerManager.refreshNotificationDrawer() /* + // TODO Error cannot be managed the same way than in Riot val event = Event(mxMessage, session.credentials.userId, roomId) room.storeOutgoingEvent(event) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index 6d5d29427f..23cd4c55e6 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -96,7 +96,6 @@ class NotificationDrawerManager(val context: Context, } else { eventList.add(notifiableEvent) } - } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt index a392746250..f3aed6ad05 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt @@ -69,7 +69,6 @@ class PushRuleTriggerListener( session?.removePushRuleListener(this) session = null drawerManager.clearAllEvents() - drawerManager.refreshNotificationDrawer() } } \ No newline at end of file From 5a1242109da4e990a2bd4f0a51b8e81b30dba13c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 16:52:34 +0200 Subject: [PATCH 21/26] Notification: display rooms and users' avatars --- .../vector/riotredesign/VectorApplication.kt | 16 ++++----- .../notifications/NotifiableEventResolver.kt | 36 ++++++++----------- .../NotificationBroadcastReceiver.kt | 2 +- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index 6f8127536d..9bef712e0c 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -16,17 +16,11 @@ 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 @@ -41,14 +35,13 @@ 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.riotredesign.core.di.AppModule import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver -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.NotificationDrawerManager import im.vector.riotredesign.features.notifications.NotificationUtils import im.vector.riotredesign.features.notifications.PushRuleTriggerListener import im.vector.riotredesign.features.rageshake.VectorFileLogger @@ -64,7 +57,6 @@ 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() { @@ -75,6 +67,8 @@ class VectorApplication : Application() { val vectorConfiguration: VectorConfiguration by inject() + private val notificationDrawerManager by inject() + // var slowMode = false @@ -131,7 +125,9 @@ class VectorApplication : Application() { @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun entersBackground() { - Timber.i("App entered background") + Timber.i("App entered background") // call persistInfo + + notificationDrawerManager.persistInfo() if (FcmHelper.isPushSupported()) { //TODO FCM fallback diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt index 1d7d5e214d..ca2ec6142e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotifiableEventResolver.kt @@ -17,6 +17,7 @@ package im.vector.riotredesign.features.notifications import androidx.core.app.NotificationCompat import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.content.ContentUrlResolver 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.toModel @@ -36,8 +37,8 @@ import timber.log.Timber * The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that, * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. */ -class NotifiableEventResolver(val stringProvider: StringProvider, - val noticeEventFormatter: NoticeEventFormatter) { +class NotifiableEventResolver(private val stringProvider: StringProvider, + private val noticeEventFormatter: NoticeEventFormatter) { //private val eventDisplay = RiotEventDisplay(context) @@ -132,26 +133,19 @@ class NotifiableEventResolver(val stringProvider: StringProvider, notifiableEvent.matrixID = session.sessionParams.credentials.userId notifiableEvent.soundName = null - //TODO get the avatar? + // Get the avatars URL + // TODO They will be not displayed the first time (known limitation) + notifiableEvent.roomAvatarPath = session.contentUrlResolver() + .resolveThumbnail(room.roomSummary?.avatarUrl, + 250, + 250, + ContentUrlResolver.ThumbnailMethod.SCALE) -// 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) -// } -// } + notifiableEvent.senderAvatarPath = session.contentUrlResolver() + .resolveThumbnail(event.senderAvatar, + 250, + 250, + ContentUrlResolver.ThumbnailMethod.SCALE) return notifiableEvent } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt index 499ea68ec3..501e8e499f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt @@ -40,7 +40,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent { override fun onReceive(context: Context?, intent: Intent?) { if (intent == null || context == null) return - Timber.v("ReplyNotificationBroadcastReceiver received : $intent") + Timber.v("NotificationBroadcastReceiver received : $intent") when (intent.action) { NotificationUtils.SMART_REPLY_ACTION -> From b388be93c8dc915d841915df7bbddce0543b84f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 17:09:01 +0200 Subject: [PATCH 22/26] Notification: better code --- .../NotificationDrawerManager.kt | 31 ++++++++++--------- .../notifications/RoomEventGroupInfo.kt | 16 +++++----- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index 23cd4c55e6..fe47764293 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -216,22 +216,23 @@ class NotificationDrawerManager(val context: Context, continue } - val roomGroup = RoomEventGroupInfo(roomId) - roomGroup.hasNewEvent = false - roomGroup.shouldBing = false - roomGroup.isDirect = events[0].roomIsDirect val roomName = events[0].roomName ?: events[0].senderName ?: "" + + val roomEventGroupInfo = RoomEventGroupInfo( + roomId = roomId, + isDirect = events[0].roomIsDirect, + roomDisplayName = roomName) + val style = NotificationCompat.MessagingStyle(Person.Builder() .setName(myUserDisplayName) .setIcon(iconLoader.getUserIcon(myUserAvatarUrl)) .setKey(events[0].matrixID) .build()) - roomGroup.roomDisplayName = roomName - style.isGroupConversation = !roomGroup.isDirect + style.isGroupConversation = !roomEventGroupInfo.isDirect - if (!roomGroup.isDirect) { - style.conversationTitle = roomName + if (!roomEventGroupInfo.isDirect) { + style.conversationTitle = roomEventGroupInfo.roomDisplayName } val largeBitmap = getRoomBitmap(events) @@ -240,10 +241,10 @@ class NotificationDrawerManager(val context: Context, for (event in events) { //if all events in this room have already been displayed there is no need to update it if (!event.hasBeenDisplayed) { - roomGroup.shouldBing = roomGroup.shouldBing || event.noisy - roomGroup.customSound = event.soundName + roomEventGroupInfo.shouldBing = roomEventGroupInfo.shouldBing || event.noisy + roomEventGroupInfo.customSound = event.soundName } - roomGroup.hasNewEvent = roomGroup.hasNewEvent || !event.hasBeenDisplayed + roomEventGroupInfo.hasNewEvent = roomEventGroupInfo.hasNewEvent || !event.hasBeenDisplayed val senderPerson = Person.Builder() .setName(event.senderName) @@ -253,7 +254,7 @@ class NotificationDrawerManager(val context: Context, if (event.outGoingMessage && event.outGoingMessageFailed) { style.addMessage(context.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson) - roomGroup.hasSmartReplyError = true + roomEventGroupInfo.hasSmartReplyError = true } else { style.addMessage(event.body, event.timestamp, senderPerson) } @@ -274,7 +275,7 @@ class NotificationDrawerManager(val context: Context, summaryInboxStyle.addLine(roomName) } - if (firstTime || roomGroup.hasNewEvent) { + if (firstTime || roomEventGroupInfo.hasNewEvent) { //Should update displayed notification Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER $roomId need refresh") val lastMessageTimestamp = events.last().timestamp @@ -283,14 +284,14 @@ class NotificationDrawerManager(val context: Context, globalLastMessageTimestamp = lastMessageTimestamp } - NotificationUtils.buildMessagesListNotification(context, style, roomGroup, largeBitmap, lastMessageTimestamp, myUserDisplayName) + NotificationUtils.buildMessagesListNotification(context, style, roomEventGroupInfo, largeBitmap, lastMessageTimestamp, myUserDisplayName) ?.let { //is there an id for this room? notifications.add(it) NotificationUtils.showNotificationMessage(context, roomId, ROOM_MESSAGES_NOTIFICATION_ID, it) } hasNewEvent = true - summaryIsNoisy = summaryIsNoisy || roomGroup.shouldBing + summaryIsNoisy = summaryIsNoisy || roomEventGroupInfo.shouldBing } else { Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER $roomId is up to date") } diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt index e1c4e58280..0256ded6a1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt @@ -20,15 +20,15 @@ package im.vector.riotredesign.features.notifications * Data class to hold information about a group of notifications for a room */ data class RoomEventGroupInfo( - val roomId: String + val roomId: String, + val roomDisplayName: String = "", + val roomAvatarPath: String? = null, + val isDirect: Boolean = false ) { - var roomDisplayName: String = "" - var roomAvatarPath: String? = null - //An event in the list has not yet been display + // An event in the list has not yet been display var hasNewEvent: Boolean = false - //true if at least one on the not yet displayed event is noisy + // true if at least one on the not yet displayed event is noisy var shouldBing: Boolean = false var customSound: String? = null - var hasSmartReplyError = false - var isDirect = false -} \ No newline at end of file + var hasSmartReplyError: Boolean = false +} From 9fa3a75fb6b40e40440018460948e4fce9051391 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jun 2019 17:31:53 +0200 Subject: [PATCH 23/26] Notification: display room avatar --- .../features/notifications/BitmapLoader.kt | 124 ++++++++++++++++++ .../NotificationDrawerManager.kt | 25 ++-- .../notifications/RoomEventGroupInfo.kt | 1 - 3 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/notifications/BitmapLoader.kt diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/BitmapLoader.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/BitmapLoader.kt new file mode 100644 index 0000000000..9a52ea5dbc --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/BitmapLoader.kt @@ -0,0 +1,124 @@ +/* + * 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.notifications + +import android.content.Context +import android.graphics.Bitmap +import android.os.Handler +import android.os.HandlerThread +import androidx.annotation.WorkerThread +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DecodeFormat +import timber.log.Timber + +/** + * FIXME It works, but it does not refresh the notification, when it's already displayed + */ +class BitmapLoader(val context: Context, + val listener: BitmapLoaderListener) { + + /** + * Avatar Url -> Icon + */ + private val cache = HashMap() + + // URLs to load + private val toLoad = HashSet() + + // Black list of URLs (broken URL, etc.) + private val blacklist = HashSet() + + private var uiHandler = Handler() + + private val handlerThread: HandlerThread = HandlerThread("BitmapLoader", Thread.MIN_PRIORITY) + private var backgroundHandler: Handler + + init { + handlerThread.start() + backgroundHandler = Handler(handlerThread.looper) + } + + /** + * Get icon of a room. + * If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready + */ + fun getRoomBitmap(path: String?): Bitmap? { + if (path == null) { + return null + } + + synchronized(cache) { + if (cache[path] != null) { + return cache[path] + } + + // Add to the queue, if not blacklisted + if (!blacklist.contains(path)) { + if (toLoad.contains(path)) { + // Wait + } else { + toLoad.add(path) + + backgroundHandler.post { + loadRoomBitmap(path) + } + } + } + } + + return null + } + + @WorkerThread + private fun loadRoomBitmap(path: String) { + val bitmap = path.let { + try { + Glide.with(context) + .asBitmap() + .load(path) + .format(DecodeFormat.PREFER_ARGB_8888) + .submit() + .get() + } catch (e: Exception) { + Timber.e(e, "decodeFile failed") + null + } + } + + synchronized(cache) { + if (bitmap == null) { + // Add to the blacklist + blacklist.add(path) + } else { + cache[path] = bitmap + } + + toLoad.remove(path) + + if (toLoad.isEmpty()) { + uiHandler.post { + listener.onBitmapsLoaded() + } + } + } + } + + + interface BitmapLoaderListener { + fun onBitmapsLoaded() + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index fe47764293..67c4aa85ae 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -18,8 +18,6 @@ package im.vector.riotredesign.features.notifications import android.app.Notification import android.content.Context import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.text.TextUtils import androidx.core.app.NotificationCompat import androidx.core.app.Person import im.vector.matrix.android.api.Matrix @@ -59,6 +57,14 @@ class NotificationDrawerManager(val context: Context, } }) + private var bitmapLoader = BitmapLoader(context, + object : BitmapLoader.BitmapLoaderListener { + override fun onBitmapsLoaded() { + // Force refresh + refreshNotificationDrawer() + } + }) + /** Should be called as soon as a new event is ready to be displayed. The notification corresponding to this event will not be displayed until @@ -372,19 +378,10 @@ class NotificationDrawerManager(val context: Context, if (events.isEmpty()) return null //Use the last event (most recent?) - val roomAvatarPath = events[events.size - 1].roomAvatarPath - ?: events[events.size - 1].senderAvatarPath - if (!TextUtils.isEmpty(roomAvatarPath)) { - val options = BitmapFactory.Options() - options.inPreferredConfig = Bitmap.Config.ARGB_8888 - try { - return BitmapFactory.decodeFile(roomAvatarPath, options) - } catch (oom: OutOfMemoryError) { - Timber.e(oom, "decodeFile failed with an oom") - } + val roomAvatarPath = events.last().roomAvatarPath + ?: events.last().senderAvatarPath - } - return null + return bitmapLoader.getRoomBitmap(roomAvatarPath) } private fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean { diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt index 0256ded6a1..eac6e4d99c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/RoomEventGroupInfo.kt @@ -22,7 +22,6 @@ package im.vector.riotredesign.features.notifications data class RoomEventGroupInfo( val roomId: String, val roomDisplayName: String = "", - val roomAvatarPath: String? = null, val isDirect: Boolean = false ) { // An event in the list has not yet been display From 2625e115082b172241e338ba591e10871183f2f6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Jun 2019 10:09:45 +0200 Subject: [PATCH 24/26] rename method --- .../android/api/pushrules/PushRuleService.kt | 3 +-- .../matrix/android/api/pushrules/RuleIds.kt | 20 +++++++++++++++++++ .../internal/session/DefaultSession.kt | 4 ++-- .../session/notification/BingRuleWatcher.kt | 2 +- .../notification/DefaultPushRuleService.kt | 2 +- .../settings/push/PushRulesViewModel.kt | 2 +- 6 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt index fc71d16d90..222d0da9cc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt @@ -20,14 +20,13 @@ import im.vector.matrix.android.api.session.events.model.Event interface PushRuleService { - /** * Fetch the push rules from the server */ fun fetchPushRules(scope: String = "global") //TODO get push rule set - fun getPushrules(scope: String = "global"): List + fun getPushRules(scope: String = "global"): List //TODO update rule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt new file mode 100644 index 0000000000..c3a64490fc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt @@ -0,0 +1,20 @@ +/* + * 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 RuleIds { +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index bdc365f466..c6aef848bb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -503,8 +503,8 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi return pushersService.livePushers() } - override fun getPushrules(scope: String): List { - return pushRuleService.getPushrules(scope) + override fun getPushRules(scope: String): List { + return pushRuleService.getPushRules(scope) } override fun fetchPushRules(scope: String) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt index b584fd8e5d..2e6a1a9a77 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -42,7 +42,7 @@ internal class BingRuleWatcher(monarchy: Monarchy, } override fun processChanges(inserted: List, updated: List, deleted: List) { - val rules = defaultPushRuleService.getPushrules("global") + val rules = defaultPushRuleService.getPushRules("global") inserted.map { it.asDomain() } .filter { it.senderId != sessionParams.credentials.userId } .let { events -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt index afaab4862f..c5c1e18e79 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -103,7 +103,7 @@ internal class DefaultPushRuleService( .executeBy(taskExecutor) } - override fun getPushrules(scope: String): List { + override fun getPushRules(scope: String): List { var contentRules: List = emptyList() var overrideRules: List = emptyList() diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt index f950d0efa5..03a2e600e0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/push/PushRulesViewModel.kt @@ -34,7 +34,7 @@ class PushRulesViewModel(initialState: PushRulesViewState) : VectorViewModel() - val rules = session.getPushrules() + val rules = session.getPushRules() return PushRulesViewState(rules) } From 134c2fcd4257b21f36425b8dc36bf484324d5784 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Jun 2019 11:43:50 +0200 Subject: [PATCH 25/26] Notification: Fix TestAccountSettings test --- .../android/api/pushrules/PushRuleService.kt | 3 ++ .../matrix/android/api/pushrules/RuleIds.kt | 31 +++++++++++++++- .../database/model/PushRulesEntity.kt | 1 + .../internal/session/DefaultSession.kt | 4 ++ .../android/internal/session/SessionModule.kt | 6 ++- .../notification/DefaultPushRuleService.kt | 13 ++++++- ...etPushrulesTask.kt => GetPushRulesTask.kt} | 0 .../pushers/UpdatePushRuleEnableStatusTask.kt | 37 +++++++++++++++++++ ...ificationTroubleshootTestManagerFactory.kt | 6 +-- .../troubleshoot/TestAccountSettings.kt | 29 ++++++++------- 10 files changed, 106 insertions(+), 24 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/{GetPushrulesTask.kt => GetPushRulesTask.kt} (100%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt index 222d0da9cc..b00450b561 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt @@ -15,6 +15,7 @@ */ package im.vector.matrix.android.api.pushrules +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.session.events.model.Event @@ -30,6 +31,8 @@ interface PushRuleService { //TODO update rule + fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback) + fun addPushRuleListener(listener: PushRuleListener) fun removePushRuleListener(listener: PushRuleListener) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt index c3a64490fc..38a64adf27 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt @@ -16,5 +16,32 @@ package im.vector.matrix.android.api.pushrules -enum class RuleIds { -} \ No newline at end of file +/** + * Known rule ids + * + * Ref: https://matrix.org/docs/spec/client_server/latest#predefined-rules + */ +object RuleIds { + // Default Override Rules + const val RULE_ID_DISABLE_ALL = ".m.rule.master" + const val RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS = ".m.rule.suppress_notices" + const val RULE_ID_INVITE_ME = ".m.rule.invite_for_me" + const val RULE_ID_PEOPLE_JOIN_LEAVE = ".m.rule.member_event" + const val RULE_ID_CONTAIN_DISPLAY_NAME = ".m.rule.contains_display_name" + + const val RULE_ID_TOMBSTONE = ".m.rule.tombstone" + const val RULE_ID_ROOM_NOTIF = ".m.rule.roomnotif" + + // Default Content Rules + const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name" + + // Default Underride Rules + const val RULE_ID_CALL = ".m.rule.call" + const val RULE_ID_one_to_one_encrypted_room = ".m.rule.encrypted_room_one_to_one" + const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one" + const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message" + const val RULE_ID_ENCRYPTED = ".m.rule.encrypted" + + // Not documented + const val RULE_ID_FALLBACK = ".m.rule.fallback" +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt index 1f1681d839..f504192550 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.kt @@ -23,6 +23,7 @@ import io.realm.annotations.Index internal open class PushRulesEntity( @Index var userId: String = "", var scope: String = "", + // "content", etc. var rulesetKey: String = "", var pushRules: RealmList = RealmList() ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index c6aef848bb..7666f49dc9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -507,6 +507,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi return pushRuleService.getPushRules(scope) } + override fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback) { + pushRuleService.updatePushRuleEnableStatus(kind, pushRule, enabled, callback) + } + override fun fetchPushRules(scope: String) { pushRuleService.fetchPushRules(scope) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index ef2fe85791..1ea4803a56 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -180,13 +180,17 @@ internal class SessionModule(private val sessionParams: SessionParams) { } scope(DefaultSession.SCOPE) { - DefaultPushRuleService(get(), get(), get(), get()) + DefaultPushRuleService(get(), get(), get(), get(), get()) } scope(DefaultSession.SCOPE) { DefaultGetPushRulesTask(get()) as GetPushRulesTask } + scope(DefaultSession.SCOPE) { + DefaultUpdatePushRuleEnableStatusTask(get()) as UpdatePushRuleEnableStatusTask + } + scope(DefaultSession.SCOPE) { DefaultProcessEventForPushTask(get()) as ProcessEventForPushTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt index c5c1e18e79..c85f0b442f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.database.model.PushRulesEntity import im.vector.matrix.android.internal.database.model.PusherEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask +import im.vector.matrix.android.internal.session.pushers.UpdatePushRuleEnableStatusTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import timber.log.Timber @@ -36,6 +37,7 @@ import timber.log.Timber internal class DefaultPushRuleService( private val sessionParams: SessionParams, private val pushRulesTask: GetPushRulesTask, + private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask, private val taskExecutor: TaskExecutor, private val monarchy: Monarchy ) : PushRuleService { @@ -95,8 +97,6 @@ internal class DefaultPushRuleService( } } realm.insertOrUpdate(underrides) - - } } }) @@ -111,6 +111,7 @@ internal class DefaultPushRuleService( var senderRules: List = emptyList() var underrideRules: List = emptyList() + // TODO Create const for ruleSetKey monarchy.doWithRealm { realm -> PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "content").findFirst()?.let { re -> contentRules = re.pushRules.map { PushRulesMapper.mapContentRule(it) } @@ -132,6 +133,14 @@ internal class DefaultPushRuleService( return contentRules + overrideRules + roomRules + senderRules + underrideRules } + override fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback) { + updatePushRuleEnableStatusTask + .configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) + // TODO Fetch the rules + .dispatchTo(callback) + .executeBy(taskExecutor) + } + override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) { listeners.remove(listener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushrulesTask.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt new file mode 100644 index 0000000000..2deedcc944 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt @@ -0,0 +1,37 @@ +/* + * 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.api.pushrules.rest.PushRule +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task + + +internal interface UpdatePushRuleEnableStatusTask : Task { + data class Params(val kind: String, + val pushRule: PushRule, + val enabled: Boolean) +} + +internal class DefaultUpdatePushRuleEnableStatusTask(private val pushRulesApi: PushRulesApi) : UpdatePushRuleEnableStatusTask { + + override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params): Try { + return executeRequest { + apiCall = pushRulesApi.updateEnableRuleStatus(params.kind, params.pushRule.ruleId, params.enabled) + } + } +} \ No newline at end of file diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt index d306fa194e..ee4e59e4ab 100644 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -16,15 +16,11 @@ package im.vector.riotredesign.push.fcm import androidx.fragment.app.Fragment -import im.vector.fragments.troubleshoot.TestAccountSettings import im.vector.matrix.android.api.session.Session +import im.vector.riotredesign.features.settings.troubleshoot.* import im.vector.riotredesign.push.fcm.troubleshoot.TestFirebaseToken import im.vector.riotredesign.push.fcm.troubleshoot.TestPlayServices import im.vector.riotredesign.push.fcm.troubleshoot.TestTokenRegistration -import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager -import im.vector.riotredesign.features.settings.troubleshoot.TestBingRulesSettings -import im.vector.riotredesign.features.settings.troubleshoot.TestDeviceSettings -import im.vector.riotredesign.features.settings.troubleshoot.TestSystemSettings class NotificationTroubleshootTestManagerFactory { diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/troubleshoot/TestAccountSettings.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/troubleshoot/TestAccountSettings.kt index 7a5441bfc3..4580f097f9 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/troubleshoot/TestAccountSettings.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/troubleshoot/TestAccountSettings.kt @@ -13,24 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.fragments.troubleshoot +package im.vector.riotredesign.features.settings.troubleshoot import androidx.fragment.app.Fragment +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.pushrules.RuleIds import im.vector.matrix.android.api.session.Session import im.vector.riotredesign.R -import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest /** * Check that the main pushRule (RULE_ID_DISABLE_ALL) is correctly setup */ -class TestAccountSettings(val fragment: Fragment, val session: Session) : TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) { +class TestAccountSettings(val fragment: Fragment, val session: Session) + : TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) { override fun perform() { - /* - TODO - val defaultRule = session?.dataHandler?.bingRulesManager?.pushRules()?.findDefaultRule(BingRule.RULE_ID_DISABLE_ALL) + val defaultRule = session.getPushRules() + .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } + if (defaultRule != null) { - if (!defaultRule.isEnabled) { + if (!defaultRule.enabled) { description = fragment.getString(R.string.settings_troubleshoot_test_account_settings_success) quickFix = null status = TestStatus.SUCCESS @@ -39,14 +41,16 @@ class TestAccountSettings(val fragment: Fragment, val session: Session) : Troubl quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_account_settings_quickfix) { override fun doFix() { if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished - session?.dataHandler?.bingRulesManager?.updateEnableRuleStatus(defaultRule, !defaultRule.isEnabled, - object : BingRulesManager.onBingRuleUpdateListener { - override fun onBingRuleUpdateSuccess() { + // TODO Use constant for kind + session.updatePushRuleEnableStatus("override", defaultRule, !defaultRule.enabled, + object : MatrixCallback { + + override fun onSuccess(data: Unit) { manager?.retry() } - override fun onBingRuleUpdateFailure(errorMessage: String) { + override fun onFailure(failure: Throwable) { manager?.retry() } }) @@ -58,8 +62,5 @@ class TestAccountSettings(val fragment: Fragment, val session: Session) : Troubl //should not happen? status = TestStatus.FAILED } - */ - - status = TestStatus.FAILED } } \ No newline at end of file From 895f0f00794c94097f4a371a4f692d5d95f9a6ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Jun 2019 12:21:30 +0200 Subject: [PATCH 26/26] Fix compilation issue on FDroid --- .../push/fcm/NotificationTroubleshootTestManagerFactory.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt index 5663603b8f..0a3fdd227d 100644 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -16,12 +16,8 @@ package im.vector.riotredesign.push.fcm import androidx.fragment.app.Fragment -import im.vector.fragments.troubleshoot.TestAccountSettings import im.vector.matrix.android.api.session.Session -import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager -import im.vector.riotredesign.features.settings.troubleshoot.TestBingRulesSettings -import im.vector.riotredesign.features.settings.troubleshoot.TestDeviceSettings -import im.vector.riotredesign.features.settings.troubleshoot.TestSystemSettings +import im.vector.riotredesign.features.settings.troubleshoot.* import im.vector.riotredesign.push.fcm.troubleshoot.TestAutoStartBoot import im.vector.riotredesign.push.fcm.troubleshoot.TestBackgroundRestrictions