diff --git a/build.gradle b/build.gradle index 14d07f07..605bccbb 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ buildscript { classpath "com.airbnb.okreplay:gradle-plugin:1.4.0" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2' + classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/docs/notifications.md b/docs/notifications.md new file mode 100644 index 00000000..ad8a1c84 --- /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 sound = default/custom`). 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-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 ec6e1203..66ddc550 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 @@ -24,7 +24,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/build.gradle b/matrix-sdk-android/build.gradle index 0bb1dbfb..0e7e381e 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -32,10 +32,11 @@ android { minSdkVersion 16 targetSdkVersion 28 versionCode 1 - versionName "1.0" + versionName "0.0.1" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" resValue "string", "git_sdk_revision", "\"${gitRevision()}\"" resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\"" resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\"" diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt index 339b435b..7ff69358 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/RoomDataHelper.kt @@ -59,7 +59,7 @@ object RoomDataHelper { eventId = Random.nextLong().toString(), content = content, prevContent = prevContent, - sender = sender, + senderId = sender, stateKey = stateKey ) } 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 63fd6a34..1df81889 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,9 +21,8 @@ import androidx.lifecycle.ProcessLifecycleOwner import androidx.work.Configuration import androidx.work.WorkManager 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.session.Session -import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.di.DaggerMatrixComponent import im.vector.matrix.android.internal.network.UserAgentHolder @@ -94,6 +93,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return instance } + fun getSdkVersion(): String { + return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")" + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt index 19dc37e6..eaaeb730 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt @@ -47,6 +47,10 @@ object MatrixPatterns { private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+" private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V3_REGEX, Pattern.CASE_INSENSITIVE) + // Ref: https://matrix.org/docs/spec/rooms/v4#event-ids + private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+" + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V4_REGEX, Pattern.CASE_INSENSITIVE) + // regex pattern to find group ids in a string. private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX" private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) @@ -120,7 +124,9 @@ object MatrixPatterns { */ fun isEventId(str: String?): Boolean { return str != null - && (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches() || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches()) + && (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches() + || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches() + || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4.matcher(str).matches()) } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 8431239a..593c33b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.failure import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse import java.io.IOException /** @@ -31,7 +32,12 @@ import java.io.IOException sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { data class Unknown(val throwable: Throwable? = null) : Failure(throwable) data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException) - data class ServerError(val error: MatrixError) : Failure(RuntimeException(error.toString())) + data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString())) + // When server send an error, but it cannot be interpreted as a MatrixError + data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody)) + + data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString())) + data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString())) abstract class FeatureFailure : Failure() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt index 17ad30c4..15d56eac 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.api.session.events.model.Event */ object PermalinkFactory { - private val MATRIX_TO_URL_BASE = "https://matrix.to/#/" + const val MATRIX_TO_URL_BASE = "https://matrix.to/#/" /** * Creates a permalink for an event. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt index 19d37cf4..71fd16e7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt @@ -36,12 +36,20 @@ object PermalinkParser { * Turns an uri to a [PermalinkData] */ fun parse(uri: Uri): PermalinkData { + if (!uri.toString().startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) { + return PermalinkData.FallbackLink(uri) + } + val fragment = uri.fragment if (fragment.isNullOrEmpty()) { return PermalinkData.FallbackLink(uri) } + + val indexOfQuery = fragment.indexOf("?") + val safeFragment = if (indexOfQuery != -1) fragment.substring(0, indexOfQuery) else fragment + // we are limiting to 2 params - val params = fragment + val params = safeFragment .split(MatrixPatterns.SEP_REGEX.toRegex()) .filter { it.isNotEmpty() } .take(2) 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 00000000..797830f4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Action.kt @@ -0,0 +1,95 @@ +/* + * 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 + + 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}") + } + } + } else { + Timber.w("Unsupported action type ${actionStrOrObj}") + return null + } + } + return if (actions.isEmpty()) null else actions + } + } +} + 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 00000000..c0bb4f16 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/Condition.kt @@ -0,0 +1,48 @@ +/* + * 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 + +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(conditionResolver: ConditionResolver): Boolean + + 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/ConditionResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt new file mode 100644 index 00000000..4d15d5de --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/ConditionResolver.kt @@ -0,0 +1,28 @@ +/* + * 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 + +/** + * Acts like a visitor on Conditions. + * This class as all required context needed to evaluate rules + */ +interface ConditionResolver { + + 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 00000000..ce9f88e3 --- /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 new file mode 100644 index 00000000..7325abba --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt @@ -0,0 +1,97 @@ +/* + * 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(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 + 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/PushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt new file mode 100644 index 00000000..b00450b5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt @@ -0,0 +1,46 @@ +/* + * 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.MatrixCallback +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 + + fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback) + + fun addPushRuleListener(listener: PushRuleListener) + + fun removePushRuleListener(listener: PushRuleListener) + +// fun fulfilledBingRule(event: Event, rules: List): PushRule? + + 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/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RoomMemberCountCondition.kt new file mode 100644 index 00000000..b2bed4c2 --- /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/RuleIds.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt new file mode 100644 index 00000000..38a64adf --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RuleIds.kt @@ -0,0 +1,47 @@ +/* + * 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 + +/** + * 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/api/session/room/timeline/TimelineEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RulesetKey.kt similarity index 72% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEventInterceptor.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RulesetKey.kt index 3a4ff224..834bdf2a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEventInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/RulesetKey.kt @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package im.vector.matrix.android.api.session.room.timeline +package im.vector.matrix.android.api.pushrules -interface TimelineEventInterceptor { - - fun canEnrich(event: TimelineEvent): Boolean - - fun enrich(event: TimelineEvent) - -} - +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/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/SenderNotificationPermissionCondition.kt new file mode 100644 index 00000000..5f553f78 --- /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.senderId != null && powerLevels.getUserPowerLevel(event.senderId) >= powerLevels.notificationLevel(key) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/GetPushRulesResponse.kt similarity index 52% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/GetPushRulesResponse.kt index eab4cc45..8d567f94 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/GetPushRulesResponse.kt @@ -13,29 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package im.vector.matrix.android.api.pushrules.rest -package im.vector.matrix.android.api.session.room.timeline - -import androidx.paging.PagedList +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass /** - * This data class is a holder for timeline data. - * It's returned by [TimelineService] + * All push rulesets for a user. */ -data class TimelineData( +@JsonClass(generateAdapter = true) +data class GetPushRulesResponse( + /** + * Global rules, account level applying to all devices + */ + @Json(name = "global") + val global: Ruleset, /** - * The [PagedList] of [TimelineEvent] to usually be render in a RecyclerView. + * Device specific rules, apply only to current device */ - val events: PagedList, - - /** - * True if Timeline is currently paginating forward on server - */ - val isLoadingForward: Boolean = false, - - /** - * True if Timeline is currently paginating backward on server - */ - val isLoadingBackward: Boolean = false + @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 new file mode 100644 index 00000000..8e63e864 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushCondition.kt @@ -0,0 +1,78 @@ +/* + * 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.* +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 -> { + 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 new file mode 100644 index 00000000..1e36a0d8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/PushRule.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.api.pushrules.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + + +@JsonClass(generateAdapter = true) +data class PushRule( + /** + * 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. + */ + val default: Boolean? = false, + /** + * 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/Ruleset.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/rest/Ruleset.kt new file mode 100644 index 00000000..5f4ca15a --- /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 6f8d745e..ac412678 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,6 +60,20 @@ interface Session : @MainThread fun open() + /** + * Requires a one time background sync + */ + fun requireBackgroundSync() + + /** + * 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 startAutomaticBackgroundSync(repeatDelay: Long = 30_000L) + + fun stopAnyBackgroundSync() + /** * This method start the sync thread. */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index e7c3c445..0f978413 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -36,7 +36,9 @@ interface CryptoService { fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback) - fun deleteDevice(deviceId: String, accountPassword: String, callback: MatrixCallback) + fun deleteDevice(deviceId: String, callback: MatrixCallback) + + fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback) fun getCryptoVersion(context: Context, longFormat: Boolean): String diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index fe68e9a2..99479d87 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -71,12 +71,11 @@ data class Event( @Json(name = "content") val content: Content? = null, @Json(name = "prev_content") val prevContent: Content? = null, @Json(name = "origin_server_ts") val originServerTs: Long? = null, - @Json(name = "sender") val sender: String? = null, + @Json(name = "sender") val senderId: String? = null, @Json(name = "state_key") val stateKey: String? = null, @Json(name = "room_id") val roomId: String? = null, @Json(name = "unsigned") val unsignedData: UnsignedData? = null, @Json(name = "redacts") val redacts: 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 new file mode 100644 index 00000000..7b1fa3bb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/Pusher.kt @@ -0,0 +1,45 @@ +/* + * 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 { + UNREGISTERED, + REGISTERING, + UNREGISTERING, + 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 00000000..21b2464d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt @@ -0,0 +1,64 @@ +/* + * 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 im.vector.matrix.android.api.MatrixCallback +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 + + + fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback) + + 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/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index fe6a5443..3e893a09 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/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index 59054dc1..7c21695e 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/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 b47e7ba2..2ec393a0 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/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt index ac08b64f..bef89839 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt @@ -63,19 +63,6 @@ interface RelationService { fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable - /** - * Update a quick reaction (toggle). - * If you have reacted with agree and then you click on disagree, this call will delete(redact) - * the disagree and add the agree - * If you click on a reaction that you already reacted with, it will undo it - * @param reaction the reaction (preferably emoji) - * @param oppositeReaction the opposite reaction(preferably emoji) - * @param targetEventId the id of the event being reacted - * @param myUserId used to know if a reaction event was made by the user - */ - fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) - - /** * Edit a text message body. Limited to "m.text" contentType * @param targetEventId The event to edit 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 ed67c1db..ab406b8e 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/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt index 2c2530bb..5f387926 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt @@ -32,7 +32,7 @@ package im.vector.matrix.android.api.session.room.timeline */ interface Timeline { - var listener: Timeline.Listener? + var listener: Listener? /** * This should be called before any other method after creating the timeline. It ensures the underlying database is open diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 9ba481a4..3341d87e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -19,12 +19,11 @@ package im.vector.matrix.android.api.session.room.timeline 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.room.model.EventAnnotationsSummary -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.send.SendState /** * This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline. - * This class is used by [TimelineService] through [TimelineData] + * This class is used by [TimelineService] * Users can also enrich it with metadata. */ data class TimelineEvent( @@ -32,6 +31,7 @@ data class TimelineEvent( val localId: String, val displayIndex: Int, val senderName: String?, + val isUniqueDisplayName: Boolean, val senderAvatar: String?, val sendState: SendState, val annotations: EventAnnotationsSummary? = null @@ -54,6 +54,18 @@ data class TimelineEvent( } } + fun getDisambiguatedDisplayName(): String { + return if (isUniqueDisplayName) { + senderName + } else { + senderName?.let { name -> + "$name (${root.senderId})" + } + } + ?: root.senderId + ?: "" + } + /** * Get the metadata associated with a key. * @param key the key to get the metadata @@ -63,7 +75,8 @@ data class TimelineEvent( return metadata[key] as T? } - fun isEncrypted() : Boolean { - return EventType.ENCRYPTED == root.getClearType() + fun isEncrypted(): Boolean { + // warning: Do not use getClearType here + return EventType.ENCRYPTED == root.type } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Try.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Try.kt deleted file mode 100644 index 365d5472..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Try.kt +++ /dev/null @@ -1,30 +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.api.util - -import arrow.core.* - -inline fun TryOf.onError(f: (Throwable) -> Unit): Try = fix() - .fold( - { - f(it) - Failure(it) - }, - { Success(it) } - ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt index 3eb8f113..81e64f5b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt @@ -27,8 +27,8 @@ import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.ThreePidMedium -import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.di.Unauthenticated +import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.util.CancelableCoroutine @@ -37,7 +37,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient -import retrofit2.Retrofit import javax.inject.Inject internal class DefaultAuthenticator @Inject constructor(@Unauthenticated @@ -70,7 +69,7 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated val job = GlobalScope.launch(coroutineDispatchers.main) { val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password) - sessionOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) }) + sessionOrFailure.foldToCallback(callback) } return CancelableCoroutine(job) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/InteractiveAuthenticationFlow.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/InteractiveAuthenticationFlow.kt new file mode 100644 index 00000000..ae75b273 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/InteractiveAuthenticationFlow.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.auth.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * An interactive authentication flow. + */ +@JsonClass(generateAdapter = true) +data class InteractiveAuthenticationFlow( + + @Json(name = "type") + val type: String? = null, + + @Json(name = "stages") + val stages: List? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowResponse.kt index a5062181..834b0aee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowResponse.kt @@ -16,7 +16,11 @@ package im.vector.matrix.android.internal.auth.data +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -internal data class LoginFlowResponse(val flows: List) \ No newline at end of file +internal data class LoginFlowResponse( + @Json(name = "flows") + val flows: List +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt index 2cf450d2..1e129f1b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlowTypes.kt @@ -16,7 +16,7 @@ package im.vector.matrix.android.internal.auth.data -internal object LoginFlowTypes { +object LoginFlowTypes { const val PASSWORD = "m.login.password" const val OAUTH2 = "m.login.oauth2" const val EMAIL_CODE = "m.login.email.code" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt new file mode 100644 index 00000000..0eb7b05b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt @@ -0,0 +1,53 @@ +/* + * 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.auth.registration + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.util.JsonDict +import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow + +@JsonClass(generateAdapter = true) +data class RegistrationFlowResponse( + + /** + * The list of flows. + */ + @Json(name = "flows") + var flows: List? = null, + + /** + * The list of stages the client has completed successfully. + */ + @Json(name = "completed") + var completedStages: List? = null, + + /** + * The session identifier that the client must pass back to the home server, if one is provided, + * in subsequent attempts to authenticate in the same API call. + */ + @Json(name = "session") + var session: String? = null, + + /** + * The information that the client will need to know in order to use a given type of authentication. + * For each login stage type presented, that type may be present as a key in this dictionary. + * For example, the public key of reCAPTCHA stage could be given here. + */ + @Json(name = "params") + var params: JsonDict? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoAsyncHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoAsyncHelper.kt deleted file mode 100644 index 411bcbf6..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoAsyncHelper.kt +++ /dev/null @@ -1,61 +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.crypto - -import android.os.Handler -import android.os.HandlerThread -import android.os.Looper - -private const val THREAD_CRYPTO_NAME = "Crypto_Thread" - -// TODO Remove and replace by Task -internal object CryptoAsyncHelper { - - private var uiHandler: Handler? = null - private var cryptoBackgroundHandler: Handler? = null - - fun getUiHandler(): Handler { - return uiHandler - ?: Handler(Looper.getMainLooper()) - .also { uiHandler = it } - } - - - fun getDecryptBackgroundHandler(): Handler { - return getCryptoBackgroundHandler() - } - - fun getEncryptBackgroundHandler(): Handler { - return getCryptoBackgroundHandler() - } - - private fun getCryptoBackgroundHandler(): Handler { - return cryptoBackgroundHandler - ?: createCryptoBackgroundHandler() - .also { cryptoBackgroundHandler = it } - } - - private fun createCryptoBackgroundHandler(): Handler { - val handlerThread = HandlerThread(THREAD_CRYPTO_NAME) - handlerThread.start() - return Handler(handlerThread.looper) - } - - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index 5213b6c7..01506b71 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -19,8 +19,11 @@ package im.vector.matrix.android.internal.crypto import android.content.Context +import android.os.Handler +import android.os.Looper import android.text.TextUtils import arrow.core.Try +import com.squareup.moshi.Types import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials @@ -55,15 +58,13 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask -import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask -import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask -import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask +import im.vector.matrix.android.internal.crypto.tasks.* import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService -import im.vector.matrix.android.internal.di.CryptoDatabase import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.di.CryptoDatabase import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.cache.ClearCacheTask import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask @@ -128,6 +129,7 @@ internal class CryptoManager @Inject constructor( private val megolmEncryptionFactory: MXMegolmEncryptionFactory, private val olmEncryptionFactory: MXOlmEncryptionFactory, private val deleteDeviceTask: DeleteDeviceTask, + private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask, // Tasks private val getDevicesTask: GetDevicesTask, private val setDeviceNameTask: SetDeviceNameTask, @@ -139,6 +141,8 @@ internal class CryptoManager @Inject constructor( private val taskExecutor: TaskExecutor ) : CryptoService { + private val uiHandler = Handler(Looper.getMainLooper()) + // MXEncrypting instance for each room. private val roomEncryptors: MutableMap = HashMap() private val isStarting = AtomicBoolean(false) @@ -167,9 +171,16 @@ internal class CryptoManager @Inject constructor( .executeBy(taskExecutor) } - override fun deleteDevice(deviceId: String, accountPassword: String, callback: MatrixCallback) { + override fun deleteDevice(deviceId: String, callback: MatrixCallback) { deleteDeviceTask - .configureWith(DeleteDeviceTask.Params(deviceId, accountPassword)) + .configureWith(DeleteDeviceTask.Params(deviceId)) + .dispatchTo(callback) + .executeBy(taskExecutor) + } + + override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback) { + deleteDeviceWithUserPasswordTask + .configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) .dispatchTo(callback) .executeBy(taskExecutor) } @@ -555,10 +566,10 @@ internal class CryptoManager @Inject constructor( } else { val algorithm = getEncryptionAlgorithm(roomId) val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, - algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) + algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) Timber.e("## encryptEventContent() : $reason") callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, reason))) + MXCryptoError.UNABLE_TO_ENCRYPT, reason))) } } } @@ -592,10 +603,7 @@ internal class CryptoManager @Inject constructor( val result = withContext(coroutineDispatchers.crypto) { internalDecryptEvent(event, timeline) } - result.fold( - { callback.onFailure(it) }, - { callback.onSuccess(it) } - ) + result.foldToCallback(callback) } } @@ -695,7 +703,7 @@ internal class CryptoManager @Inject constructor( monarchy.doWithRealm { realm -> // Check whether the event content must be encrypted for the invited members. val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() - && shouldEncryptForInvitedMembers(roomId) + && shouldEncryptForInvitedMembers(roomId) userIds = if (encryptForInvitedMembers) { RoomMembers(realm, roomId).getActiveRoomMemberIds() @@ -782,35 +790,31 @@ internal class CryptoManager @Inject constructor( * @param anIterationCount the encryption iteration count (0 means no encryption) * @param callback the exported keys */ - fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback) { - val iterationCount = Math.max(0, anIterationCount) + private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback) { + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + val iterationCount = Math.max(0, anIterationCount) - val exportedSessions = ArrayList() + val exportedSessions = ArrayList() - val inboundGroupSessions = cryptoStore.getInboundGroupSessions() + val inboundGroupSessions = cryptoStore.getInboundGroupSessions() - for (session in inboundGroupSessions) { - val megolmSessionData = session.exportKeys() + for (session in inboundGroupSessions) { + val megolmSessionData = session.exportKeys() - if (null != megolmSessionData) { - exportedSessions.add(megolmSessionData) - } + if (null != megolmSessionData) { + exportedSessions.add(megolmSessionData) + } + } + + val adapter = MoshiProvider.providesMoshi() + .adapter(List::class.java) + + MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount) + } + }.foldToCallback(callback) } - - val encryptedRoomKeys: ByteArray - - try { - val adapter = MoshiProvider.providesMoshi() - .adapter(List::class.java) - - encryptedRoomKeys = MXMegolmExportEncryption - .encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount) - } catch (e: Exception) { - callback.onFailure(e) - return - } - - callback.onSuccess(encryptedRoomKeys) } /** @@ -825,40 +829,33 @@ internal class CryptoManager @Inject constructor( password: String, progressListener: ProgressListener?, callback: MatrixCallback) { - Timber.v("## importRoomKeys starts") + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + Timber.v("## importRoomKeys starts") - val t0 = System.currentTimeMillis() - val roomKeys: String + val t0 = System.currentTimeMillis() + val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) + val t1 = System.currentTimeMillis() - try { - roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) - } catch (e: Exception) { - callback.onFailure(e) - return + Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms") + + val importedSessions = MoshiProvider.providesMoshi() + .adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) + .fromJson(roomKeys) + + val t2 = System.currentTimeMillis() + + Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms") + + if (importedSessions == null) { + throw Exception("Error") + } + + megolmSessionDataImporter.handle(importedSessions, true, uiHandler, progressListener) + } + }.foldToCallback(callback) } - - val importedSessions: List - - val t1 = System.currentTimeMillis() - - Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms") - - try { - val list = MoshiProvider.providesMoshi() - .adapter(List::class.java) - .fromJson(roomKeys) - importedSessions = list as List - } catch (e: Exception) { - Timber.e(e, "## importRoomKeys failed") - callback.onFailure(e) - return - } - - val t2 = System.currentTimeMillis() - - Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms") - - megolmSessionDataImporter.handle(importedSessions, true, progressListener, callback) } /** @@ -893,7 +890,7 @@ internal class CryptoManager @Inject constructor( // trigger an an unknown devices exception callback.onFailure( Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) + MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) } } ) @@ -1056,10 +1053,7 @@ internal class CryptoManager @Inject constructor( CoroutineScope(coroutineDispatchers.crypto).launch { deviceListManager .downloadKeys(userIds, forceDownload) - .fold( - { callback.onFailure(it) }, - { callback.onSuccess(it) } - ) + .foldToCallback(callback) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index f5ed61b6..89baf1c6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -24,53 +24,13 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteBackupTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule import im.vector.matrix.android.internal.crypto.store.db.hash -import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask -import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice -import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask -import im.vector.matrix.android.internal.crypto.tasks.DefaultDownloadKeysForUsers -import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask -import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask -import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask -import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadKeysTask -import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask -import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask -import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask -import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask +import im.vector.matrix.android.internal.crypto.tasks.* import im.vector.matrix.android.internal.di.CryptoDatabase import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.cache.ClearCacheTask @@ -103,17 +63,19 @@ internal abstract class CryptoModule { @JvmStatic @Provides @CryptoDatabase - fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask { + fun providesClearCacheTask(@CryptoDatabase + realmConfiguration: RealmConfiguration): ClearCacheTask { return RealmClearCacheTask(realmConfiguration) } @JvmStatic @Provides - fun providesCryptoStore(@CryptoDatabase realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore { + fun providesCryptoStore(@CryptoDatabase + realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore { return RealmCryptoStore(false /* TODO*/, - realmConfiguration, - credentials) + realmConfiguration, + credentials) } @JvmStatic @@ -176,36 +138,37 @@ internal abstract class CryptoModule { abstract fun bindGetKeysBackupLastVersionTask(getKeysBackupLastVersionTask: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask @Binds - abstract fun bindGetKeysBackupVersionTask(getKeysBackupVersionTask: DefaultGetKeysBackupVersionTask) : GetKeysBackupVersionTask + abstract fun bindGetKeysBackupVersionTask(getKeysBackupVersionTask: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask @Binds - abstract fun bindGetRoomSessionDataTask(getRoomSessionDataTask: DefaultGetRoomSessionDataTask) : GetRoomSessionDataTask + abstract fun bindGetRoomSessionDataTask(getRoomSessionDataTask: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask @Binds - abstract fun bindGetRoomSessionsDataTask(getRoomSessionDataTask: DefaultGetRoomSessionsDataTask) : GetRoomSessionsDataTask + abstract fun bindGetRoomSessionsDataTask(getRoomSessionDataTask: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask @Binds - abstract fun bindGetSessionsDataTask(getRoomSessionDataTask: DefaultGetSessionsDataTask) : GetSessionsDataTask + abstract fun bindGetSessionsDataTask(getRoomSessionDataTask: DefaultGetSessionsDataTask): GetSessionsDataTask @Binds - abstract fun bindStoreRoomSessionDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionDataTask) : StoreRoomSessionDataTask + abstract fun bindStoreRoomSessionDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask @Binds - abstract fun bindStoreRoomSessionsDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionsDataTask) : StoreRoomSessionsDataTask + abstract fun bindStoreRoomSessionsDataTask(storeRoomSessionDataTask: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask @Binds - abstract fun bindStoreSessionsDataTask(storeRoomSessionDataTask: DefaultStoreSessionsDataTask) : StoreSessionsDataTask + abstract fun bindStoreSessionsDataTask(storeRoomSessionDataTask: DefaultStoreSessionsDataTask): StoreSessionsDataTask @Binds - abstract fun bindUpdateKeysBackupVersionTask(updateKeysBackupVersionTask: DefaultUpdateKeysBackupVersionTask) : UpdateKeysBackupVersionTask + abstract fun bindUpdateKeysBackupVersionTask(updateKeysBackupVersionTask: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask @Binds - abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask) : SendToDeviceTask + abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask @Binds - abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice) : ClaimOneTimeKeysForUsersDeviceTask - + abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask + @Binds + abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt index d6450f9c..5d471d5a 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt @@ -21,7 +21,7 @@ import android.text.TextUtils import arrow.core.Try import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.api.util.onError +import im.vector.matrix.android.internal.extensions.onError import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt index 6587f546..056d9942 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt @@ -65,7 +65,7 @@ open class IncomingRoomKeyRequest { * @param event the event */ constructor(event: Event) { - userId = event.sender + userId = event.senderId val roomKeyShareRequest = event.getClearContent().toModel()!! deviceId = roomKeyShareRequest.requestingDeviceId requestId = roomKeyShareRequest.requestId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt index 6a991018..82b2c95d 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto import android.text.TextUtils import android.util.Base64 +import im.vector.matrix.android.internal.extensions.toUnsignedInt import timber.log.Timber import java.io.ByteArrayOutputStream import java.nio.charset.Charset @@ -43,16 +44,6 @@ object MXMegolmExportEncryption { // default iteration count to export the e2e keys const val DEFAULT_ITERATION_COUNT = 500000 - /** - * Convert a signed byte to a int value - * - * @param bVal the byte value to convert - * @return the matched int value - */ - private fun byteToInt(bVal: Byte): Int { - return (bVal and 0xFF.toByte()).toInt() - } - /** * Extract the AES key from the deriveKeys result. * @@ -108,7 +99,8 @@ object MXMegolmExportEncryption { val salt = Arrays.copyOfRange(body, 1, 1 + 16) val iv = Arrays.copyOfRange(body, 17, 17 + 16) - val iterations = byteToInt(body[33]) shl 24 or (byteToInt(body[34]) shl 16) or (byteToInt(body[35]) shl 8) or byteToInt(body[36]) + val iterations = + (body[33].toUnsignedInt() shl 24) or (body[34].toUnsignedInt() shl 16) or (body[35].toUnsignedInt() shl 8) or body[36].toUnsignedInt() val ciphertext = Arrays.copyOfRange(body, 37, 37 + ciphertextLength) val hmac = Arrays.copyOfRange(body, body.size - 32, body.size) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt index 34124cec..918bdd73 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -16,9 +16,9 @@ package im.vector.matrix.android.internal.crypto.actions -import im.vector.matrix.android.api.MatrixCallback +import android.os.Handler +import androidx.annotation.WorkerThread import im.vector.matrix.android.api.listeners.ProgressListener -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MegolmSessionData import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager @@ -37,34 +37,32 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi /** * Import a list of megolm session keys. + * Must be call on the crypto coroutine thread * * @param megolmSessionsData megolm sessions. * @param backUpKeys true to back up them to the homeserver. * @param progressListener the progress listener - * @param callback + * @return import room keys result */ + @WorkerThread fun handle(megolmSessionsData: List, fromBackup: Boolean, - progressListener: ProgressListener?, - callback: MatrixCallback) { + uiHandler: Handler, + progressListener: ProgressListener?): ImportRoomKeysResult { val t0 = System.currentTimeMillis() val totalNumbersOfKeys = megolmSessionsData.size - var cpt = 0 var lastProgress = 0 var totalNumbersOfImportedKeys = 0 if (progressListener != null) { - CryptoAsyncHelper.getUiHandler().post { + uiHandler.post { progressListener.onProgress(0, 100) } } val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData) - for (megolmSessionData in megolmSessionsData) { - cpt++ - - + megolmSessionsData.forEachIndexed { cpt, megolmSessionData -> val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm) if (null != decrypting) { @@ -92,7 +90,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi } if (progressListener != null) { - CryptoAsyncHelper.getUiHandler().post { + uiHandler.post { val progress = 100 * cpt / totalNumbersOfKeys if (lastProgress != progress) { @@ -113,10 +111,6 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)") - val finalTotalNumbersOfImportedKeys = totalNumbersOfImportedKeys - - CryptoAsyncHelper.getUiHandler().post { - callback.onSuccess(ImportRoomKeysResult(totalNumbersOfKeys, finalTotalNumbersOfImportedKeys)) - } + return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 6ba5ddaf..796d91d1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -131,7 +131,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials, * @param event the event */ private fun requestKeysForEvent(event: Event) { - val sender = event.sender!! + val sender = event.senderId!! val encryptedEventContent = event.content.toModel()!! val recipients = ArrayList>() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt index 13292e27..1a00188c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt @@ -121,9 +121,9 @@ internal class MXOlmDecryption( MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))) } - if (!TextUtils.equals(olmPayloadContent.sender, event.sender)) { + if (!TextUtils.equals(olmPayloadContent.sender, event.senderId)) { Timber.e("Event " + event.eventId + ": original sender " + olmPayloadContent.sender - + " does not match reported sender " + event.sender) + + " does not match reported sender " + event.senderId) throw MXDecryptionException(MXCryptoError(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index 673fd6db..bd52c3d4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -31,7 +31,10 @@ import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener -import im.vector.matrix.android.internal.crypto.* +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import im.vector.matrix.android.internal.crypto.MXOlmDevice +import im.vector.matrix.android.internal.crypto.MegolmSessionData +import im.vector.matrix.android.internal.crypto.ObjectSigner import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature @@ -47,11 +50,16 @@ import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrap import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.Task 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 im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.olm.OlmException import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkEncryption @@ -91,7 +99,8 @@ internal class KeysBackup @Inject constructor( private val storeSessionDataTask: StoreSessionsDataTask, private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, // Task executor - private val taskExecutor: TaskExecutor + private val taskExecutor: TaskExecutor, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : KeysBackupService { private val uiHandler = Handler(Looper.getMainLooper()) @@ -134,55 +143,53 @@ internal class KeysBackup @Inject constructor( override fun prepareKeysBackupVersion(password: String?, progressListener: ProgressListener?, callback: MatrixCallback) { - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - try { - val olmPkDecryption = OlmPkDecryption() - val megolmBackupAuthData = MegolmBackupAuthData() + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + val olmPkDecryption = OlmPkDecryption() + val megolmBackupAuthData = MegolmBackupAuthData() - if (password != null) { - // Generate a private key from the password - val backgroundProgressListener = if (progressListener == null) { - null - } else { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - try { - progressListener.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "prepareKeysBackupVersion: onProgress failure") + if (password != null) { + // Generate a private key from the password + val backgroundProgressListener = if (progressListener == null) { + null + } else { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + uiHandler.post { + try { + progressListener.onProgress(progress, total) + } catch (e: Exception) { + Timber.e(e, "prepareKeysBackupVersion: onProgress failure") + } } } } } + + val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) + megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey) + megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt + megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations + } else { + val publicKey = olmPkDecryption.generateKey() + + megolmBackupAuthData.publicKey = publicKey } - val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) - megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey) - megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt - megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations - } else { - val publicKey = olmPkDecryption.generateKey() + val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) - megolmBackupAuthData.publicKey = publicKey + megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson) + + + val megolmBackupCreationInfo = MegolmBackupCreationInfo() + megolmBackupCreationInfo.algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP + megolmBackupCreationInfo.authData = megolmBackupAuthData + megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) + + megolmBackupCreationInfo } - - val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) - - megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson) - - - val megolmBackupCreationInfo = MegolmBackupCreationInfo() - megolmBackupCreationInfo.algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP - megolmBackupCreationInfo.authData = megolmBackupAuthData - megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) - - uiHandler.post { callback.onSuccess(megolmBackupCreationInfo) } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - - uiHandler.post { callback.onFailure(e) } - } + }.foldToCallback(callback) } } @@ -225,37 +232,39 @@ internal class KeysBackup @Inject constructor( } override fun deleteBackup(version: String, callback: MatrixCallback?) { - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - // If we're currently backing up to this backup... stop. - // (We start using it automatically in createKeysBackupVersion so this is symmetrical). - if (keysBackupVersion != null && version == keysBackupVersion!!.version) { - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Unknown - } + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + // If we're currently backing up to this backup... stop. + // (We start using it automatically in createKeysBackupVersion so this is symmetrical). + if (keysBackupVersion != null && version == keysBackupVersion!!.version) { + resetKeysBackupData() + keysBackupVersion = null + keysBackupStateManager.state = KeysBackupState.Unknown + } - deleteBackupTask.configureWith(DeleteBackupTask.Params(version)) - .dispatchTo(object : MatrixCallback { - private fun eventuallyRestartBackup() { - // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver - if (state == KeysBackupState.Unknown) { - checkAndStartKeysBackup() + deleteBackupTask.configureWith(DeleteBackupTask.Params(version)) + .dispatchTo(object : MatrixCallback { + private fun eventuallyRestartBackup() { + // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver + if (state == KeysBackupState.Unknown) { + checkAndStartKeysBackup() + } } - } - override fun onSuccess(data: Unit) { - eventuallyRestartBackup() + override fun onSuccess(data: Unit) { + eventuallyRestartBackup() - uiHandler.post { callback?.onSuccess(Unit) } - } + uiHandler.post { callback?.onSuccess(Unit) } + } - override fun onFailure(failure: Throwable) { - eventuallyRestartBackup() + override fun onFailure(failure: Throwable) { + eventuallyRestartBackup() - uiHandler.post { callback?.onFailure(failure) } - } - }) - .executeBy(taskExecutor) + uiHandler.post { callback?.onFailure(failure) } + } + }) + .executeBy(taskExecutor) + } } } @@ -430,85 +439,81 @@ internal class KeysBackup @Inject constructor( callback: MatrixCallback) { Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val myUserId = credentials.userId + // Get auth data to update it + val authData = getMegolmBackupAuthData(keysBackupVersion) - // Get auth data to update it - val authData = getMegolmBackupAuthData(keysBackupVersion) + if (authData == null) { + Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") - if (authData == null) { - Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") + callback.onFailure(IllegalArgumentException("Missing element")) + } else { + GlobalScope.launch(coroutineDispatchers.main) { + val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { + val myUserId = credentials.userId - uiHandler.post { - callback.onFailure(IllegalArgumentException("Missing element")) + // Get current signatures, or create an empty set + val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() + ?: HashMap()) + + if (trust) { + // Add current device signature + val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + + val deviceSignatures = objectSigner.signObject(canonicalJson) + + deviceSignatures[myUserId]?.forEach { entry -> + myUserSignatures[entry.key] = entry.value + } + } else { + // Remove current device signature + myUserSignatures.remove("ed25519:${credentials.deviceId}") + } + + // Create an updated version of KeysVersionResult + val updateKeysBackupVersionBody = UpdateKeysBackupVersionBody(keysBackupVersion.version!!) + + updateKeysBackupVersionBody.algorithm = keysBackupVersion.algorithm + + val newMegolmBackupAuthData = authData.copy() + + val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() + newSignatures[myUserId] = myUserSignatures + + newMegolmBackupAuthData.signatures = newSignatures + + val moshi = MoshiProvider.providesMoshi() + val adapter = moshi.adapter(Map::class.java) + + updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map? + + updateKeysBackupVersionBody } - return@post - } + // And send it to the homeserver + updateKeysBackupVersionTask + .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: Unit) { + // Relaunch the state machine on this updated backup version + val newKeysBackupVersion = KeysVersionResult() - // Get current signatures, or create an empty set - val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() ?: HashMap()) + newKeysBackupVersion.version = keysBackupVersion.version + newKeysBackupVersion.algorithm = keysBackupVersion.algorithm + newKeysBackupVersion.count = keysBackupVersion.count + newKeysBackupVersion.hash = keysBackupVersion.hash + newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData - if (trust) { - // Add current device signature - val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + checkAndStartWithKeysBackupVersion(newKeysBackupVersion) - val deviceSignatures = objectSigner.signObject(canonicalJson) - - deviceSignatures[myUserId]?.forEach { entry -> - myUserSignatures[entry.key] = entry.value - } - } else { - // Remove current device signature - myUserSignatures.remove("ed25519:${credentials.deviceId}") - } - - // Create an updated version of KeysVersionResult - val updateKeysBackupVersionBody = UpdateKeysBackupVersionBody(keysBackupVersion.version!!) - - updateKeysBackupVersionBody.algorithm = keysBackupVersion.algorithm - - val newMegolmBackupAuthData = authData.copy() - - val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() - newSignatures[myUserId] = myUserSignatures - - newMegolmBackupAuthData.signatures = newSignatures - - val moshi = MoshiProvider.providesMoshi() - val adapter = moshi.adapter(Map::class.java) - - - updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map? - - // And send it to the homeserver - updateKeysBackupVersionTask - .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: Unit) { - // Relaunch the state machine on this updated backup version - val newKeysBackupVersion = KeysVersionResult() - - newKeysBackupVersion.version = keysBackupVersion.version - newKeysBackupVersion.algorithm = keysBackupVersion.algorithm - newKeysBackupVersion.count = keysBackupVersion.count - newKeysBackupVersion.hash = keysBackupVersion.hash - newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData - - checkAndStartWithKeysBackupVersion(newKeysBackupVersion) - - uiHandler.post { callback.onSuccess(data) } - } - override fun onFailure(failure: Throwable) { - uiHandler.post { + override fun onFailure(failure: Throwable) { callback.onFailure(failure) } - } - }) - .executeBy(taskExecutor) + }) + .executeBy(taskExecutor) + } } } @@ -517,17 +522,18 @@ internal class KeysBackup @Inject constructor( callback: MatrixCallback) { Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { - Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") - - uiHandler.post { - callback.onFailure(IllegalArgumentException("Invalid recovery key or password")) - } - return@post + GlobalScope.launch(coroutineDispatchers.main) { + val isValid = withContext(coroutineDispatchers.crypto) { + isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion) } - trustKeysBackupVersion(keysBackupVersion, true, callback) + if (!isValid) { + Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") + + callback.onFailure(IllegalArgumentException("Invalid recovery key or password")) + } else { + trustKeysBackupVersion(keysBackupVersion, true, callback) + } } } @@ -536,21 +542,19 @@ internal class KeysBackup @Inject constructor( callback: MatrixCallback) { Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, null) + GlobalScope.launch(coroutineDispatchers.main) { + val recoveryKey = withContext(coroutineDispatchers.crypto) { + recoveryKeyFromPassword(password, keysBackupVersion, null) + } if (recoveryKey == null) { Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data") - uiHandler.post { - callback.onFailure(IllegalArgumentException("Missing element")) - } - - return@post + callback.onFailure(IllegalArgumentException("Missing element")) + } else { + // Check trust using the recovery key + trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback) } - - // Check trust using the recovery key - trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback) } } @@ -595,12 +599,10 @@ internal class KeysBackup @Inject constructor( } override fun getBackupProgress(progressListener: ProgressListener) { - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) - val total = cryptoStore.inboundGroupSessionsCount(false) + val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) + val total = cryptoStore.inboundGroupSessionsCount(false) - uiHandler.post { progressListener.onProgress(backedUpKeys, total) } - } + progressListener.onProgress(backedUpKeys, total) } override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, @@ -611,88 +613,95 @@ internal class KeysBackup @Inject constructor( callback: MatrixCallback) { Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post(Runnable { - // Check if the recovery is valid before going any further - if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") - uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) } - return@Runnable - } - - // Get a PK decryption instance - val decryption = pkDecryptionFromRecoveryKey(recoveryKey) - if (decryption == null) { - // This should not happen anymore - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error") - uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) } - return@Runnable - } - - if (stepProgressListener != null) { - uiHandler.post { stepProgressListener.onStepProgress(StepProgressListener.Step.DownloadingKey) } - } - - // Get backed up keys from the homeserver - getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback { - override fun onSuccess(data: KeysBackupData) { - val sessionsData = ArrayList() - // Restore that data - var sessionsFromHsCount = 0 - for (roomIdLoop in data.roomIdToRoomKeysBackupData.keys) { - for (sessionIdLoop in data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData.keys) { - sessionsFromHsCount++ - - val keyBackupData = data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData[sessionIdLoop]!! - - val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption) - - sessionData?.let { - sessionsData.add(it) - } - } - } - Timber.v("restoreKeysWithRecoveryKey: Decrypted " + sessionsData.size + " keys out of " - + sessionsFromHsCount + " from the backup store on the homeserver") - - // Do not trigger a backup for them if they come from the backup version we are using - val backUp = keysVersionResult.version != keysBackupVersion?.version - if (backUp) { - Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up to backup version: " + keysBackupVersion?.version) + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + // Check if the recovery is valid before going any further + if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { + Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") + throw InvalidParameterException("Invalid recovery key") } - // Import them into the crypto store - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - // Note: no need to post to UI thread, importMegolmSessionsData() will do it - stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) - } - } - } else { - null + // Get a PK decryption instance + val decryption = pkDecryptionFromRecoveryKey(recoveryKey) + if (decryption == null) { + // This should not happen anymore + Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error") + throw InvalidParameterException("Invalid recovery key") } - megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener, object : MatrixCallback { - override fun onSuccess(data: ImportRoomKeysResult) { - // Do not back up the key if it comes from a backup recovery - if (backUp) { - maybeBackupKeys() - } - - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - }) + decryption!! } + }.fold( + { + callback.onFailure(it) + }, + { decryption -> + stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) - override fun onFailure(failure: Throwable) { - uiHandler.post { callback.onFailure(failure) } - } - }) - }) + // Get backed up keys from the homeserver + getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback { + override fun onSuccess(data: KeysBackupData) { + GlobalScope.launch(coroutineDispatchers.main) { + val importRoomKeysResult = withContext(coroutineDispatchers.crypto) { + val sessionsData = ArrayList() + // Restore that data + var sessionsFromHsCount = 0 + for (roomIdLoop in data.roomIdToRoomKeysBackupData.keys) { + for (sessionIdLoop in data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData.keys) { + sessionsFromHsCount++ + + val keyBackupData = data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData[sessionIdLoop]!! + + val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption) + + sessionData?.let { + sessionsData.add(it) + } + } + } + Timber.v("restoreKeysWithRecoveryKey: Decrypted " + sessionsData.size + " keys out of " + + sessionsFromHsCount + " from the backup store on the homeserver") + + // Do not trigger a backup for them if they come from the backup version we are using + val backUp = keysVersionResult.version != keysBackupVersion?.version + if (backUp) { + Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up to backup version: " + keysBackupVersion?.version) + } + + // Import them into the crypto store + val progressListener = if (stepProgressListener != null) { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + // Note: no need to post to UI thread, importMegolmSessionsData() will do it + stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) + } + } + } else { + null + } + + val result = megolmSessionDataImporter.handle(sessionsData, !backUp, uiHandler, progressListener) + + // Do not back up the key if it comes from a backup recovery + if (backUp) { + maybeBackupKeys() + } + + result + } + + callback.onSuccess(importRoomKeysResult) + } + } + + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + }) + } + ) + } } override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, @@ -703,31 +712,36 @@ internal class KeysBackup @Inject constructor( callback: MatrixCallback) { Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + val progressListener = if (stepProgressListener != null) { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + uiHandler.post { + stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) + } } } - } - } else { - null - } - - val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, progressListener) - - if (recoveryKey == null) { - uiHandler.post { - Timber.v("backupKeys: Invalid configuration") - callback.onFailure(IllegalStateException("Invalid configuration")) + } else { + null } - return@post - } - - restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback) + Try { + recoveryKeyFromPassword(password, keysBackupVersion, progressListener) + } + }.fold( + { + callback.onFailure(it) + }, + { recoveryKey -> + if (recoveryKey == null) { + Timber.v("backupKeys: Invalid configuration") + callback.onFailure(IllegalStateException("Invalid configuration")) + } else { + restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback) + } + } + ) } } @@ -993,9 +1007,9 @@ internal class KeysBackup @Inject constructor( } } - /* ========================================================================================== - * Private - * ========================================================================================== */ +/* ========================================================================================== + * Private + * ========================================================================================== */ /** * Extract MegolmBackupAuthData data from a backup version. @@ -1194,94 +1208,96 @@ internal class KeysBackup @Inject constructor( keysBackupStateManager.state = KeysBackupState.BackingUp - CryptoAsyncHelper.getEncryptBackgroundHandler().post { - Timber.v("backupKeys: 2 - Encrypting keys") + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Timber.v("backupKeys: 2 - Encrypting keys") - // Gather data to send to the homeserver - // roomId -> sessionId -> MXKeyBackupData - val keysBackupData = KeysBackupData() - keysBackupData.roomIdToRoomKeysBackupData = HashMap() + // Gather data to send to the homeserver + // roomId -> sessionId -> MXKeyBackupData + val keysBackupData = KeysBackupData() + keysBackupData.roomIdToRoomKeysBackupData = HashMap() - for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { - val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) - if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { - val roomKeysBackupData = RoomKeysBackupData() - roomKeysBackupData.sessionIdToKeyBackupData = HashMap() - keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData + for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { + val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) + if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { + val roomKeysBackupData = RoomKeysBackupData() + roomKeysBackupData.sessionIdToKeyBackupData = HashMap() + keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData + } + + try { + keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData + } catch (e: OlmException) { + Timber.e(e, "OlmException") + } } - try { - keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - } + Timber.v("backupKeys: 4 - Sending request") - Timber.v("backupKeys: 4 - Sending request") - - // Make the request - storeSessionDataTask - .configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: BackupKeysResult) { - uiHandler.post { - Timber.v("backupKeys: 5a - Request complete") - - // Mark keys as backed up - cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - - if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { - Timber.v("backupKeys: All keys have been backed up") - onServerDataRetrieved(data.count, data.hash) - - // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - } else { - Timber.v("backupKeys: Continue to back up keys") - keysBackupStateManager.state = KeysBackupState.WillBackUp - - backupKeys() - } - } - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError) { + // Make the request + storeSessionDataTask + .configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: BackupKeysResult) { uiHandler.post { - Timber.e(failure, "backupKeys: backupKeys failed.") + Timber.v("backupKeys: 5a - Request complete") - when (failure.error.code) { - MatrixError.NOT_FOUND, - MatrixError.WRONG_ROOM_KEYS_VERSION -> { - // Backup has been deleted on the server, or we are not using the last backup version - keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - resetKeysBackupData() - keysBackupVersion = null + // Mark keys as backed up + cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver - checkAndStartKeysBackup() - } - else -> // Come back to the ready state so that we will retry on the next received key - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { + Timber.v("backupKeys: All keys have been backed up") + onServerDataRetrieved(data.count, data.hash) + + // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + } else { + Timber.v("backupKeys: Continue to back up keys") + keysBackupStateManager.state = KeysBackupState.WillBackUp + + backupKeys() } } - } else { - uiHandler.post { - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() + } - Timber.e("backupKeys: backupKeys failed.") + override fun onFailure(failure: Throwable) { + if (failure is Failure.ServerError) { + uiHandler.post { + Timber.e(failure, "backupKeys: backupKeys failed.") - // Retry a bit later - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - maybeBackupKeys() + when (failure.error.code) { + MatrixError.NOT_FOUND, + MatrixError.WRONG_ROOM_KEYS_VERSION -> { + // Backup has been deleted on the server, or we are not using the last backup version + keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion + backupAllGroupSessionsCallback?.onFailure(failure) + resetBackupAllGroupSessionsListeners() + resetKeysBackupData() + keysBackupVersion = null + + // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver + checkAndStartKeysBackup() + } + else -> // Come back to the ready state so that we will retry on the next received key + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + } + } + } else { + uiHandler.post { + backupAllGroupSessionsCallback?.onFailure(failure) + resetBackupAllGroupSessionsListeners() + + Timber.e("backupKeys: backupKeys failed.") + + // Retry a bit later + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + maybeBackupKeys() + } } } - } - }) - .executeBy(taskExecutor) + }) + .executeBy(taskExecutor) + } } } @@ -1298,7 +1314,8 @@ internal class KeysBackup @Inject constructor( "algorithm" to sessionData!!.algorithm, "sender_key" to sessionData.senderKey, "sender_claimed_keys" to sessionData.senderClaimedKeys, - "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain ?: ArrayList()), + "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain + ?: ArrayList()), "session_key" to sessionData.sessionKey) var encryptedSessionBackupData: OlmPkMessage? = null @@ -1380,9 +1397,9 @@ internal class KeysBackup @Inject constructor( private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100 } - /* ========================================================================================== - * DEBUG INFO - * ========================================================================================== */ +/* ========================================================================================== + * DEBUG INFO + * ========================================================================================== */ override fun toString() = "KeysBackup for ${credentials.userId}" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceAuth.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceAuth.kt index f50fbaba..53ba4179 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceAuth.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceAuth.kt @@ -19,10 +19,10 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * This class provides the + * This class provides the authentication data to delete a device */ @JsonClass(generateAdapter = true) -data class DeleteDeviceAuth( +internal data class DeleteDeviceAuth( // device device session id @Json(name = "session") @@ -32,6 +32,9 @@ data class DeleteDeviceAuth( @Json(name = "type") var type: String? = null, + @Json(name = "user") var user: String? = null, + + @Json(name = "password") var password: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt index ba3ed903..f823de2e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt @@ -15,12 +15,14 @@ */ package im.vector.matrix.android.internal.crypto.model.rest +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** * This class provides the parameter to delete a device */ @JsonClass(generateAdapter = true) -data class DeleteDeviceParams( - var auth: DeleteDeviceAuth? = null +internal data class DeleteDeviceParams( + @Json(name = "auth") + var deleteDeviceAuth: DeleteDeviceAuth? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt index 6fb16621..0f93e213 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt @@ -17,8 +17,13 @@ package im.vector.matrix.android.internal.crypto.tasks import arrow.core.Try +import arrow.core.failure +import arrow.core.recoverWith +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams +import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.Task @@ -26,8 +31,7 @@ import javax.inject.Inject internal interface DeleteDeviceTask : Task { data class Params( - val deviceId: String, - val accountPassword: String + val deviceId: String ) } @@ -35,11 +39,30 @@ internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi : DeleteDeviceTask { override suspend fun execute(params: DeleteDeviceTask.Params): Try { - return executeRequest { - apiCall = cryptoApi.deleteDevice(params.deviceId, - DeleteDeviceParams()) - } + return executeRequest { + apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()) + }.recoverWith { throwable -> + if (throwable is Failure.OtherServerError && throwable.httpCode == 401) { + // Parse to get a RegistrationFlowResponse + val registrationFlowResponse = try { + MoshiProvider.providesMoshi() + .adapter(RegistrationFlowResponse::class.java) + .fromJson(throwable.errorBody) + } catch (e: Exception) { + null + } - // TODO Recover error, see legacy code MXSession.deleteDevice() + // check if the server response can be casted + if (registrationFlowResponse != null) { + Failure.RegistrationFlowError(registrationFlowResponse).failure() + } else { + throwable.failure() + } + + } else { + // Other error + throwable.failure() + } + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt new file mode 100644 index 00000000..2476f242 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.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.crypto.tasks + +import arrow.core.Try +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.internal.auth.data.LoginFlowTypes +import im.vector.matrix.android.internal.crypto.api.CryptoApi +import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth +import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface DeleteDeviceWithUserPasswordTask : Task { + data class Params( + val deviceId: String, + val authSession: String?, + val password: String + ) +} + +internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(private val cryptoApi: CryptoApi, + private val credentials: Credentials) + : DeleteDeviceWithUserPasswordTask { + + override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params): Try { + return executeRequest { + apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams() + .apply { + deleteDeviceAuth = DeleteDeviceAuth() + .apply { + type = LoginFlowTypes.PASSWORD + session = params.authSession + user = credentials.userId + password = params.password + } + }) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 803bcb98..ebf3294a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -27,17 +27,12 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf 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.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.session.SessionScope @@ -142,8 +137,8 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) { setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, - deviceID, - userId) + deviceID, + userId) listeners.forEach { try { @@ -157,7 +152,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre private suspend fun onStartRequestReceived(event: Event) { val startReq = event.getClearContent().toModel()!! - val otherUserId = event.sender + val otherUserId = event.senderId if (!startReq.isValid()) { Timber.e("## received invalid verification request") if (startReq.transactionID != null) { @@ -210,7 +205,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } else { Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}") cancelTransaction(tid, otherUserId, startReq.fromDevice - ?: event.getSenderKey()!!, CancelCode.UnknownMethod) + ?: event.getSenderKey()!!, CancelCode.UnknownMethod) } } }, @@ -245,7 +240,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre Timber.e("## Received invalid accept request") return } - val otherUserId = event.sender!! + val otherUserId = event.senderId!! Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}") val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!) @@ -267,7 +262,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre Timber.e("## Received invalid accept request") return } - val otherUserId = event.sender!! + val otherUserId = event.senderId!! val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!) if (existing == null) { Timber.e("## Received invalid accept request") @@ -291,7 +286,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre Timber.e("## Received invalid key request") return } - val otherUserId = event.sender!! + val otherUserId = event.senderId!! val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!) if (existing == null) { Timber.e("## Received invalid accept request") @@ -312,7 +307,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre Timber.e("## Received invalid key request") return } - val otherUserId = event.sender!! + val otherUserId = event.senderId!! val existing = getExistingTransaction(otherUserId, macReq.transactionID!!) if (existing == null) { Timber.e("## Received invalid accept request") @@ -376,9 +371,8 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre userId, deviceID) addTransaction(tx) - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - tx.start() - } + + tx.start() return txID } else { throw IllegalArgumentException("Unknown verification method") @@ -403,9 +397,9 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre override fun transactionUpdated(tx: VerificationTransaction) { dispatchTxUpdated(tx) if (tx is SASVerificationTransaction - && (tx.state == SasVerificationTxState.Cancelled - || tx.state == SasVerificationTxState.OnCancelled - || tx.state == SasVerificationTxState.Verified) + && (tx.state == SasVerificationTxState.Cancelled + || tx.state == SasVerificationTxState.OnCancelled + || tx.state == SasVerificationTxState.Verified) ) { //remove this.removeTransaction(tx.otherUserId, tx.transactionId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt index 1d40337b..2a4f0a1d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt @@ -22,13 +22,12 @@ import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTr import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.task.TaskExecutor @@ -61,21 +60,21 @@ internal class IncomingSASVerificationTransaction( override val uxState: IncomingSasVerificationTransaction.UxState get() { return when (state) { - SasVerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT + SasVerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT SasVerificationTxState.SendingAccept, SasVerificationTxState.Accepted, SasVerificationTxState.OnKeyReceived, SasVerificationTxState.SendingKey, - SasVerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT + SasVerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT SasVerificationTxState.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS SasVerificationTxState.ShortCodeAccepted, SasVerificationTxState.SendingMac, SasVerificationTxState.MacSent, - SasVerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION - SasVerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED - SasVerificationTxState.Cancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME - SasVerificationTxState.OnCancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER - else -> IncomingSasVerificationTransaction.UxState.UNKNOWN + SasVerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION + SasVerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED + SasVerificationTxState.Cancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME + SasVerificationTxState.OnCancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER + else -> IncomingSasVerificationTransaction.UxState.UNKNOWN } } @@ -125,9 +124,7 @@ internal class IncomingSASVerificationTransaction( //TODO force download keys!! //would be probably better to download the keys //for now I cancel - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - cancel(CancelCode.User) - } + cancel(CancelCode.User) } else { // val otherKey = info.identityKey() //need to jump back to correct thread @@ -139,9 +136,7 @@ internal class IncomingSASVerificationTransaction( shortAuthenticationStrings = agreedShortCode, commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT) ) - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - doAccept(accept) - } + doAccept(accept) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt index aa18133f..2210c25a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt @@ -86,7 +86,6 @@ internal class OutgoingSASVerificationRequest( } fun start() { - if (state != SasVerificationTxState.None) { Timber.e("## start verification from invalid state") //should I cancel?? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index 858c48b4..87849d18 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -23,7 +23,6 @@ import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXKey @@ -31,6 +30,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.extensions.toUnsignedInt import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import org.matrix.olm.OlmSAS @@ -223,13 +223,17 @@ internal abstract class SASVerificationTransaction( cancel(CancelCode.MismatchedKeys) return } + + val verifiedDevices = ArrayList() + //cannot be empty because it has been validated theirMac!!.mac!!.keys.forEach { val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint() if (otherDeviceKey == null) { - cancel(CancelCode.MismatchedKeys) - return + Timber.e("Verification: Could not find device $keyIDNoPrefix to verify") + //just ignore and continue + return@forEach } val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it) if (mac != theirMac?.mac?.get(it)) { @@ -237,12 +241,21 @@ internal abstract class SASVerificationTransaction( cancel(CancelCode.MismatchedKeys) return } + verifiedDevices.add(keyIDNoPrefix) } - setDeviceVerified( - otherDeviceId ?: "", - otherUserId) + // if none of the keys could be verified, then error because the app + // should be informed about that + if (verifiedDevices.isEmpty()) { + Timber.e("Verification: No devices verified") + cancel(CancelCode.MismatchedKeys) + return + } + //TODO what if the otherDevice is not in this list? and should we + verifiedDevices.forEach { + setDeviceVerified(it, otherUserId) + } state = SasVerificationTxState.Verified } @@ -278,21 +291,17 @@ internal abstract class SASVerificationTransaction( .dispatchTo(object : MatrixCallback { override fun onSuccess(data: Unit) { Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - if (onDone != null) { - onDone() - } else { - state = nextState - } + if (onDone != null) { + onDone() + } else { + state = nextState } } override fun onFailure(failure: Throwable) { Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - cancel(onErrorReason) - } + cancel(onErrorReason) } }) .executeBy(taskExecutor) @@ -359,11 +368,11 @@ internal abstract class SASVerificationTransaction( * or with the three numbers on separate lines. */ fun getDecimalCodeRepresentation(byteArray: ByteArray): String { - val b0 = byteArray[0].toInt().and(0xff) //need unsigned byte - val b1 = byteArray[1].toInt().and(0xff) //need unsigned byte - val b2 = byteArray[2].toInt().and(0xff) //need unsigned byte - val b3 = byteArray[3].toInt().and(0xff) //need unsigned byte - val b4 = byteArray[4].toInt().and(0xff) //need unsigned byte + val b0 = byteArray[0].toUnsignedInt() //need unsigned byte + val b1 = byteArray[1].toUnsignedInt() //need unsigned byte + val b2 = byteArray[2].toUnsignedInt() //need unsigned byte + val b3 = byteArray[3].toUnsignedInt() //need unsigned byte + val b4 = byteArray[4].toUnsignedInt() //need unsigned byte //(B0 << 5 | B1 >> 3) + 1000 val first = (b0.shl(5) or b1.shr(3)) + 1000 //((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 @@ -384,12 +393,12 @@ internal abstract class SASVerificationTransaction( * to that number 7 emoji are selected from a list of 64 emoji (see Appendix A) */ fun getEmojiCodeRepresentation(byteArray: ByteArray): List { - val b0 = byteArray[0].toInt().and(0xff) - val b1 = byteArray[1].toInt().and(0xff) - val b2 = byteArray[2].toInt().and(0xff) - val b3 = byteArray[3].toInt().and(0xff) - val b4 = byteArray[4].toInt().and(0xff) - val b5 = byteArray[5].toInt().and(0xff) + val b0 = byteArray[0].toUnsignedInt() + val b1 = byteArray[1].toUnsignedInt() + val b2 = byteArray[2].toUnsignedInt() + val b3 = byteArray[3].toUnsignedInt() + val b4 = byteArray[4].toUnsignedInt() + val b5 = byteArray[5].toUnsignedInt() return listOf( getEmojiForCode((b0 and 0xFC).shr(2)), getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index 8d5fe49e..17df08e5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -37,7 +37,7 @@ internal object EventMapper { eventEntity.prevContent = ContentMapper.map(resolvedPrevContent) eventEntity.stateKey = event.stateKey eventEntity.type = event.getClearType() - eventEntity.sender = event.sender + eventEntity.sender = event.senderId eventEntity.originServerTs = event.originServerTs eventEntity.redacts = event.redacts eventEntity.age = event.unsignedData?.age ?: event.originServerTs @@ -63,7 +63,7 @@ internal object EventMapper { content = ContentMapper.map(eventEntity.content), prevContent = ContentMapper.map(eventEntity.prevContent), originServerTs = eventEntity.originServerTs, - sender = eventEntity.sender, + senderId = eventEntity.sender, stateKey = eventEntity.stateKey, roomId = eventEntity.roomId, unsignedData = ud, 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 00000000..eef122a5 --- /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 00000000..1ed2151e --- /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/mapper/PushersMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushersMapper.kt new file mode 100644 index 00000000..8cac50a4 --- /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 00000000..e6c3d406 --- /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 00000000..dd6cc790 --- /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 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, + //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 00000000..f5041925 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PushRulesEntity.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.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 = "", + // "content", etc. + var rulesetKey: String = "", + var pushRules: RealmList = RealmList() +) : RealmObject() { + companion object +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherDataEntity.kt similarity index 65% rename from vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherDataEntity.kt index 83b1fa6c..87694fb7 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/PusherDataEntity.kt @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package im.vector.matrix.android.internal.database.model -package im.vector.riotredesign.features.home.room.detail.timeline.helper - -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent - -object RoomMemberEventHelper { - +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 00000000..6e567c0a --- /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? = null, + var appId: String = "", + var appDisplayName: String? = null, + var deviceDisplayName: String? = null, + var profileTag: String? = null, + var lang: String? = null, + var data: PusherDataEntity? = null +) : RealmObject() { + private var stateStr: String = PusherState.UNREGISTERED.name + + var state: PusherState + get() { + try { + return PusherState.valueOf(stateStr) + } catch (e: Exception) { + //can this happen? + return PusherState.UNREGISTERED + } + + } + 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 20ba8d44..b926da99 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 8fca0abd..6f4db6f4 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 00000000..b7f54f17 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/PushersQueries.kt @@ -0,0 +1,46 @@ +/* + * 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.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 { + return realm.where() + .equalTo(PusherEntityFields.USER_ID, userId) + .apply { + if (pushKey != null) { + 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/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt index b1767ddc..875dd5f7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt @@ -18,9 +18,10 @@ package im.vector.matrix.android.internal.di import android.content.Context import android.content.res.Resources +import android.os.Handler +import android.os.HandlerThread import dagger.Module import dagger.Provides -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.android.asCoroutineDispatcher @@ -32,11 +33,14 @@ internal object MatrixModule { @JvmStatic @Provides fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers { - val cryptoHandler = CryptoAsyncHelper.getDecryptBackgroundHandler() + val THREAD_CRYPTO_NAME = "Crypto_Thread" + val handlerThread = HandlerThread(THREAD_CRYPTO_NAME) + handlerThread.start() + return MatrixCoroutineDispatchers(io = Dispatchers.IO, - computation = Dispatchers.IO, - main = Dispatchers.Main, - crypto = cryptoHandler.asCoroutineDispatcher("crypto") + computation = Dispatchers.IO, + main = Dispatchers.Main, + crypto = Handler(handlerThread.looper).asCoroutineDispatcher("crypto") ) } 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 acd3353b..3849a2f7 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 00000000..b758a4de --- /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/vector/src/main/java/im/vector/riotredesign/core/services/PushSimulatorWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Primitives.kt similarity index 50% rename from vector/src/main/java/im/vector/riotredesign/core/services/PushSimulatorWorker.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Primitives.kt index d3f93f32..0a0ca1a1 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/services/PushSimulatorWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Primitives.kt @@ -14,23 +14,9 @@ * limitations under the License. */ -package im.vector.riotredesign.core.services - -import android.content.Context -import androidx.work.Worker -import androidx.work.WorkerParameters +package im.vector.matrix.android.internal.extensions /** - * This class simulate push event when FCM is not working/disabled + * Convert a signed byte to a int value */ -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 +fun Byte.toUnsignedInt() = toInt() and 0xff diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Try.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Try.kt new file mode 100644 index 00000000..18ab0e47 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Try.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.extensions + +import arrow.core.* +import im.vector.matrix.android.api.MatrixCallback + +inline fun TryOf.onError(f: (Throwable) -> Unit): Try = fix() + .fold( + { + f(it) + Failure(it) + }, + { Success(it) } + ) + +fun Try.foldToCallback(callback: MatrixCallback): Unit = fold( + { callback.onFailure(it) }, + { callback.onSuccess(it) }) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt index 0f723d41..70b16a4d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/AccessTokenInterceptor.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.network import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.internal.session.SessionScope import okhttp3.Interceptor import okhttp3.Response import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index f8a6bc57..bbd5859b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -23,44 +23,72 @@ import arrow.effects.IO import arrow.effects.fix import arrow.effects.instances.io.async.async import arrow.integrations.retrofit.adapter.runAsync +import com.squareup.moshi.JsonDataException import com.squareup.moshi.Moshi import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.internal.di.MoshiProvider +import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.ResponseBody import retrofit2.Call +import timber.log.Timber import java.io.IOException +import kotlin.coroutines.resume -internal inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() +internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() internal class Request { private val moshi: Moshi = MoshiProvider.providesMoshi() lateinit var apiCall: Call - fun execute(): Try { - return Try { - val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync() - if (response.isSuccessful) { - response.body() ?: throw IllegalStateException("The request returned a null body") - } else { - throw manageFailure(response.errorBody()) + suspend fun execute(): Try { + return suspendCancellableCoroutine { continuation -> + continuation.invokeOnCancellation { + Timber.v("Request is canceled") + apiCall.cancel() } - }.recoverWith { - when (it) { - is IOException -> Failure.NetworkConnection(it) - is Failure.ServerError -> it - else -> Failure.Unknown(it) - }.failure() + val result = Try { + val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync() + if (response.isSuccessful) { + response.body() + ?: throw IllegalStateException("The request returned a null body") + } else { + throw manageFailure(response.errorBody(), response.code()) + } + }.recoverWith { + when (it) { + is IOException -> Failure.NetworkConnection(it) + is Failure.ServerError, + is Failure.OtherServerError -> it + else -> Failure.Unknown(it) + }.failure() + } + continuation.resume(result) } + } - private fun manageFailure(errorBody: ResponseBody?): Throwable { - val matrixError = errorBody?.let { - val matrixErrorAdapter = moshi.adapter(MatrixError::class.java) - matrixErrorAdapter.fromJson(errorBody.source()) - } ?: return RuntimeException("Matrix error should not be null") - return Failure.ServerError(matrixError) - } + private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable { + if (errorBody == null) { + return RuntimeException("Error body should not be null") + } + val errorBodyStr = errorBody.string() + + val matrixErrorAdapter = moshi.adapter(MatrixError::class.java) + + try { + val matrixError = matrixErrorAdapter.fromJson(errorBodyStr) + + if (matrixError != null) { + return Failure.ServerError(matrixError, httpCode) + } + } catch (ex: JsonDataException) { + // This is not a MatrixError + Timber.w("The error returned by the server is not a MatrixError") + } + + return Failure.OtherServerError(errorBodyStr, httpCode) + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UserAgentInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UserAgentInterceptor.kt index 9d6be122..44e3ddd1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UserAgentInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UserAgentInterceptor.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.network -import im.vector.matrix.android.internal.di.MatrixScope import okhttp3.Interceptor import okhttp3.Response import javax.inject.Inject 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 5e3f34f2..afbe85e6 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 @@ -16,18 +16,21 @@ package im.vector.matrix.android.internal.session +import android.content.Context import android.os.Looper import androidx.annotation.MainThread import androidx.lifecycle.LiveData 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.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 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 @@ -38,11 +41,13 @@ import im.vector.matrix.android.api.util.MatrixCallbackDelegate import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.session.sync.job.SyncThread +import im.vector.matrix.android.internal.session.sync.job.SyncWorker import timber.log.Timber import javax.inject.Inject @SessionScope internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams, + private val context: Context, private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>, private val monarchy: Monarchy, private val sessionListeners: SessionListeners, @@ -53,22 +58,27 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se private val filterService: FilterService, private val cacheService: CacheService, private val signOutService: SignOutService, + private val pushRuleService: PushRuleService, + private val pushersService: PushersService, private val cryptoService: CryptoManager, private val syncThread: SyncThread, private val contentUrlResolver: ContentUrlResolver, private val contentUploadProgressTracker: ContentUploadStateTracker) : Session, - RoomService by roomService, - RoomDirectoryService by roomDirectoryService, - GroupService by groupService, - UserService by userService, - CryptoService by cryptoService, - CacheService by cacheService, - SignOutService by signOutService, - FilterService by filterService { + RoomService by roomService, + RoomDirectoryService by roomDirectoryService, + GroupService by groupService, + UserService by userService, + CryptoService by cryptoService, + CacheService by cacheService, + SignOutService by signOutService, + FilterService by filterService, + PushRuleService by pushRuleService, + PushersService by pushersService { private var isOpen = false + @MainThread override fun open() { assertMainThread() @@ -80,10 +90,27 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se liveEntityObservers.forEach { it.start() } } + override fun requireBackgroundSync() { + SyncWorker.requireBackgroundSync(context, sessionParams.credentials.userId) + } + + override fun startAutomaticBackgroundSync(repeatDelay: Long) { + SyncWorker.automaticallyBackgroundSync(context, sessionParams.credentials.userId, 0, repeatDelay) + } + + override fun stopAnyBackgroundSync() { + SyncWorker.stopAnyBackgroundSync(context) + } + @MainThread override fun startSync() { assert(isOpen) - syncThread.start() + if (!syncThread.isAlive) { + syncThread.start() + } else { + syncThread.restart() + Timber.w("Attempt to start an already started thread") + } } @MainThread @@ -113,8 +140,8 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se 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 { @@ -164,4 +191,5 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se } } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index 30e61427..0cb78026 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -28,6 +28,8 @@ import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.filter.FilterModule import im.vector.matrix.android.internal.session.group.GetGroupDataWorker import im.vector.matrix.android.internal.session.group.GroupModule +import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker +import im.vector.matrix.android.internal.session.pushers.PushersModule import im.vector.matrix.android.internal.session.room.RoomModule import im.vector.matrix.android.internal.session.room.relation.SendRelationWorker import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker @@ -35,22 +37,24 @@ import im.vector.matrix.android.internal.session.room.send.RedactEventWorker import im.vector.matrix.android.internal.session.room.send.SendEventWorker 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.SyncWorker import im.vector.matrix.android.internal.session.user.UserModule @Component(dependencies = [MatrixComponent::class], - modules = [ - SessionModule::class, - RoomModule::class, - SyncModule::class, - SignOutModule::class, - GroupModule::class, - UserModule::class, - FilterModule::class, - GroupModule::class, - ContentModule::class, - CacheModule::class, - CryptoModule::class - ] + modules = [ + SessionModule::class, + RoomModule::class, + SyncModule::class, + SignOutModule::class, + GroupModule::class, + UserModule::class, + FilterModule::class, + GroupModule::class, + ContentModule::class, + CacheModule::class, + CryptoModule::class, + PushersModule::class + ] ) @SessionScope internal interface SessionComponent { @@ -69,6 +73,10 @@ internal interface SessionComponent { fun inject(uploadContentWorker: UploadContentWorker) + fun inject(syncWorker: SyncWorker) + + fun inject(addHttpPusherWorker: AddHttpPusherWorker) + @Component.Factory interface Factory { 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 cff657ae..10d4abc9 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 @@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.RetrofitFactory 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.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.user.UserEntityUpdater @@ -126,6 +127,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver + @Binds + @IntoSet + abstract fun bindBingRuleWatcher(bingRuleWatcher: BingRuleWatcher): LiveEntityObserver + @Binds @IntoSet abstract fun bindUserEntityUpdater(groupSummaryUpdater: UserEntityUpdater): LiveEntityObserver diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt index f87d0fd6..1faf489d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt @@ -20,7 +20,6 @@ import dagger.Binds import dagger.Module import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUrlResolver -import im.vector.matrix.android.internal.session.SessionScope @Module internal abstract class ContentModule { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt index 33941729..fbd7983d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt @@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.content import android.os.Handler import android.os.Looper import im.vector.matrix.android.api.session.content.ContentUploadStateTracker -import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.session.SessionScope import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt index 50fe285a..55508b11 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.content import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.session.content.ContentUrlResolver -import im.vector.matrix.android.internal.session.SessionScope import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index dac85cb2..61052084 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -21,10 +21,7 @@ import arrow.core.Try.Companion.raise import com.squareup.moshi.Moshi import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.di.Authenticated -import im.vector.matrix.android.internal.di.MoshiProvider -import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.ProgressRequestBody -import im.vector.matrix.android.internal.session.SessionScope import okhttp3.* import java.io.File import java.io.IOException diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index 80b9b0a2..4c3235e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -19,20 +19,14 @@ package im.vector.matrix.android.internal.session.content import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.squareup.inject.assisted.AssistedInject import com.squareup.moshi.JsonClass 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.events.model.toModel -import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent -import im.vector.matrix.android.api.session.room.model.message.MessageContent -import im.vector.matrix.android.api.session.room.model.message.MessageFileContent -import im.vector.matrix.android.api.session.room.model.message.MessageImageContent -import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent +import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.internal.network.ProgressRequestBody import im.vector.matrix.android.internal.session.room.send.SendEventWorker -import im.vector.matrix.android.internal.worker.DelegateWorkerFactory import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent @@ -56,7 +50,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.success() + ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() sessionComponent.inject(this) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt index f6a0a13b..c46da8b3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt @@ -16,11 +16,10 @@ package im.vector.matrix.android.internal.session.filter -import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.database.model.FilterEntity import im.vector.matrix.android.internal.database.model.FilterEntityFields import im.vector.matrix.android.internal.database.query.getFilter -import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.di.SessionDatabase import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.where diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterService.kt index e64bce19..445e416b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterService.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.session.filter import im.vector.matrix.android.api.session.sync.FilterService -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt index 26612b23..5fa5d0a2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt @@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.filter import arrow.core.Try import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.network.executeRequest -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.Task import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt index a10f654f..092038ee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt @@ -42,5 +42,6 @@ internal interface FilterApi { * @return Filter */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}") - fun getFilterById(@Path("userId") userId: String, @Path("filterId") filterId: String): Call + fun getFilterById(@Path("userId") userId: String, @Path("filterId") + filterId: String): Call } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt index 3c763d98..ed8c11bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt @@ -21,14 +21,13 @@ import arrow.core.fix import arrow.instances.`try`.monad.monad import arrow.typeclasses.binding import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.group.model.GroupRooms import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse import im.vector.matrix.android.internal.session.group.model.GroupUsers +import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.kotlin.createObject import javax.inject.Inject @@ -46,21 +45,17 @@ internal class DefaultGetGroupDataTask @Inject constructor( override suspend fun execute(params: GetGroupDataTask.Params): Try { val groupId = params.groupId + val groupSummary = executeRequest { + apiCall = groupAPI.getSummary(groupId) + } + val groupRooms = executeRequest { + apiCall = groupAPI.getRooms(groupId) + } + val groupUsers = executeRequest { + apiCall = groupAPI.getUsers(groupId) + } return Try.monad().binding { - - val groupSummary = executeRequest { - apiCall = groupAPI.getSummary(groupId) - }.bind() - - val groupRooms = executeRequest { - apiCall = groupAPI.getRooms(groupId) - }.bind() - - val groupUsers = executeRequest { - apiCall = groupAPI.getUsers(groupId) - }.bind() - - insertInDb(groupSummary, groupRooms, groupUsers, groupId).bind() + insertInDb(groupSummary.bind(), groupRooms.bind(), groupUsers.bind(), groupId).bind() }.fix() } @@ -72,12 +67,13 @@ internal class DefaultGetGroupDataTask @Inject constructor( return monarchy .tryTransactionSync { realm -> val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst() - ?: realm.createObject(groupId) + ?: realm.createObject(groupId) groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: "" val name = groupSummary.profile?.name groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name - groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: "" + groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription + ?: "" val roomIds = groupRooms.rooms.map { it.roomId } groupSummaryEntity.roomIds.clear() 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 00000000..dcba72c3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -0,0 +1,56 @@ +/* + * 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.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 +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.task.configureWith +import javax.inject.Inject + + +internal class BingRuleWatcher @Inject constructor(monarchy: Monarchy, + private val task: ProcessEventForPushTask, + private val defaultPushRuleService: DefaultPushRuleService, + private val sessionParams: SessionParams, + private val taskExecutor: TaskExecutor) : + 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) { + val rules = defaultPushRuleService.getPushRules("global") + inserted.map { it.asDomain() } + .filter { it.senderId != sessionParams.credentials.userId } + .let { events -> + task.configureWith(ProcessEventForPushTask.Params(events, rules)) + .executeBy(taskExecutor) + } + } + + +} \ No newline at end of file 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 00000000..9b947829 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -0,0 +1,186 @@ +/* + * 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.GetPushRulesResponse +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.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.session.pushers.UpdatePushRuleEnableStatusTask +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import timber.log.Timber +import javax.inject.Inject + + +internal class DefaultPushRuleService @Inject constructor( + private val sessionParams: SessionParams, + private val pushRulesTask: GetPushRulesTask, + private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask, + 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: GetPushRulesResponse) { + monarchy.runTransactionSync { realm -> + //clear existings? + //TODO + realm.where(PushRulesEntity::class.java) + .equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId) + .findAll().deleteAllFromRealm() + + val content = PushRulesEntity(sessionParams.credentials.userId, scope, "content") + data.global.content?.forEach { rule -> + PushRulesMapper.map(rule).also { + content.pushRules.add(it) + } + } + realm.insertOrUpdate(content) + + val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override") + data.global.override?.forEach { rule -> + PushRulesMapper.map(rule).also { + override.pushRules.add(it) + } + } + realm.insertOrUpdate(override) + + val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room") + data.global.room?.forEach { rule -> + PushRulesMapper.map(rule).also { + rooms.pushRules.add(it) + } + } + realm.insertOrUpdate(rooms) + + val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender") + data.global.sender?.forEach { rule -> + PushRulesMapper.map(rule).also { + senders.pushRules.add(it) + } + } + realm.insertOrUpdate(senders) + + val 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() + var overrideRules: List = emptyList() + var roomRules: List = emptyList() + 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) } + } + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "override").findFirst()?.let { re -> + overrideRules = re.pushRules.map { PushRulesMapper.map(it) } + } + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "room").findFirst()?.let { re -> + roomRules = re.pushRules.map { PushRulesMapper.mapRoomRule(it) } + } + PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "sender").findFirst()?.let { re -> + senderRules = re.pushRules.map { PushRulesMapper.mapSenderRule(it) } + } + 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 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) + } + + + 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) { + Timber.e(e, "Error while dispatching bing") + } + } + + 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/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt new file mode 100644 index 00000000..d6d09c86 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt @@ -0,0 +1,56 @@ +package im.vector.matrix.android.internal.session.notification + +import arrow.core.Try +import im.vector.matrix.android.api.auth.data.SessionParams +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.room.RoomService +import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver +import im.vector.matrix.android.internal.task.Task +import timber.log.Timber +import javax.inject.Inject + +internal interface ProcessEventForPushTask : Task { + data class Params( + val events: List, + val rules: List + ) +} + +internal class DefaultProcessEventForPushTask @Inject constructor( + private val defaultPushRuleService: DefaultPushRuleService, + private val roomService: RoomService, + private val sessionParams: SessionParams +) : 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, roomService, sessionParams) + 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 new file mode 100644 index 00000000..6ac59607 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt @@ -0,0 +1,100 @@ +/* + * 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.PusherEntity +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.worker.WorkerParamsFactory +import im.vector.matrix.android.internal.worker.getSessionComponent +import javax.inject.Inject + +internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) + : CoroutineWorker(context, params) { + + @JsonClass(generateAdapter = true) + internal data class Params( + val pusher: JsonPusher, + val userId: String + ) + + @Inject lateinit var pushersAPI: PushersAPI + @Inject lateinit var monarchy: Monarchy + + override suspend fun doWork(): Result { + + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.failure() + + val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + sessionComponent.inject(this) + + 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?.format = pusher.data?.format + echo.data?.url = pusher.data?.url + 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/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt new file mode 100644 index 00000000..a05cfb6c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultConditionResolver.kt @@ -0,0 +1,52 @@ +/* + * 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 timber.log.Timber + +internal class DefaultConditionResolver(private val event: Event, + private val roomService: RoomService, + private val sessionParams: SessionParams) : ConditionResolver { + + + 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/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt new file mode 100644 index 00000000..1377ade5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -0,0 +1,115 @@ +/* + * 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.worker.WorkerParamsFactory +import java.util.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + + +internal class DefaultPusherService @Inject constructor( + private val monarchy: Monarchy, + private val sessionParam: SessionParams, + private val getPusherTask: GetPushersTask, + private val removePusherTask: RemovePusherTask, + private val taskExecutor: TaskExecutor +) : PushersService { + + + override fun refreshPushers() { + getPusherTask + .configureWith(Unit) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: GetPushersResponse) { + 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 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) }, + { it.asDomain() } + ) + } +} \ 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 00000000..46af3b63 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt @@ -0,0 +1,34 @@ +/* + * 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.GetPushRulesResponse +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + + +internal interface GetPushRulesTask : Task + +internal class DefaultGetPushRulesTask @Inject constructor(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/auth/data/LoginFlow.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersResponse.kt similarity index 76% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlow.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersResponse.kt index fba7980b..4f4cac93 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/LoginFlow.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersResponse.kt @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package im.vector.matrix.android.internal.session.pushers -package im.vector.matrix.android.internal.auth.data - +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass + @JsonClass(generateAdapter = true) -internal data class LoginFlow(val type: String, - val stages: List) \ No newline at end of file +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 new file mode 100644 index 00000000..05b36fb5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt @@ -0,0 +1,32 @@ +/* + * 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 +import javax.inject.Inject + +internal interface GetPushersTask : Task + +internal class DefaultGetPusherTask @Inject constructor(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 00000000..d262eeb7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusher.kt @@ -0,0 +1,103 @@ +/* + * 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 +import im.vector.matrix.android.internal.di.SerializeNulls + +/** + * Example: + * + * + * { + * "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( + /** + * 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 +) + 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 00000000..95898aca --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/JsonPusherData.kt @@ -0,0 +1,34 @@ +/* + * 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/PushRulesApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushRulesApi.kt new file mode 100644 index 00000000..f191b21d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushRulesApi.kt @@ -0,0 +1,83 @@ +/* + * 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.GetPushRulesResponse +import im.vector.matrix.android.api.pushrules.rest.PushRule +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 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 + + + /** + * 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 + + /** + * 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/pushers/PushersAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.kt new file mode 100644 index 00000000..7516e48a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersAPI.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.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/latest#get-matrix-client-r0-pushers + */ + @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. + * + * 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 + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersModule.kt new file mode 100644 index 00000000..85276252 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/PushersModule.kt @@ -0,0 +1,76 @@ +/* + * + * * 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 dagger.Binds +import dagger.Module +import dagger.Provides +import im.vector.matrix.android.api.pushrules.ConditionResolver +import im.vector.matrix.android.api.pushrules.PushRuleService +import im.vector.matrix.android.api.session.pushers.PushersService +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 retrofit2.Retrofit + +@Module +internal abstract class PushersModule { + + @Module + companion object { + + @JvmStatic + @Provides + fun providesPushersAPI(retrofit: Retrofit): PushersAPI { + return retrofit.create(PushersAPI::class.java) + } + + @JvmStatic + @Provides + fun providesPushRulesApi(retrofit: Retrofit): PushRulesApi { + return retrofit.create(PushRulesApi::class.java) + } + + } + + @Binds + abstract fun bindPusherService(pusherService: DefaultPusherService): PushersService + + @Binds + abstract fun bindConditionResolver(conditionResolver: DefaultConditionResolver): ConditionResolver + + @Binds + abstract fun bindGetPushersTask(getPusherTask: DefaultGetPusherTask): GetPushersTask + + @Binds + abstract fun bindGetPushRulesTask(getPushRulesTask: DefaultGetPushRulesTask): GetPushRulesTask + + @Binds + abstract fun bindRemovePusherTask(removePusherTask: DefaultRemovePusherTask): RemovePusherTask + + @Binds + abstract fun bindUpdatePushRuleEnableStatusTask(updatePushRuleEnableStatusTask: DefaultUpdatePushRuleEnableStatusTask): UpdatePushRuleEnableStatusTask + + @Binds + abstract fun bindPushRuleService(pushRuleService: DefaultPushRuleService): PushRuleService + + @Binds + abstract fun bindProcessEventForPushTask(processEventForPushTask: DefaultProcessEventForPushTask): ProcessEventForPushTask + +} \ 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 00000000..8f32930c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt @@ -0,0 +1,64 @@ +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 +import javax.inject.Inject + +internal interface RemovePusherTask : Task { + data class Params(val userId: String, + val pushKey: String, + val pushAppId: String) +} + +internal class DefaultRemovePusherTask @Inject constructor( + 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 deleteBody = JsonPusher( + pushKey = params.pushKey, + appId = params.pushAppId, + // kind null deletes the pusher + kind = null, + 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(deleteBody) + } + }.flatMap { + monarchy.tryTransactionSync { + val existing = PusherEntity.where(it, params.userId, params.pushKey).findFirst() + existing?.deleteFromRealm() + } + } + } + + +} \ No newline at end of file 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 00000000..9af67a6b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.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.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 +import javax.inject.Inject + + +internal interface UpdatePushRuleEnableStatusTask : Task { + data class Params(val kind: String, + val pushRule: PushRule, + val enabled: Boolean) +} + +internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor(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/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 44249c59..3e2958d5 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 @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.room import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations -import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.Room @@ -34,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 import javax.inject.Inject internal class DefaultRoom @Inject constructor(override val roomId: String, @@ -46,14 +46,14 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val relationService: RelationService, private val roomMembersService: MembershipService ) : Room, - TimelineService by timelineService, - SendService by sendService, - StateService by stateService, - ReadService by readService, - RelationService by relationService, - MembershipService by roomMembersService { + TimelineService by timelineService, + SendService by sendService, + StateService by stateService, + ReadService by readService, + 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) } @@ -69,6 +69,12 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, } } + override val roomSummary: RoomSummary? + get() { + var sum: RoomSummaryEntity? = monarchy.fetchCopied { 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/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index 2ed98f92..401cb096 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -229,7 +229,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(private sum.count = 1 sum.sourceEvents.add(reactionEventId) } - sum.addedByMe = sum.addedByMe || (userId == event.sender) + sum.addedByMe = sum.addedByMe || (userId == event.senderId) eventSummary.reactionsSummary.add(sum) } else { //is this a known event (is possible? pagination?) @@ -251,7 +251,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(private sum.sourceEvents.add(reactionEventId) } - sum.addedByMe = sum.addedByMe || (userId == event.sender) + sum.addedByMe = sum.addedByMe || (userId == event.senderId) } } 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 fe7fbb4b..cd7be990 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 @@ -68,8 +68,8 @@ internal class RoomFactory @Inject constructor(private val context: Context, val sendService = DefaultSendService(context, credentials, 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 reactionService = DefaultRelationService(context, credentials, roomId, eventFactory, findReactionEventForUndoTask, updateQuickReactionTask, monarchy, taskExecutor) + val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials) + val relationService = DefaultRelationService(context, credentials, roomId, eventFactory, findReactionEventForUndoTask, monarchy, taskExecutor) return DefaultRoom( roomId, @@ -79,7 +79,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, stateService, readService, cryptoService, - reactionService, + relationService, roomMembersService ) } 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 4beb61fc..9cf6ac69 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 @@ -138,5 +138,4 @@ internal abstract class RoomModule { @Binds abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService - } 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 a64071a5..06835374 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 @@ -27,7 +27,6 @@ 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.util.Cancelable import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask @@ -68,6 +67,14 @@ internal class DefaultMembershipService @Inject constructor(private val roomId: ) } + 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/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt index dd395909..41d5879c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt @@ -54,7 +54,7 @@ internal class RoomMembers(private val realm: Realm, } return EventEntity .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .equalTo(EventEntityFields.CONTENT, displayName) + .contains(EventEntityFields.CONTENT, "\"displayname\":\"$displayName\"") .distinct(EventEntityFields.STATE_KEY) .findAll() .size == 1 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 321e9890..3edfed82 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,19 +18,25 @@ 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.Credentials 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.session.SessionScope +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 import javax.inject.Inject internal class DefaultReadService @Inject constructor(private val roomId: String, - private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, - private val setReadMarkersTask: SetReadMarkersTask) : ReadService { + private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor, + private val setReadMarkersTask: SetReadMarkersTask, + private val credentials: Credentials) : ReadService { override fun markAllAsRead(callback: MatrixCallback) { val latestEvent = getLatestEvent() @@ -52,5 +58,20 @@ internal class DefaultReadService @Inject constructor(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, 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/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index bbac2d0a..9b07bb28 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -50,7 +50,6 @@ internal class DefaultRelationService @Inject constructor(private val context: C private val roomId: String, private val eventFactory: LocalEchoEventFactory, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, - private val updateQuickReactionTask: UpdateQuickReactionTask, private val monarchy: Monarchy, private val taskExecutor: TaskExecutor) : RelationService { @@ -68,7 +67,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C private fun createSendRelationWork(event: Event): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(credentials.userId,roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(sendWorkData) @@ -107,42 +106,11 @@ internal class DefaultRelationService @Inject constructor(private val context: C } - - override fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) { - - val params = UpdateQuickReactionTask.Params( - roomId, - targetEventId, - reaction, - oppositeReaction, - myUserId - ) - - updateQuickReactionTask.configureWith(params) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: UpdateQuickReactionTask.Result) { - data.reactionToAdd?.also { sendReaction(it, targetEventId) } - data.reactionToRedact.forEach { - val redactEvent = eventFactory.createRedactEvent(roomId, it, null).also { - saveLocalEcho(it) - } - val redactWork = createRedactEventWork(redactEvent, it, null) - TimelineSendEventWorkCommon.postWork(context, roomId, redactWork) - } - } - }) - .executeBy(taskExecutor) - } - - private fun buildWorkIdentifier(identifier: String): String { - return "${roomId}_$identifier" - } - //TODO duplicate with send service? private fun createRedactEventWork(localEvent: Event, eventId: String, reason: String?): OneTimeWorkRequest { - val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId ,localEvent.eventId!!, - roomId, eventId, reason) + val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId, localEvent.eventId!!, + roomId, eventId, reason) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(redactWorkData) } @@ -151,7 +119,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C val event = eventFactory.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, MessageType.MSGTYPE_TEXT, compatibilityBodyText).also { saveLocalEcho(it) } - val sendContentWorkerParams = SendEventWorker.Params(credentials.userId ,roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) //TODO use relation API? @@ -167,7 +135,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText)?.also { saveLocalEcho(it) } ?: return null - val sendContentWorkerParams = SendEventWorker.Params(credentials.userId ,roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) @@ -197,9 +165,9 @@ internal class DefaultRelationService @Inject constructor(private val context: C private fun saveLocalEcho(event: Event) { monarchy.tryTransactionAsync { realm -> val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() - ?: return@tryTransactionAsync + ?: return@tryTransactionAsync val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId) - ?: return@tryTransactionAsync + ?: return@tryTransactionAsync roomEntity.addSendingEvent(event, liveChunk.forwardsStateIndex ?: 0) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt index b269f161..16e5f52f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt @@ -16,9 +16,8 @@ package im.vector.matrix.android.internal.session.room.relation import android.content.Context -import androidx.work.Worker +import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.squareup.inject.assisted.AssistedInject import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.events.model.Event @@ -28,13 +27,12 @@ import im.vector.matrix.android.api.session.room.model.relation.ReactionInfo import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.send.SendResponse -import im.vector.matrix.android.internal.worker.DelegateWorkerFactory import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import javax.inject.Inject -internal class SendRelationWorker(context: Context, params: WorkerParameters): Worker(context, params) { +internal class SendRelationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( @@ -42,13 +40,13 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters): W val roomId: String, val event: Event, val relationType: String? = null - ): SessionWorkerParams + ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI - override fun doWork(): Result { + override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.failure() + ?: return Result.failure() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() sessionComponent.inject(this) @@ -58,10 +56,10 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters): W return Result.failure() } val relationContent = localEvent.content.toModel() - ?: return Result.failure() + ?: return Result.failure() val relatedEventId = relationContent.relatesTo?.eventId ?: return Result.failure() val relationType = (relationContent.relatesTo as? ReactionInfo)?.type ?: params.relationType - ?: return Result.failure() + ?: return Result.failure() val result = executeRequest { apiCall = roomAPI.sendRelation( @@ -73,15 +71,15 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters): W ) } return result.fold({ - when (it) { - is Failure.NetworkConnection -> Result.retry() - else -> { - //TODO mark as failed to send? - //always return success, or the chain will be stuck for ever! - Result.success() - } - } - }, { Result.success() }) + when (it) { + is Failure.NetworkConnection -> Result.retry() + else -> { + //TODO mark as failed to send? + //always return success, or the chain will be stuck for ever! + Result.success() + } + } + }, { Result.success() }) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index c5a775b7..fb6f1be0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -17,13 +17,7 @@ package im.vector.matrix.android.internal.session.room.send import android.content.Context -import androidx.work.BackoffPolicy -import androidx.work.Constraints -import androidx.work.ExistingWorkPolicy -import androidx.work.NetworkType -import androidx.work.OneTimeWorkRequest -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager +import androidx.work.* import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.content.ContentAttachmentData @@ -128,7 +122,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte private fun createEncryptEventWork(event: Event): OneTimeWorkRequest { // Same parameter - val params = EncryptEventWorker.Params(credentials.userId ,roomId, event) + val params = EncryptEventWorker.Params(credentials.userId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(params) return OneTimeWorkRequestBuilder() @@ -139,7 +133,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte } private fun createSendEventWork(event: Event): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(credentials.userId ,roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(sendWorkData) @@ -149,13 +143,13 @@ internal class DefaultSendService @Inject constructor(private val context: Conte val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also { saveLocalEcho(it) } - val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId ,redactEvent.eventId!!, roomId, event.eventId, reason) + val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId, redactEvent.eventId!!, roomId, event.eventId, reason) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(redactWorkData) } private fun createUploadMediaWork(event: Event, attachment: ContentAttachmentData): OneTimeWorkRequest { - val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId ,roomId, event, attachment) + val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId, roomId, event, attachment) val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams) return OneTimeWorkRequestBuilder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt index 4e6537b7..359eb181 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt @@ -19,8 +19,6 @@ package im.vector.matrix.android.internal.session.room.send import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters -import com.squareup.inject.assisted.Assisted -import com.squareup.inject.assisted.AssistedInject import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.failure.Failure @@ -28,9 +26,8 @@ import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult -import im.vector.matrix.android.internal.worker.WorkerParamsFactory -import im.vector.matrix.android.internal.worker.DelegateWorkerFactory import im.vector.matrix.android.internal.worker.SessionWorkerParams +import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import java.util.concurrent.CountDownLatch import javax.inject.Inject @@ -43,15 +40,15 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) override val userId: String, val roomId: String, val event: Event - ): SessionWorkerParams + ) : SessionWorkerParams - @Inject lateinit var crypto: CryptoService + @Inject lateinit var crypto: CryptoService @Inject lateinit var localEchoUpdater: LocalEchoUpdater override fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.success() + ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() sessionComponent.inject(this) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EventFactory.kt deleted file mode 100644 index d76b730e..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EventFactory.kt +++ /dev/null @@ -1,61 +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.room.send - -import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.api.session.events.model.Content -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.room.model.message.MessageTextContent -import im.vector.matrix.android.internal.di.MoshiProvider -import im.vector.matrix.android.internal.session.SessionScope -import javax.inject.Inject - -internal class EventFactory @Inject constructor(private val credentials: Credentials) { - - private val moshi = MoshiProvider.providesMoshi() - - fun createTextEvent(roomId: String, msgType: String, text: String): Event { - val content = MessageTextContent(type = msgType, body = text) - - return Event( - roomId = roomId, - originServerTs = dummyOriginServerTs(), - sender = credentials.userId, - eventId = dummyEventId(roomId), - type = EventType.MESSAGE, - content = toContent(content) - ) - } - - private fun dummyOriginServerTs(): Long { - return System.currentTimeMillis() - } - - private fun dummyEventId(roomId: String): String { - return roomId + "-" + dummyOriginServerTs() - } - - @Suppress("UNCHECKED_CAST") - private inline fun toContent(data: T?): Content? { - val moshiAdapter = moshi.adapter(T::class.java) - val jsonValue = moshiAdapter.toJsonValue(data) - return jsonValue as? Content? - } - - -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 924025f3..dbdfe124 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -133,7 +133,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), - sender = credentials.userId, + senderId = credentials.userId, eventId = localId, type = EventType.REACTION, content = content.toContent(), @@ -224,7 +224,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), - sender = credentials.userId, + senderId = credentials.userId, eventId = localID, type = EventType.MESSAGE, content = content.toContent(), @@ -244,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials //Fallbacks and event representation //TODO Add error/warning logs when any of this is null val permalink = PermalinkFactory.createPermalink(eventReplied) ?: return null - val userId = eventReplied.sender ?: return null + val userId = eventReplied.senderId ?: return null val userLink = PermalinkFactory.createPermalink(userId) ?: return null // //
@@ -333,7 +333,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), - sender = credentials.userId, + senderId = credentials.userId, eventId = localID, type = EventType.REDACTION, redacts = eventId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt index ec063f3a..f4401ca0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt @@ -22,7 +22,6 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.util.tryTransactionAsync import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt index 23e1d92d..b3e2edcf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt @@ -16,20 +16,18 @@ package im.vector.matrix.android.internal.session.room.send import android.content.Context -import androidx.work.Worker +import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.squareup.inject.assisted.AssistedInject import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI -import im.vector.matrix.android.internal.worker.DelegateWorkerFactory import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import javax.inject.Inject -internal class RedactEventWorker(context: Context, params: WorkerParameters) : Worker(context, params) { +internal class RedactEventWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( @@ -42,9 +40,9 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : W @Inject lateinit var roomAPI: RoomAPI - override fun doWork(): Result { + override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.failure() + ?: return Result.failure() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() sessionComponent.inject(this) @@ -59,17 +57,17 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : W ) } return result.fold({ - when (it) { - is Failure.NetworkConnection -> Result.retry() - else -> { - //TODO mark as failed to send? - //always return success, or the chain will be stuck for ever! - Result.success() - } - } - }, { - Result.success() - }) + when (it) { + is Failure.NetworkConnection -> Result.retry() + else -> { + //TODO mark as failed to send? + //always return success, or the chain will be stuck for ever! + Result.success() + } + } + }, { + Result.success() + }) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt index 948eb25b..6b6585ef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt @@ -17,24 +17,22 @@ package im.vector.matrix.android.internal.session.room.send import android.content.Context -import androidx.work.Worker +import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.squareup.inject.assisted.AssistedInject import com.squareup.moshi.JsonClass -import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI -import im.vector.matrix.android.internal.worker.DelegateWorkerFactory import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import javax.inject.Inject + internal class SendEventWorker constructor(context: Context, params: WorkerParameters) - : Worker(context, params) { + : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( @@ -46,9 +44,10 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam @Inject lateinit var localEchoUpdater: LocalEchoUpdater @Inject lateinit var roomAPI: RoomAPI - override fun doWork(): Result { + override suspend fun doWork(): Result { + val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.success() + ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() sessionComponent.inject(this) @@ -68,15 +67,15 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam ) } return result.fold({ - when (it) { - is Failure.NetworkConnection -> Result.retry() - else -> { - localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED) - //always return success, or the chain will be stuck for ever! - Result.success() - } - } - }, { Result.success() }) + when (it) { + is Failure.NetworkConnection -> Result.retry() + else -> { + localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED) + //always return success, or the chain will be stuck for ever! + Result.success() + } + } + }, { Result.success() }) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt index b3ae4442..053bf7f7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt @@ -19,14 +19,13 @@ package im.vector.matrix.android.internal.session.room.state import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.state.StateService -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import javax.inject.Inject internal class DefaultStateService @Inject constructor(private val roomId: String, - private val taskExecutor: TaskExecutor, - private val sendStateTask: SendStateTask) : StateService { + private val taskExecutor: TaskExecutor, + private val sendStateTask: SendStateTask) : StateService { override fun updateTopic(topic: String, callback: MatrixCallback) { val params = SendStateTask.Params(roomId, 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 445e71cf..fbf22c37 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 @@ -41,10 +41,11 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St override fun getTimeLineEvent(eventId: String): TimelineEvent? { return monarchy .fetchCopyMap({ - EventEntity.where(it, eventId = eventId).findFirst() - }, { entity, realm -> - timelineEventFactory.create(entity, realm) - }) + EventEntity.where(it, eventId = eventId).findFirst() + }, { entity, realm -> + timelineEventFactory.create(entity, realm) + }) } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt index fa602500..c2f9827d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.session.room.EventRelationExtractor +import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor import io.realm.Realm import timber.log.Timber @@ -50,7 +51,11 @@ internal class TimelineEventFactory @Inject constructor( val cacheKey = sender + eventEntity.localId val senderData = senderCache.getOrPut(cacheKey) { val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm) - SenderData(senderRoomMember?.displayName, senderRoomMember?.avatarUrl) + val isUniqueDisplayName = RoomMembers(realm, eventEntity.roomId).isUniqueDisplayName(senderRoomMember?.displayName) + + SenderData(senderRoomMember?.displayName, + isUniqueDisplayName, + senderRoomMember?.avatarUrl) } val event = eventEntity.asDomain() if (event.getClearType() == EventType.ENCRYPTED) { @@ -63,6 +68,7 @@ internal class TimelineEventFactory @Inject constructor( eventEntity.localId, eventEntity.displayIndex, senderData.senderName, + senderData.isUniqueDisplayName, senderData.senderAvatar, eventEntity.sendState, relations @@ -97,7 +103,7 @@ internal class TimelineEventFactory @Inject constructor( private data class SenderData( val senderName: String?, + val isUniqueDisplayName: Boolean, val senderAvatar: String? ) - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt index f24e7b46..e8011b8c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt @@ -18,8 +18,6 @@ package im.vector.matrix.android.internal.session.signout import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.signout.SignOutService -import im.vector.matrix.android.internal.SessionManager -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt index b4863090..c123dd01 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt @@ -21,8 +21,6 @@ import dagger.Module import dagger.Provides import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.internal.session.SessionScope -import im.vector.matrix.android.internal.session.user.DefaultUpdateUserTask -import im.vector.matrix.android.internal.session.user.UpdateUserTask import retrofit2.Retrofit @Module diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt index baf67191..903763e4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -18,11 +18,9 @@ package im.vector.matrix.android.internal.session.signout import arrow.core.Try import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.network.executeRequest -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.Task import javax.inject.Inject 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 35f0775b..78f949de 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 @@ -24,7 +24,6 @@ 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.network.executeRequest -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.task.Task @@ -32,7 +31,7 @@ import javax.inject.Inject internal interface SyncTask : Task { - data class Params(val token: String?) + data class Params(val token: String?, var timeout: Long = 30_000L) } @@ -46,10 +45,10 @@ internal class DefaultSyncTask @Inject constructor(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() @@ -59,7 +58,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, }.recoverWith { throwable -> // Intercept 401 if (throwable is Failure.ServerError - && throwable.error.code == MatrixError.UNKNOWN_TOKEN) { + && throwable.error.code == MatrixError.UNKNOWN_TOKEN) { sessionParamsStore.delete(credentials.userId) } 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 00000000..a1872d0a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -0,0 +1,242 @@ +/* + * 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.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 timber.log.Timber +import java.net.SocketTimeoutException +import java.util.* +import javax.inject.Inject +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 + */ +internal open class SyncService : Service() { + + private var mIsSelfDestroyed: Boolean = false + private var cancelableTask: Cancelable? = null + + @Inject lateinit var syncTokenStore: SyncTokenStore + @Inject lateinit var syncTask: SyncTask + @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker + @Inject lateinit var taskExecutor: TaskExecutor + + 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(true) + } 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(once: Boolean = false) { + var nextBatch = syncTokenStore.getLastToken() + if (!networkConnectivityChecker.isConnected()) { + Timber.v("Sync is Paused. Waiting...") + //TODO Retry in ? + timer.schedule(object : TimerTask() { + override fun run() { + doSync() + } + }, 5_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() + if (!once) { + timer.schedule(object : TimerTask() { + override fun run() { + doSync() + } + }, nextBatchDelay) + } else { + //stop + stopMe() + } + } + + 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() + } + }, 5_000L) + } + + if (failure !is Failure.NetworkConnection + || failure.cause is JsonEncodingException) { + // Wait 10s before retrying + timer.schedule(object : TimerTask() { + override fun run() { + doSync() + } + }, 5_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/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 8825c4f4..ba4c009a 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 @@ -39,6 +39,8 @@ import java.util.concurrent.CountDownLatch import javax.inject.Inject private const val RETRY_WAIT_TIME_MS = 10_000L +private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L +private const val DEFAULT_LONG_POOL_DELAY = 0L internal class SyncThread @Inject constructor(private val syncTask: SyncTask, private val networkConnectivityChecker: NetworkConnectivityChecker, @@ -93,14 +95,14 @@ internal class SyncThread @Inject constructor(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 $DEFAULT_LONG_POOL_TIMEOUT") val latch = CountDownLatch(1) - val params = SyncTask.Params(nextBatch) + val params = SyncTask.Params(nextBatch, DEFAULT_LONG_POOL_TIMEOUT) cancelableTask = syncTask.configureWith(params) .callbackOn(TaskThread.CALLER) .executeOn(TaskThread.CALLER) @@ -137,10 +139,15 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, }) .executeBy(taskExecutor) - latch.await() + + latch.await() if (state is SyncState.RUNNING) { updateStateTo(SyncState.RUNNING(catchingUp = false)) } + + 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") } } Timber.v("Sync killed") @@ -168,7 +175,6 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, 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 new file mode 100644 index 00000000..6b2af8fc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -0,0 +1,123 @@ +/* + * 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 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.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.worker.WorkerParamsFactory +import im.vector.matrix.android.internal.worker.getSessionComponent +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + + +private const val DEFAULT_LONG_POOL_TIMEOUT = 0L + +internal class SyncWorker(context: Context, + workerParameters: WorkerParameters +) : CoroutineWorker(context, workerParameters) { + + @JsonClass(generateAdapter = true) + internal data class Params( + val userId: String, + val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT, + val automaticallyRetry: Boolean = false + ) + + @Inject lateinit var syncAPI: SyncAPI + @Inject lateinit var filterRepository: FilterRepository + @Inject lateinit var syncResponseHandler: SyncResponseHandler + @Inject lateinit var sessionParamsStore: SessionParamsStore + @Inject lateinit var syncTokenStore: SyncTokenStore + + + override suspend fun doWork(): Result { + Timber.i("Sync work starting") + val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success() + val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + sessionComponent.inject(this) + + 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) + }.fold( + { + if (it is Failure.ServerError + && it.error.code == MatrixError.UNKNOWN_TOKEN) { + sessionParamsStore.delete(params.userId) + Result.failure() + } else { + Timber.i("Sync work failed $it") + Result.retry() + } + }, + { + Timber.i("Sync work success next batch ${it.nextBatch}") + if (!isStopped) { + syncResponseHandler.handleResponse(it, token, false) + syncTokenStore.saveToken(it.nextBatch) + } + if (params.automaticallyRetry) Result.retry() else Result.success() + } + ) + } + + companion object { + fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) { + val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false)) + val workRequest = OneTimeWorkRequestBuilder() + .setInputData(data) + .setConstraints(Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build()) + .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) + .build() + WorkManager.getInstance(context).enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) + } + + fun automaticallyBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0, delay: Long = 30_000) { + val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, true)) + val workRequest = OneTimeWorkRequestBuilder() + .setInputData(data) + .setConstraints(Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build()) + .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) + .build() + WorkManager.getInstance(context).enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) + } + + fun stopAnyBackgroundSync(context: Context) { + WorkManager.getInstance(context).cancelUniqueWork("BG_SYNCP") + } + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt index cfab8ef4..c46b86bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt @@ -19,7 +19,8 @@ package im.vector.matrix.android.internal.task import arrow.core.Try import im.vector.matrix.android.api.util.Cancelable -import im.vector.matrix.android.internal.di.MatrixScope +import im.vector.matrix.android.internal.extensions.foldToCallback +import im.vector.matrix.android.internal.extensions.onError import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope @@ -41,12 +42,11 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers task.execute(task.params) } } - resultOrFailure.fold({ - Timber.d(it, "Task failed") - task.callback.onFailure(it) - }, { - task.callback.onSuccess(it) - }) + resultOrFailure + .onError { + Timber.d(it, "Task failed") + } + .foldToCallback(task.callback) } return CancelableCoroutine(job) } diff --git a/matrix-sdk-android/src/main/res/values-bg/strings.xml b/matrix-sdk-android/src/main/res/values-bg/strings.xml index 897be232..106febb9 100644 --- a/matrix-sdk-android/src/main/res/values-bg/strings.xml +++ b/matrix-sdk-android/src/main/res/values-bg/strings.xml @@ -85,4 +85,69 @@ Съобщение премахнато от %1$s Премахнато съобщение [причина: %1$s] Съобщение премахнато от %1$s [причина: %2$s] + Куче + Котка + Лъв + Кон + Еднорог + Прасе + Слон + Заек + Панда + Петел + Пингвин + Костенурка + Риба + Октопод + Пеперуда + Цвете + Дърво + Кактус + Гъба + Глобус + Луна + Облак + Огън + Банан + Ябълка + Ягода + Царевица + Пица + Торта + Сърце + Усмивка + Робот + Шапка + Очила + Гаечен ключ + Дядо Коледа + Палец нагоре + Чадър + Пясъчен часовник + Часовник + Подарък + Лампа + Книга + Молив + Кламер + Ножици + Катинар + Ключ + Чук + Телефон + Знаме + Влак + Колело + Самолет + Ракета + Трофей + Топка + Китара + Тромпет + Звънец + Котва + Слушалки + Папка + Карфица + diff --git a/matrix-sdk-android/src/main/res/values-es/strings.xml b/matrix-sdk-android/src/main/res/values-es/strings.xml index 232ae0d4..0acb2bfc 100644 --- a/matrix-sdk-android/src/main/res/values-es/strings.xml +++ b/matrix-sdk-android/src/main/res/values-es/strings.xml @@ -92,4 +92,8 @@ + Mensaje eliminado + Mensaje eliminado por %1$s + Mensaje eliminado [motivo: %1$s] + Mensaje eliminado por %1$s [motivo: %2$s] diff --git a/matrix-sdk-android/src/main/res/values-eu/strings.xml b/matrix-sdk-android/src/main/res/values-eu/strings.xml index 4dfab9f4..00bcf69e 100644 --- a/matrix-sdk-android/src/main/res/values-eu/strings.xml +++ b/matrix-sdk-android/src/main/res/values-eu/strings.xml @@ -85,4 +85,69 @@ %1$s erabiltzaileak mezua kendu du Mezua kendu da [arrazoia: %1$s] %1$s erabiltzaileak mezua kendu du [arrazoia: %2$s] + Txakurra + Katua + Lehoia + Zaldia + Unikornioa + Zerria + Elefantea + Untxia + Panda + Oilarra + Pinguinoa + Dortoka + Arraina + Olagarroa + Tximeleta + Lorea + Zuhaitza + Kaktusa + Perretxikoa + Lurra + Ilargia + Hodeia + Sua + Banana + Sagarra + Marrubia + Artoa + Pizza + Pastela + Bihotza + Irrifartxoa + Robota + Txanoa + Betaurrekoak + Giltza ingelesa + Santa + Ederto + Aterkia + Harea-erlojua + Erlojua + Oparia + Bonbilla + Liburua + Arkatza + Klipa + Artaziak + Giltzarrapoa + Giltza + Mailua + Telefonoa + Bandera + Trena + Bizikleta + Hegazkina + Kohetea + Saria + Baloia + Gitarra + Tronpeta + Kanpaia + Aingura + Aurikularrak + Karpeta + Txintxeta + diff --git a/matrix-sdk-android/src/main/res/values-fi/strings.xml b/matrix-sdk-android/src/main/res/values-fi/strings.xml index d76293b2..a405c14c 100644 --- a/matrix-sdk-android/src/main/res/values-fi/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fi/strings.xml @@ -86,4 +86,69 @@ %1$s poisti viestin Viesti poistettu [syy: %1$s] %1$s poisti viestin [syy: %2$s] + Koira + Kissa + Leijona + Hevonen + Yksisarvinen + Sika + Norsu + Kani + Panda + Kukko + Pingviini + Kilpikonna + Kala + Tursas + Perhonen + Kukka + Puu + Kaktus + Sieni + Maapallo + Kuu + Pilvi + Tuli + Banaani + Omena + Mansikka + Maissi + Pizza + Kakku + Sydän + Hymiö + Robotti + Hattu + Silmälasit + Jakoavain + Joulupukki + Peukut ylös + Sateenvarjo + Tiimalasi + Kello + Lahja + Hehkulamppu + Kirja + Lyijykynä + Klemmari + Sakset + Lukko + Avain + Vasara + Puhelin + Lippu + Juna + Polkupyörä + Lentokone + Raketti + Palkinto + Pallo + Kitara + Trumpetti + Soittokello + Ankkuri + Kuulokkeet + Kansio + Nuppineula + diff --git a/matrix-sdk-android/src/main/res/values-fr/strings.xml b/matrix-sdk-android/src/main/res/values-fr/strings.xml index c54e1b7c..542f0174 100644 --- a/matrix-sdk-android/src/main/res/values-fr/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fr/strings.xml @@ -15,7 +15,7 @@ %1$s a banni %2$s %1$s a annulé l’invitation de %2$s %1$s a changé d’avatar - %1$s a réglé son nom affiché en %2$s + %1$s a modifié son nom affiché en %2$s %1$s a modifié son nom affiché %2$s en %3$s %1$s a supprimé son nom affiché (%2$s) %1$s a changé le sujet en : %2$s @@ -85,4 +85,69 @@ Message supprimé par %1$s Message supprimé [motif : %1$s] Message supprimé par %1$s [motif : %2$s] + Chien + Chat + Lion + Cheval + Licorne + Cochon + Éléphant + Lapin + Panda + Coq + Manchot + Tortue + Poisson + Pieuvre + Papillon + Fleur + Arbre + Cactus + Champignon + Terre + Lune + Nuage + Feu + Banane + Pomme + Fraise + Maïs + Pizza + Gâteau + Cœur + Smiley + Robot + Chapeau + Lunettes + Clé plate + Père Noël + Pouce levé + Parapluie + Sablier + Horloge + Cadeau + Ampoule + Livre + Crayon + Trombone + Ciseaux + Cadenas + Clé + Marteau + Téléphone + Drapeau + Train + Vélo + Avion + Fusée + Trophée + Balle + Guitare + Trompette + Cloche + Ancre + Écouteurs + Dossier + Épingle + diff --git a/matrix-sdk-android/src/main/res/values-hu/strings.xml b/matrix-sdk-android/src/main/res/values-hu/strings.xml index 5e4881be..cf257cab 100644 --- a/matrix-sdk-android/src/main/res/values-hu/strings.xml +++ b/matrix-sdk-android/src/main/res/values-hu/strings.xml @@ -84,4 +84,69 @@ Üzenetet eltávolította: %1$s Üzenet eltávolítva [ok: %1$s] Üzenetet eltávolította: %1$s [ok: %2$s] + Kutya + Macska + Oroszlán + + Egyszarvú + Malac + Elefánt + Nyúl + Panda + Kakas + Pingvin + Teknős + Hal + Polip + Pillangó + Virág + Fa + Kaktusz + Gomba + Föld + Hold + Felhő + Tűz + Banán + Alma + Eper + Kukorica + Pizza + Süti + Szív + Smiley + Robot + Kalap + Szemüveg + Csavarkulcs + Télapó + Hüvelykujj fel + Esernyő + Homokóra + Óra + Ajándék + Égő + Könyv + Ceruza + Gémkapocs + Olló + Zár + Kulcs + Kalapács + Telefon + Zászló + Vonat + Kerékpár + Repülő + Rakéta + Trófea + Labda + Gitár + Trombita + Harang + Vasmacska + Fejhallgató + Mappa + + diff --git a/matrix-sdk-android/src/main/res/values-it/strings.xml b/matrix-sdk-android/src/main/res/values-it/strings.xml index bfa3d559..7adc3638 100644 --- a/matrix-sdk-android/src/main/res/values-it/strings.xml +++ b/matrix-sdk-android/src/main/res/values-it/strings.xml @@ -85,4 +85,69 @@ Messaggio rimosso da %1$s Messaggio rimosso [motivo: %1$s] Messaggio rimosso da %1$s [motivo: %2$s] + Cane + Gatto + Leone + Cavallo + Unicorno + Maiale + Elefante + Coniglio + Panda + Gallo + Pinguino + Tartaruga + Pesce + Piovra + Farfalla + Fiore + Albero + Cactus + Fungo + Globo + Luna + Nuvola + Fuoco + Banana + Mela + Fragola + Mais + Pizza + Torta + Cuore + Sorriso + Robot + Cappello + Occhiali + Chiave inglese + Babbo Natale + Pollice in su + Ombrello + Clessidra + Orologio + Regalo + Lampadina + Libro + Matita + Graffetta + Forbici + Lucchetto + Chiave + Martello + Telefono + Bandiera + Treno + Bicicletta + Aeroplano + Razzo + Trofeo + Palla + Chitarra + Tromba + Campana + Ancora + Cuffie + Cartella + Spillo + diff --git a/matrix-sdk-android/src/main/res/values-nl/strings.xml b/matrix-sdk-android/src/main/res/values-nl/strings.xml index cd04670c..c20124d1 100644 --- a/matrix-sdk-android/src/main/res/values-nl/strings.xml +++ b/matrix-sdk-android/src/main/res/values-nl/strings.xml @@ -94,4 +94,69 @@ Bericht verwijderd door %1$s Bericht verwijderd [reden: %1$s] Bericht verwijderd door %1$s [reden: %2$s] + Hond + Kat + Leeuw + Paard + Eenhoorn + Varken + Olifant + Konijn + Panda + Haan + Pinguïn + Schildpad + Vis + Octopus + Vlinder + Bloem + Boom + Cactus + Paddenstoel + Aardbol + Maan + Wolk + Vuur + Banaan + Appel + Aardbei + Maïs + Pizza + Taart + Hart + Smiley + Robot + Hoed + Bril + Moersleutel + Kerstman + Duim omhoog + Paraplu + Zandloper + Klok + Cadeau + Gloeilamp + Boek + Potlood + Paperclip + Schaar + Hangslot + Sleutel + Hamer + Telefoon + Vlag + Trein + Fiets + Vliegtuig + Raket + Trofee + Bal + Gitaar + Trompet + Bel + Anker + Koptelefoon + Map + Speld + diff --git a/matrix-sdk-android/src/main/res/values-nn/strings.xml b/matrix-sdk-android/src/main/res/values-nn/strings.xml index ea13ece8..a8490200 100644 --- a/matrix-sdk-android/src/main/res/values-nn/strings.xml +++ b/matrix-sdk-android/src/main/res/values-nn/strings.xml @@ -4,73 +4,73 @@ %1$s: %2$s - %1$s sende eit bilete. + %1$s sende eit bilæte. %1$s sende eit klistremerke. - %s si innbyding + %s si innbjoding %1$s baud %2$s inn %1$s baud deg inn %1$s kom inn %1$s fór ut - %1$s sa nei til innbydinga + %1$s sa nei til innbjodingi %1$s sparka %2$s %1$s slapp %2$s inn att %1$s stengde %2$s ute - %1$s tok attende %2$s si innbyding - %1$s endra avataren sin + %1$s tok attende %2$s si innbjoding + %1$s byta avataren sin %1$s sette visingsnamnet sitt som %2$s - %1$s endra visingsnamnet sitt frå %2$s til %3$s - %1$s fjerna visingsnamnet sitt (%2$s) - %1$s endra emnet til: %2$s - %1$s endra romnamnet til: %2$s - %s starta ei videosamtale. - %s starta ei røystsamtale. - %s tok røret. - %s la på røret. - %1$s gjorde den framtidige romhistoria synleg for %2$s + %1$s byta visingsnamnet sitt frå %2$s til %3$s + %1$s tok burt visingsnamnet sitt (%2$s) + %1$s gjorde emnet til: %2$s + %1$s gjorde romnamnet til: %2$s + %s starta ei videosamtala. + %s starta ei røystsamtala. + %s tok røyret. + %s la på røyret. + %1$s gjorde den framtidige romsoga synleg for %2$s alle rommedlemer, frå då dei vart bodne inn. alle rommedlemer, frå då dei kom inn. alle rommedlemer. - kven som helst. - ukjend (%s). - %1$s skrudde ende-til-ende-enkryptering på (%2$s) + kven som heldst. + uvisst (%s). + %1$s skrudde ende-til-ende-kryptering på (%2$s) - %1$s bad om ei VoIP-gruppesamtale - VoIP-gruppesamtala er starta - VoIP-gruppesamtala er ferdig + %1$s bad um ei VoIP-gruppasamtala + VoIP-gruppasamtala er starta + VoIP-gruppasamtala er ferdug - (avataren vart endra òg) - %1$s fjerna romnamnet - %1$s fjerna romemnet - %1$s oppdaterte profilbiletet sitt %2$s - %1$s baud %2$s inn i rommet - %1$s sa ja til innbydinga til %2$s + (avataren vart au byta) + %1$s tok burt romnamnet + %1$s tok burt romemnet + %1$s gjorde um på skildringi si %2$s + %1$s baud %2$s inn i romet + %1$s sa ja til innbjodingi til %2$s - ** Klarte ikkje dekryptere: %s ** - Avsendareininga har ikkje sendt oss nyklane for denne meldinga. + ** Fekk ikkje til å dekryptera: %s ** + Avsendareiningi hev ikkje sendt oss nyklane fyr denna meldingi. Som svar til - Kunne ikkje gjera om - Klarte ikkje å senda meldinga + Kunde ikkje gjera um + Fekk ikkje å senda meldingi - Fekk ikkje til å lasta biletet opp + Fekk ikkje til å lasta biletet upp - Noko gjekk gale med nettverket + Noko gjekk gale med netverket Noko gjekk gale med Matrix - Det er førebels ikkje mogeleg å fara inn att i eit tomt rom. + Det lèt seg fyrebils ikkje gjera å fara inn att i eit tomt rom. Epostadresse Telefonnummer - sende eit bilete. + sende eit bilæte. sende ein video. - sende ein ljodfil. + sende ei ljodfil. sende ei fil. - Byd inn frå %s - Rominnbyding + Innbjoding frå %s + Rominnbjoding %1$s og %2$s @@ -80,4 +80,73 @@ Tomt rom + Ei melding vart stroki + %1$s strauk meldingi + Meldingi vart stroki [av di: %1$s] + %1$s strauk meldingi [av di: %2$s] + Hund + Katt + Løva + Hest + Einhyrning + Gris + Elefant + Hare + Panda + Hane + Pingvin + Skjoldpadda + Fisk + Blekksprut + Fivrelde + Blome + Tre + Kaktus + Sopp + Klote + Måne + Sky + Eld + Banan + Eple + Jordbær + Mais + Pizza + Kaka + Hjarta + Smilandlit + Robot + Hatt + Brillor + Skiftenykel + Nissen + Tumalen Upp + Regnskjold + Timeglas + Ur + Gåva + Ljospera + Bok + Blyant + Binders + Saks + Lås + Nykel + Hamar + Telefon + Flagg + Tog + Sykkel + Flyg + Rakett + Pokal + Ball + Gitar + Trompet + Klokka + Ankar + Hodetelefon + Mappa + Nål + diff --git a/matrix-sdk-android/src/main/res/values-pl/strings.xml b/matrix-sdk-android/src/main/res/values-pl/strings.xml index 1bb8aef3..4a807028 100644 --- a/matrix-sdk-android/src/main/res/values-pl/strings.xml +++ b/matrix-sdk-android/src/main/res/values-pl/strings.xml @@ -81,4 +81,70 @@ wyślij plik audio. wyślij plik. + Wiadomość usunięta + Wiadomość usunięta przez %1$s + Wiadomość usunięta [powód: %1$s] + Wiadomość usunięta przez %1$s [powód: %2$s] + Pies + Kot + Lew + Koń + Jednorożec + Świnia + Słoń + Królik + Panda + Kogut + Pingwin + Żółw + Ryba + Ośmiornica + Motyl + Kwiat + Drzewo + Kaktus + Grzyb + Księżyc + Chmura + Ogień + Banan + Jabłko + Truskawka + Kukurydza + Pizza + Ciasto + Serce + Robot + Kapelusz + Okulary + Parasol + Klepsydra + Zegar + Żarówka + Książka + Ołówek + Spinacz + Nożyczki + Klucz + Telefon + Flaga + Pociąg + Rower + Samolot + Rakieta + Trofeum + Gitara + Trąbka + Dzwonek + Kotwica + Słuchawki + Folder + Pinezka + + Ziemia + Uśmiech + Klucz francuski + Mikołaj + Prezent + Młotek diff --git a/matrix-sdk-android/src/main/res/values-ru/strings.xml b/matrix-sdk-android/src/main/res/values-ru/strings.xml index 4799ecfd..63a45655 100644 --- a/matrix-sdk-android/src/main/res/values-ru/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ru/strings.xml @@ -94,4 +94,73 @@ + Сообщение удалено + %1$s удалил(а) сообщение + Сообщение удалено [причина: %1$s] + %1$s удалил(а) сообщение [причина: %2$s] + Собака + Кошка + Лев + Лошадь + Единорог + Поросёнок + Слон + Кролик + Панда + Петух + Пингвин + Черепаха + Рыба + Осьминог + Бабочка + Цветок + Дерево + Кактус + Гриб + Земля + Луна + Облако + Огонь + Банан + Яблоко + Клубника + Кукуруза + Пицца + Пирожное + Сердце + Смайлик + Робот + Шляпа + Очки + гаечный ключ + Санта + Большой палец вверх + Зонтик + Песочные часы + Часы + Подарок + Лампочка + Книга + Карандаш + Скрепка для бумаг + Ножницы + Замок + Ключ + Молоток + Телефон + Флаг + Поезд + Велосипед + Самолёт + Ракета + Трофей + Мяч + Гитара + Труба + Колокол + Якорь + Гарнитура + Папка + Булавка + diff --git a/matrix-sdk-android/src/main/res/values-sq/strings.xml b/matrix-sdk-android/src/main/res/values-sq/strings.xml index 0203f3fa..7c182df3 100644 --- a/matrix-sdk-android/src/main/res/values-sq/strings.xml +++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml @@ -84,4 +84,66 @@ Mesazhi u hoq nga %1$s Mesazh i hequr [arsye: %1$s] Mesazh i hequr nga %1$s [arsye: %2$s] + Qen + Mace + Luan + Kalë + Njëbrirësh + Derr + Elefant + Lepur + Panda + Këndes + Pinguin + Breshkë + Peshk + Oktapod + Flutur + Lule + Pemë + Kaktus + Kërpudhë + Rruzull + Hëna + Re + Zjarr + Banane + Mollë + Luleshtrydhe + Misër + Picë + Tortë + Zemër + Emotikon + Robot + Kapë + Syze + Çelës + Babagjyshi i Vitit të Ri + Ombrellë + Klepsidër + Sahat + Dhuratë + Llambë + Libër + Laps + Kapëse + Gërshërë + Dry + Kyç + Çekiç + Telefon + Flamur + Tren + Biçikletë + Aeroplan + Raketë + Trofe + Top + Kitarë + Trombë + Kambanë + Spirancë + Kufje + Dosje diff --git a/matrix-sdk-android/src/main/res/values-vls/strings.xml b/matrix-sdk-android/src/main/res/values-vls/strings.xml new file mode 100644 index 00000000..61da5173 --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-vls/strings.xml @@ -0,0 +1,153 @@ + + + %1$s: %2$s + %1$s èt e fotootje gesteurd. + %1$s èt e sticker gesteurd. + + Uutnodigienge van %s + %1$s èt %2$s uutgenodigd + %1$s èt joun uitgenodigd + %1$s neemt nu deel an ’t gesprek + %1$s èt ’t gesprek verloatn + %1$s èt d’uitnodigienge geweigerd + %1$s èt %2$s uut ’t gesprek verwyderd + %1$s èt %2$s ountbann + %1$s èt %2$s verbann + %1$s èt d’uutnodigienge van %2$s ingetrokkn + %1$s èt zyn/heur avatar angepast + %1$s èt zyn/heur noame angepast noa %2$s + %1$s èt zyn/heur noame angepast van %2$s noa %3$s + %1$s èt zyn/heur noame verwyderd (%2$s) + %1$s èt ’t ounderwerp veranderd noa: %2$s + %1$s èt de gespreksnoame veranderd noa: %2$s + %s èt e video-iproep gemakt. + %s èt e sproakiproep gemakt. + %s èt den iproep beantwoord. + %s èt ipgehangn. + %1$s èt de toekomstige gespreksgeschiedenisse zichtboar gemakt vo %2$s + alle deelnemers an ’t gesprek, vanaf ’t punt dan ze zyn uutgenodigd. + alle deelnemers an ’t gesprek, vanaf ’t punt dan ze zyn toegetreedn. + alle deelnemers an ’t gesprek. + iedereen. + ounbekend (%s). + %1$s èt eind-tout-eind-versleutelienge angezet (%2$s) + + %1$s èt e VoIP-vergoaderienge angevroagd + VoIP-vergoaderienge begunn + VoIP-vergoaderienge gestopt + + (avatar es ook veranderd) + %1$s èt de gespreksnoame verwyderd + %1$s èt ’t gespreksounderwerp verwyderd + Bericht verwyderd + Bericht verwyderd deur %1$s + Bericht verwyderd [reden: %1$s] + Bericht verwyderd deur %1$s [reden: %2$s] + %1$s èt zyn/heur profiel %2$s bygewerkt + %1$s èt een uutnodigienge noa %2$s gesteurd vo ’t gesprek toe te treden + %1$s èt d’uutnodigienge vo %2$s anveird + + ** Kun nie ountsleuteln: %s ** + ’t Toestel van den afzender èt geen sleutels vo dit bericht gesteurd. + + Als antwoord ip + + Kosteg nie verwyderd wordn + Kosteg ’t bericht nie verzendn + + Iploadn van ’t fotootje es mislukt + + Netwerkfout + Matrix-fout + + ’t Es vo de moment nie meuglik van e leeg gesprek were toe te treedn. + + Versleuteld bericht + + E-mailadresse + Telefongnumero + + èt e fotootje gesteurd. + èt e filmtje gesteurd. + èt e geluudsfragment gesteurd. + èt e bestand gesteurd. + + Uutnodigienge van %s + Gespreksuutnodigienge + + %1$s en %2$s + + + %1$s en 1 andere + %1$s en %2$d anderen + + + Leeg gesprek + + + Hound + Katte + Leeuw + Peird + Eenhoorn + Zwyn + Olifant + Keun + Panda + Hoane + Pinguin + Schildpadde + Vis + Octopus + Beutervlieg + Bloem + Boom + Cactus + Paddestoel + Eirdbol + Moane + Wolk + Vier + Banoan + Appel + Freize + Mais + Pizza + Toarte + Erte + Smiley + Robot + Hoed + Bril + Moersleutel + Kestman + Duum omhooge + Paraplu + Zandloper + Klok + Cadeau + Gloeilampe + Boek + Potlood + Paperclip + Schoar + Hangslot + Sleutel + Oamer + Telefong + Vlagge + Tring + Veloo + Vlieger + Rakette + Trofee + Bolle + Gitoar + Trompette + Belle + Anker + Koptelefong + Mappe + Pinne + + diff --git a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml index 5b3d5a7e..22cf1c0f 100644 --- a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml +++ b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml @@ -83,4 +83,69 @@ 訊息已被 %1$s 移除 訊息已移除 [理由:%1$s] 訊息已被 %1$s 移除 [理由:%2$s] + + + + + 獨角獸 + + + + 貓熊 + 公雞 + 企鵝 + + + 章魚 + + + + 仙人掌 + 蘑菇 + 地球 + 月亮 + + + 香蕉 + 蘋果 + 草莓 + 玉米 + 披薩 + 蛋糕 + + 微笑 + 機器人 + 帽子 + 眼鏡 + 扳手 + 聖誕老人 + + 雨傘 + 沙漏 + 時鐘 + 禮物 + 燈泡 + + 鉛筆 + 迴紋針 + 剪刀 + + 鑰匙 + 鎚子 + 電話 + 旗子 + 火車 + 腳踏車 + 飛機 + 火箭 + 獎盃 + + 吉他 + 喇叭 + + + 耳機 + 資料夾 + 別針 + diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 1414c723..be64a659 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -168,7 +168,7 @@ Glasses - Wrench + Spanner Santa @@ -206,7 +206,7 @@ Bicycle - Airplane + Aeroplane Rocket @@ -222,7 +222,7 @@ Anchor - Headphone + Headphones Folder 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 00000000..9c0e4e61 --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushRuleActionsTest.kt @@ -0,0 +1,74 @@ +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]" + } + ], + "actions": [ + "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 actions", Action.mapFrom(pushRule!!)) + + val actions = Action.mapFrom(pushRule) + Assert.assertEquals(3, actions!!.size) + + + Assert.assertEquals("First action should be notify", Action.Type.NOTIFY, actions[0].type) + + + 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, 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 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 00000000..11c2be1d --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -0,0 +1,294 @@ +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 { + + + @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)) + } + + + @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 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 { + 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/tools/check/forbidden_strings_in_resources.txt b/tools/check/forbidden_strings_in_resources.txt index 26d77255..120438ef 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) ] } -def buildNumber = System.getenv("BUILD_NUMBER") as Integer ?: 0 +def buildNumber = System.getenv("BUILDKITE_BUILD_NUMBER") as Integer ?: 0 android { compileSdkVersion 28 @@ -63,9 +64,16 @@ android { versionCode generateVersionCodeFromTimestamp() versionName "${versionMajor}.${versionMinor}.${versionPatch}" + buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" resValue "string", "git_revision", "\"${gitRevision()}\"" + + buildConfigField "String", "GIT_REVISION_DATE", "\"${gitRevisionDate()}\"" resValue "string", "git_revision_date", "\"${gitRevisionDate()}\"" + + buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\"" resValue "string", "git_branch_name", "\"${gitBranchName()}\"" + + buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\"" resValue "string", "build_number", "\"${buildNumber}\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -156,6 +164,9 @@ dependencies { implementation "com.squareup.moshi:moshi-adapters:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" + // OSS License + implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' + // Log implementation 'com.jakewharton.timber:timber:4.7.1' @@ -166,6 +177,7 @@ dependencies { implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0' + // TODO RxBindings3 exists implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0' implementation("com.airbnb.android:epoxy:$epoxy_version") @@ -175,7 +187,7 @@ dependencies { // Work implementation "androidx.work:work-runtime-ktx:2.1.0-beta01" - // FP + // Functional Programming implementation "io.arrow-kt:arrow-core:$arrow_version" // Pref diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 0cc39d63..042d9ef8 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 69% 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 82f2df9a..11826a7b 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) { + fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) { // No op } } 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 5663603b..0a3fdd22 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 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 4cb092b1..a13bc71a 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/google-services.json b/vector/src/gplay/google-services.json index 8ffc2cef..187c727a 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 deleted file mode 100755 index 00fff7dd..00000000 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.java +++ /dev/null @@ -1,120 +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 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 im.vector.riotredesign.R; -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) { - 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, new OnSuccessListener() { - @Override - public void onSuccess(InstanceIdResult instanceIdResult) { - storeFcmToken(activity, instanceIdResult.getToken()); - } - }) - .addOnFailureListener(activity, new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception 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 00000000..25f380f4 --- /dev/null +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt @@ -0,0 +1,102 @@ +/* + * 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 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 + * TODO Store in realm + * + * @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/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt index d306fa19..ee4e59e4 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/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt index 1387e865..1355ef63 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,18 +22,27 @@ 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 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.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 import im.vector.riotredesign.features.notifications.NotificationDrawerManager import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent +<<<<<<< HEAD +======= +import im.vector.riotredesign.features.settings.PreferencesManager +import org.koin.android.ext.android.inject +>>>>>>> develop import timber.log.Timber /** @@ -41,12 +50,18 @@ import timber.log.Timber */ class VectorFirebaseMessagingService : FirebaseMessagingService() { +<<<<<<< HEAD @Inject lateinit var notificationDrawerManager: NotificationDrawerManager private val notifiableEventResolver by lazy { NotifiableEventResolver(this) } +======= + private val notificationDrawerManager by inject() + private val pusherManager by inject() +>>>>>>> develop + private val notifiableEventResolver by inject() // UI handler private val mUIHandler by lazy { Handler(Looper.getMainLooper()) @@ -58,26 +73,27 @@ 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 } 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) } - - //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) } } /** @@ -87,11 +103,27 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * you retrieve the token. */ override fun onNewToken(refreshedToken: String?) { + if (Matrix.getInstance().currentSession == null) return 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 { + if (PreferencesManager.areNotificationEnabledForDevice(applicationContext)) { + 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()") } @@ -102,55 +134,58 @@ 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") + session.requireBackgroundSync() + } } - */ + } 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) + Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined") } } @@ -186,48 +221,44 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { isPushGatewayEvent = true ) notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent) - notificationDrawerManager.refreshNotificationDrawer(null) + notificationDrawerManager.refreshNotificationDrawer() 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, null, null /* TODO session.fulfillRule(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(null) } + + 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 @@ -256,13 +287,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { try { return Event(eventId = data["event_id"], - sender = data["sender"], + senderId = data["sender"], roomId = data["room_id"], type = data.getValue("type"), // 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/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index ac3f0725..aaae3edb 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"> - + android:theme="@style/AppTheme.Launcher" /> + + + - + @@ -52,17 +56,37 @@ android:label="@string/encryption_message_recovery" /> - + + + + + + + + + + + + + + + + - \ No newline at end of file diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 15d9252c..55ff8543 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -21,11 +21,12 @@ div { padding: 4px; } +
-

Riot Android

+

RiotX Android

Third Party Licenses

@@ -173,6 +174,62 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +
    +
  • + Tapadoo/Alerter +
    + Tapadoo/Alerter is licensed under the MIT License + Copyright 2017 Tapadoo, Dublin. +
  • +
+
+
+Copyright 2017 Tapadoo, Dublin.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+ +
    +
  • + com.github.piasy:BigImageViewer +
    + MIT License + + Copyright (c) 2018 Piasy +
  • +
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+ +
    +
  • + textdrawable +
    + textdrawable is licensed under the MIT License +
  • +

Apache License @@ -192,78 +249,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (c) 2019 The Matrix.org Foundation C.I.C
  • - Jitsi Meet (jitsi-meet) + rxkotlin
    - Copyright @ 2018-present 8x8, Inc. -
    - Copyright @ 2017-2018 Atlassian Pty Ltd + Copyright io.reactivex.
  • - Retrofit + rxandroid
    - Copyright 2013 Square, Inc. + Copyright io.reactivex.
  • - okhttp + rxrelay
    - Copyright 2016 Square, Inc. + Copyright 2014 Netflix, Inc. + Copyright 2015 Jake Wharton
  • - ShortcutBadger + rxbinding
    - Copyright 2014 Leo Lin -
  • -
  • - html-textview -
    - Copyright (C) 2013-2015 Dominik Schürmann -
    - Copyright (C) 2013-2015 Juha Kuitunen -
    - Copyright (C) 2013 Mohammed Lakkadshaw -
    - Copyright (C) 2007 The Android Open Source Project -
  • -
  • - anddown -
    - Copyright (c) 2016 CommonsWare, LLC -
  • -
  • - zip4j -
    - Copyright 2010 Srikanth Reddy Lingala -
  • -
  • - SwipeBack -
    - Copyright 2015 Eric Liu -
  • -
  • - Libphonenumber -
    - Copyright 2017 Google -
  • -
  • - Butter Knife -
    - Copyright 2013 Jake Wharton -
  • -
  • - FloatingActionButton -
    - Copyright (C) 2014 Jerzy Chalupski -
  • -
  • - Spanny -
    - Copyright 2015 Pavlovsky Ivan -
  • -
  • - PhotoView -
    - Copyright 2018 Chris Banes + Copyright (C) 2015 Jake Wharton
  • Epoxy @@ -271,9 +275,69 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright 2016 Airbnb, Inc.
  • - Anko + mvrx
    - Copyright 2016 JetBrains s.r.o. + Copyright 2018 Airbnb, Inc. +
  • +
  • + arrow-core +
    + Copyright (C) 2017 The Λrrow Authors +
  • +
  • + material +
    + Copyright (C) 2016 Google +
  • +
  • + span +
    + Copyright 2018 Jun Gu +
  • +
  • + ru.noties.markwon +
    + Copyright 2017 Dimitry Ivanov (mail@dimitryivanov.ru) +
  • +
  • + better-link-movement-method +
    + Copyright 2018 Saket Narayan. +
  • +
  • + zxcvbn +
    + Copyright (c) 2012-2016 Dan Wheeler and Dropbox, Inc. +
  • +
  • + com.otaliastudios:autocomplete +
    + Copyright (c) 2017 +
  • +
  • + Butterknife +
    + Copyright 2013 Jake Wharton +
  • +
  • + seismic +
    + Copyright 2012 Square, Inc. +
  • +
  • + videocache +
    + Copyright 2014-2017 Alexey Danilov +
  • +
  • + ShortcutBadger +
    + Copyright 2014 Leo Lin +
  • +
  • + FilePicker +
    + Copyright (c) 2018, Jaisel Rahman
  • @@ -453,25 +517,5 @@ Apache License
           of your accepting any such warranty or additional liability.
     
    - -
      -
    • - Tapadoo/Alerter -
      - Tapadoo/Alerter is licensed under the MIT License - Copyright 2017 Tapadoo, Dublin. -
    • -
    -
    -
    -Copyright 2017 Tapadoo, Dublin.
    -
    -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    -
    -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    -
    -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    -
    -
    diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index 52267e99..61754276 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -23,6 +23,10 @@ import android.os.Handler import android.os.HandlerThread 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 com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyController @@ -32,55 +36,106 @@ 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.api.MatrixConfiguration -import im.vector.riotredesign.core.di.* +import im.vector.matrix.android.api.auth.Authenticator +import im.vector.riotredesign.core.di.ActiveSessionHolder +import im.vector.riotredesign.core.di.DaggerVectorComponent +import im.vector.riotredesign.core.di.HasVectorInjector +import im.vector.riotredesign.core.di.VectorComponent +import im.vector.riotredesign.core.extensions.configureAndStart +import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver import im.vector.riotredesign.features.configuration.VectorConfiguration 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 import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler +import im.vector.riotredesign.features.settings.PreferencesManager +import im.vector.riotredesign.features.version.getVersion +import im.vector.riotredesign.push.fcm.FcmHelper import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.* import javax.inject.Inject -import kotlin.system.measureTimeMillis - class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider { + lateinit var appContext: Context //font thread handler + @Inject lateinit var authenticator: Authenticator @Inject lateinit var vectorConfiguration: VectorConfiguration @Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var notificationDrawerManager: NotificationDrawerManager + @Inject lateinit var pushRuleTriggerListener: PushRuleTriggerListener lateinit var vectorComponent: VectorComponent private var fontThreadHandler: Handler? = null + +// var slowMode = false + + override fun onCreate() { - val time = measureTimeMillis { - super.onCreate() - appContext = this - vectorComponent = DaggerVectorComponent.factory().create(this) - vectorComponent.inject(this) - vectorUncaughtExceptionHandler.activate(this) - // Log - VectorFileLogger.init(this) - Timber.plant(Timber.DebugTree(), VectorFileLogger) - if (BuildConfig.DEBUG) { - Stetho.initializeWithDefaults(this) - } - AndroidThreeTen.init(this) - BigImageViewer.initialize(GlideImageLoader.with(applicationContext)) - EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() - EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() - registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks()) - val fontRequest = FontRequest( - "com.google.android.gms.fonts", - "com.google.android.gms", - "Noto Color Emoji Compat", - R.array.com_google_android_gms_fonts_certs - ) - FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) - vectorConfiguration.initConfiguration() + super.onCreate() + appContext = this + vectorComponent = DaggerVectorComponent.factory().create(this) + vectorComponent.inject(this) + vectorUncaughtExceptionHandler.activate(this) + // Log + VectorFileLogger.init(this) + Timber.plant(Timber.DebugTree(), VectorFileLogger) + if (BuildConfig.DEBUG) { + Stetho.initializeWithDefaults(this) } - Timber.v("On create took $time ms") + logInfo() + AndroidThreeTen.init(this) + BigImageViewer.initialize(GlideImageLoader.with(applicationContext)) + EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks()) + val fontRequest = FontRequest( + "com.google.android.gms.fonts", + "com.google.android.gms", + "Noto Color Emoji Compat", + R.array.com_google_android_gms_fonts_certs + ) + FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) + vectorConfiguration.initConfiguration() + NotificationUtils.createNotificationChannels(applicationContext) + if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { + val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!! + activeSessionHolder.setActiveSession(lastAuthenticatedSession) + lastAuthenticatedSession.configureAndStart(pushRuleTriggerListener) + } + ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver { + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + fun entersForeground() { + AlarmSyncBroadcastReceiver.cancelAlarm(appContext) + activeSessionHolder.getActiveSession().also { + it.stopAnyBackgroundSync() + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + fun entersBackground() { + Timber.i("App entered background") // call persistInfo + notificationDrawerManager.persistInfo() + if (FcmHelper.isPushSupported()) { + //TODO FCM fallback + } else { + //TODO check if notifications are enabled for this device + //We need to use alarm in this mode + if (PreferencesManager.areNotificationEnabledForDevice(applicationContext)) { + AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext, 4_000L) + Timber.i("Alarm scheduled to restart service") + } + } + } + + }) } override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION) @@ -91,6 +146,20 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. return vectorComponent } + private fun logInfo() { + val appVersion = getVersion(longFormat = true, useBuildNumber = true) + val sdkVersion = Matrix.getSdkVersion() + val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date()) + + Timber.v("----------------------------------------------------------------") + Timber.v("----------------------------------------------------------------") + Timber.v(" Application version: $appVersion") + Timber.v(" SDK version: $sdkVersion") + Timber.v(" Local time: $date") + Timber.v("----------------------------------------------------------------") + Timber.v("----------------------------------------------------------------\n\n\n\n") + } + override fun attachBaseContext(base: Context) { super.attachBaseContext(base) MultiDex.install(this) diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/ActiveSessionHolder.kt b/vector/src/main/java/im/vector/riotredesign/core/di/ActiveSessionHolder.kt index 17602126..b02934d8 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/ActiveSessionHolder.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/ActiveSessionHolder.kt @@ -44,6 +44,10 @@ class ActiveSessionHolder @Inject constructor(private val authenticator: Authent return activeSession.get() != null } + fun getSafeActiveSession(): Session? { + return activeSession.get() + } + fun getActiveSession(): Session { return activeSession.get() ?: throw IllegalStateException("You should authenticate before using this") } diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt index 47b92a9f..7bceedc8 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt @@ -133,7 +133,6 @@ interface ScreenComponent { fun inject(videoMediaViewerActivity: VideoMediaViewerActivity) - @Component.Factory interface Factory { fun create(vectorComponent: VectorComponent, diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/VectorAssistedModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/VectorAssistedModule.kt index cd69d182..bbf37bfe 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/VectorAssistedModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/VectorAssistedModule.kt @@ -16,9 +16,6 @@ package im.vector.riotredesign.core.di -import com.squareup.inject.assisted.dagger2.AssistedModule -import dagger.Module - /* @Module(includes = [AssistedInject_VectorAssistedModule::class]) @AssistedModule diff --git a/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt b/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt index 90c12de4..22ec2f90 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt @@ -19,34 +19,30 @@ package im.vector.riotredesign.core.dialogs import android.app.Activity import android.text.Editable import android.text.TextUtils -import android.text.TextWatcher import android.widget.Button +import android.widget.ImageView import androidx.appcompat.app.AlertDialog import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.showPassword +import im.vector.riotredesign.core.platform.SimpleTextWatcher class ExportKeysDialog { + var passwordVisible = false + fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null) val builder = AlertDialog.Builder(activity) .setTitle(R.string.encryption_export_room_keys) .setView(dialogLayout) - val passPhrase1EditText = dialogLayout.findViewById(R.id.dialog_e2e_keys_passphrase_edit_text) - val passPhrase2EditText = dialogLayout.findViewById(R.id.dialog_e2e_keys_confirm_passphrase_edit_text) - val passPhrase2Til = dialogLayout.findViewById(R.id.dialog_e2e_keys_confirm_passphrase_til) - val exportButton = dialogLayout.findViewById