mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Merge branch 'develop' into feature/dagger [WIP]
This commit is contained in:
commit
b2d2582e0f
354 changed files with 12548 additions and 3924 deletions
|
@ -17,6 +17,7 @@ buildscript {
|
||||||
classpath "com.airbnb.okreplay:gradle-plugin:1.4.0"
|
classpath "com.airbnb.okreplay:gradle-plugin:1.4.0"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2'
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|
281
docs/notifications.md
Normal file
281
docs/notifications.md
Normal file
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import io.reactivex.Observable
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
fun liveRoomSummary(): Observable<RoomSummary> {
|
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||||
return room.roomSummary.asObservable()
|
return room.liveRoomSummary.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||||
|
|
|
@ -32,10 +32,11 @@ android {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "0.0.1"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||||
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
||||||
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
||||||
|
|
|
@ -59,7 +59,7 @@ object RoomDataHelper {
|
||||||
eventId = Random.nextLong().toString(),
|
eventId = Random.nextLong().toString(),
|
||||||
content = content,
|
content = content,
|
||||||
prevContent = prevContent,
|
prevContent = prevContent,
|
||||||
sender = sender,
|
senderId = sender,
|
||||||
stateKey = stateKey
|
stateKey = stateKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,8 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.zhuinden.monarchy.Monarchy
|
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.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.SessionManager
|
||||||
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
||||||
import im.vector.matrix.android.internal.network.UserAgentHolder
|
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||||
|
@ -94,6 +93,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSdkVersion(): String {
|
||||||
|
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,10 @@ object MatrixPatterns {
|
||||||
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
|
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)
|
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.
|
// regex pattern to find group ids in a string.
|
||||||
private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
|
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)
|
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 {
|
fun isEventId(str: String?): Boolean {
|
||||||
return str != null
|
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.api.failure
|
package im.vector.matrix.android.api.failure
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,7 +32,12 @@ import java.io.IOException
|
||||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
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()))
|
data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString()))
|
||||||
|
|
||||||
abstract class FeatureFailure : Failure()
|
abstract class FeatureFailure : Failure()
|
||||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||||
*/
|
*/
|
||||||
object PermalinkFactory {
|
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.
|
* Creates a permalink for an event.
|
||||||
|
|
|
@ -36,12 +36,20 @@ object PermalinkParser {
|
||||||
* Turns an uri to a [PermalinkData]
|
* Turns an uri to a [PermalinkData]
|
||||||
*/
|
*/
|
||||||
fun parse(uri: Uri): PermalinkData {
|
fun parse(uri: Uri): PermalinkData {
|
||||||
|
if (!uri.toString().startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) {
|
||||||
|
return PermalinkData.FallbackLink(uri)
|
||||||
|
}
|
||||||
|
|
||||||
val fragment = uri.fragment
|
val fragment = uri.fragment
|
||||||
if (fragment.isNullOrEmpty()) {
|
if (fragment.isNullOrEmpty()) {
|
||||||
return PermalinkData.FallbackLink(uri)
|
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
|
// we are limiting to 2 params
|
||||||
val params = fragment
|
val params = safeFragment
|
||||||
.split(MatrixPatterns.SEP_REGEX.toRegex())
|
.split(MatrixPatterns.SEP_REGEX.toRegex())
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.take(2)
|
.take(2)
|
||||||
|
|
|
@ -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<Action>? {
|
||||||
|
val actions = ArrayList<Action>()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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<MessageContent>()
|
||||||
|
}
|
||||||
|
// EventType.ENCRYPTED -> {
|
||||||
|
// event.root.getClearContent()?.toModel<MessageContent>()
|
||||||
|
// }
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<PushRule>
|
||||||
|
|
||||||
|
//TODO update rule
|
||||||
|
|
||||||
|
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun addPushRuleListener(listener: PushRuleListener)
|
||||||
|
|
||||||
|
fun removePushRuleListener(listener: PushRuleListener)
|
||||||
|
|
||||||
|
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
|
||||||
|
|
||||||
|
interface PushRuleListener {
|
||||||
|
fun onMatchRule(event: Event, actions: List<Action>)
|
||||||
|
fun batchFinish()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String?, Int>? {
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -13,15 +13,14 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package im.vector.matrix.android.api.pushrules
|
||||||
package im.vector.matrix.android.api.session.room.timeline
|
|
||||||
|
|
||||||
|
|
||||||
interface TimelineEventInterceptor {
|
enum class RulesetKey(val value: String) {
|
||||||
|
CONTENT("content"),
|
||||||
fun canEnrich(event: TimelineEvent): Boolean
|
OVERRIDE("override"),
|
||||||
|
ROOM("room"),
|
||||||
fun enrich(event: TimelineEvent)
|
SENDER("sender"),
|
||||||
|
UNDERRIDE("underride"),
|
||||||
|
UNKNOWN("")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,29 +13,25 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package im.vector.matrix.android.api.pushrules.rest
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.timeline
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import androidx.paging.PagedList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This data class is a holder for timeline data.
|
* All push rulesets for a user.
|
||||||
* It's returned by [TimelineService]
|
|
||||||
*/
|
*/
|
||||||
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<TimelineEvent>,
|
@Json(name = "device")
|
||||||
|
val device: Ruleset? = null
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
)
|
)
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Any>,
|
||||||
|
/**
|
||||||
|
* 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<PushCondition>? = null,
|
||||||
|
/**
|
||||||
|
* The glob-style pattern to match against. Only applicable to content rules.
|
||||||
|
*/
|
||||||
|
val pattern: String? = null
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.pushrules.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Ruleset(
|
||||||
|
val content: List<PushRule>? = null,
|
||||||
|
val override: List<PushRule>? = null,
|
||||||
|
val room: List<PushRule>? = null,
|
||||||
|
val sender: List<PushRule>? = null,
|
||||||
|
val underride: List<PushRule>? = null
|
||||||
|
)
|
|
@ -19,11 +19,13 @@ package im.vector.matrix.android.api.session
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
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.cache.CacheService
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
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.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
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.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.RoomDirectoryService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
|
@ -43,7 +45,9 @@ interface Session :
|
||||||
CryptoService,
|
CryptoService,
|
||||||
CacheService,
|
CacheService,
|
||||||
SignOutService,
|
SignOutService,
|
||||||
FilterService {
|
FilterService,
|
||||||
|
PushRuleService,
|
||||||
|
PushersService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The params associated to the session
|
* The params associated to the session
|
||||||
|
@ -56,6 +60,20 @@ interface Session :
|
||||||
@MainThread
|
@MainThread
|
||||||
fun open()
|
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.
|
* This method start the sync thread.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -36,7 +36,9 @@ interface CryptoService {
|
||||||
|
|
||||||
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun deleteDevice(deviceId: String, accountPassword: String, callback: MatrixCallback<Unit>)
|
fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
||||||
|
|
||||||
|
|
|
@ -71,12 +71,11 @@ data class Event(
|
||||||
@Json(name = "content") val content: Content? = null,
|
@Json(name = "content") val content: Content? = null,
|
||||||
@Json(name = "prev_content") val prevContent: Content? = null,
|
@Json(name = "prev_content") val prevContent: Content? = null,
|
||||||
@Json(name = "origin_server_ts") val originServerTs: Long? = 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 = "state_key") val stateKey: String? = null,
|
||||||
@Json(name = "room_id") val roomId: String? = null,
|
@Json(name = "room_id") val roomId: String? = null,
|
||||||
@Json(name = "unsigned") val unsignedData: UnsignedData? = null,
|
@Json(name = "unsigned") val unsignedData: UnsignedData? = null,
|
||||||
@Json(name = "redacts") val redacts: String? = null
|
@Json(name = "redacts") val redacts: String? = null
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
|
||||||
|
*/
|
||||||
|
fun addHttpPusher(pushkey: String,
|
||||||
|
appId: String,
|
||||||
|
profileTag: String,
|
||||||
|
lang: String,
|
||||||
|
appDisplayName: String,
|
||||||
|
deviceDisplayName: String,
|
||||||
|
url: String,
|
||||||
|
append: Boolean,
|
||||||
|
withEventIdOnly: Boolean): UUID
|
||||||
|
|
||||||
|
|
||||||
|
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EVENT_ID_ONLY = "event_id_only"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun livePushers(): LiveData<List<Pusher>>
|
||||||
|
}
|
|
@ -47,6 +47,8 @@ interface Room :
|
||||||
* A live [RoomSummary] associated with the room
|
* A live [RoomSummary] associated with the room
|
||||||
* You can observe this summary to get dynamic data from this room.
|
* You can observe this summary to get dynamic data from this room.
|
||||||
*/
|
*/
|
||||||
val roomSummary: LiveData<RoomSummary>
|
val liveRoomSummary: LiveData<RoomSummary>
|
||||||
|
|
||||||
|
val roomSummary: RoomSummary?
|
||||||
|
|
||||||
}
|
}
|
|
@ -49,6 +49,8 @@ interface MembershipService {
|
||||||
*/
|
*/
|
||||||
fun getRoomMemberIdsLive(): LiveData<List<String>>
|
fun getRoomMemberIdsLive(): LiveData<List<String>>
|
||||||
|
|
||||||
|
fun getNumberOfJoinedMembers() : Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invite a user in the room
|
* Invite a user in the room
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -39,4 +39,6 @@ data class CallInviteContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun isVideo(): Boolean = offer.sdp.contains(Offer.SDP_VIDEO)
|
||||||
}
|
}
|
|
@ -63,19 +63,6 @@ interface RelationService {
|
||||||
fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable
|
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
|
* Edit a text message body. Limited to "m.text" contentType
|
||||||
* @param targetEventId The event to edit
|
* @param targetEventId The event to edit
|
||||||
|
|
|
@ -38,4 +38,5 @@ interface ReadService {
|
||||||
*/
|
*/
|
||||||
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun isEventRead(eventId: String): Boolean
|
||||||
}
|
}
|
|
@ -32,7 +32,7 @@ package im.vector.matrix.android.api.session.room.timeline
|
||||||
*/
|
*/
|
||||||
interface 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
|
* This should be called before any other method after creating the timeline. It ensures the underlying database is open
|
||||||
|
|
|
@ -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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
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 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.
|
* Users can also enrich it with metadata.
|
||||||
*/
|
*/
|
||||||
data class TimelineEvent(
|
data class TimelineEvent(
|
||||||
|
@ -32,6 +31,7 @@ data class TimelineEvent(
|
||||||
val localId: String,
|
val localId: String,
|
||||||
val displayIndex: Int,
|
val displayIndex: Int,
|
||||||
val senderName: String?,
|
val senderName: String?,
|
||||||
|
val isUniqueDisplayName: Boolean,
|
||||||
val senderAvatar: String?,
|
val senderAvatar: String?,
|
||||||
val sendState: SendState,
|
val sendState: SendState,
|
||||||
val annotations: EventAnnotationsSummary? = null
|
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.
|
* Get the metadata associated with a key.
|
||||||
* @param key the key to get the metadata
|
* @param key the key to get the metadata
|
||||||
|
@ -64,6 +76,7 @@ data class TimelineEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isEncrypted(): Boolean {
|
fun isEncrypted(): Boolean {
|
||||||
return EventType.ENCRYPTED == root.getClearType()
|
// warning: Do not use getClearType here
|
||||||
|
return EventType.ENCRYPTED == root.type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix()
|
|
||||||
.fold(
|
|
||||||
{
|
|
||||||
f(it)
|
|
||||||
Failure(it)
|
|
||||||
},
|
|
||||||
{ Success(it) }
|
|
||||||
)
|
|
|
@ -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.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
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.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.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
||||||
|
@ -37,7 +37,6 @@ import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import retrofit2.Retrofit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
||||||
|
@ -70,7 +69,7 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
||||||
|
|
||||||
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
val job = GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
|
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
|
||||||
sessionOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
|
sessionOrFailure.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
return CancelableCoroutine(job)
|
return CancelableCoroutine(job)
|
||||||
|
|
||||||
|
|
|
@ -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<String>? = null
|
||||||
|
)
|
|
@ -16,7 +16,11 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.data
|
package im.vector.matrix.android.internal.auth.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class LoginFlowResponse(val flows: List<LoginFlow>)
|
internal data class LoginFlowResponse(
|
||||||
|
@Json(name = "flows")
|
||||||
|
val flows: List<InteractiveAuthenticationFlow>
|
||||||
|
)
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.data
|
package im.vector.matrix.android.internal.auth.data
|
||||||
|
|
||||||
internal object LoginFlowTypes {
|
object LoginFlowTypes {
|
||||||
const val PASSWORD = "m.login.password"
|
const val PASSWORD = "m.login.password"
|
||||||
const val OAUTH2 = "m.login.oauth2"
|
const val OAUTH2 = "m.login.oauth2"
|
||||||
const val EMAIL_CODE = "m.login.email.code"
|
const val EMAIL_CODE = "m.login.email.code"
|
||||||
|
|
|
@ -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<InteractiveAuthenticationFlow>? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of stages the client has completed successfully.
|
||||||
|
*/
|
||||||
|
@Json(name = "completed")
|
||||||
|
var completedStages: List<String>? = 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
|
||||||
|
)
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -19,8 +19,11 @@
|
||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
|
import com.squareup.moshi.Types
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
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.model.rest.RoomKeyRequestBody
|
||||||
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
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.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.*
|
||||||
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.verification.DefaultSasVerificationService
|
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.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
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.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.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
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 megolmEncryptionFactory: MXMegolmEncryptionFactory,
|
||||||
private val olmEncryptionFactory: MXOlmEncryptionFactory,
|
private val olmEncryptionFactory: MXOlmEncryptionFactory,
|
||||||
private val deleteDeviceTask: DeleteDeviceTask,
|
private val deleteDeviceTask: DeleteDeviceTask,
|
||||||
|
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
||||||
// Tasks
|
// Tasks
|
||||||
private val getDevicesTask: GetDevicesTask,
|
private val getDevicesTask: GetDevicesTask,
|
||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
|
@ -139,6 +141,8 @@ internal class CryptoManager @Inject constructor(
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
// MXEncrypting instance for each room.
|
// MXEncrypting instance for each room.
|
||||||
private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap()
|
private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap()
|
||||||
private val isStarting = AtomicBoolean(false)
|
private val isStarting = AtomicBoolean(false)
|
||||||
|
@ -167,9 +171,16 @@ internal class CryptoManager @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevice(deviceId: String, accountPassword: String, callback: MatrixCallback<Unit>) {
|
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||||
deleteDeviceTask
|
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<Unit>) {
|
||||||
|
deleteDeviceWithUserPasswordTask
|
||||||
|
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password))
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -592,10 +603,7 @@ internal class CryptoManager @Inject constructor(
|
||||||
val result = withContext(coroutineDispatchers.crypto) {
|
val result = withContext(coroutineDispatchers.crypto) {
|
||||||
internalDecryptEvent(event, timeline)
|
internalDecryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
result.fold(
|
result.foldToCallback(callback)
|
||||||
{ callback.onFailure(it) },
|
|
||||||
{ callback.onSuccess(it) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,7 +790,10 @@ internal class CryptoManager @Inject constructor(
|
||||||
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
||||||
* @param callback the exported keys
|
* @param callback the exported keys
|
||||||
*/
|
*/
|
||||||
fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback<ByteArray>) {
|
private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback<ByteArray>) {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
val iterationCount = Math.max(0, anIterationCount)
|
val iterationCount = Math.max(0, anIterationCount)
|
||||||
|
|
||||||
val exportedSessions = ArrayList<MegolmSessionData>()
|
val exportedSessions = ArrayList<MegolmSessionData>()
|
||||||
|
@ -797,20 +808,13 @@ internal class CryptoManager @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val encryptedRoomKeys: ByteArray
|
|
||||||
|
|
||||||
try {
|
|
||||||
val adapter = MoshiProvider.providesMoshi()
|
val adapter = MoshiProvider.providesMoshi()
|
||||||
.adapter(List::class.java)
|
.adapter(List::class.java)
|
||||||
|
|
||||||
encryptedRoomKeys = MXMegolmExportEncryption
|
MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
||||||
.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
}
|
||||||
} catch (e: Exception) {
|
}.foldToCallback(callback)
|
||||||
callback.onFailure(e)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onSuccess(encryptedRoomKeys)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -825,40 +829,33 @@ internal class CryptoManager @Inject constructor(
|
||||||
password: String,
|
password: String,
|
||||||
progressListener: ProgressListener?,
|
progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
Timber.v("## importRoomKeys starts")
|
Timber.v("## importRoomKeys starts")
|
||||||
|
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
val roomKeys: String
|
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
|
||||||
|
|
||||||
try {
|
|
||||||
roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
callback.onFailure(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val importedSessions: List<MegolmSessionData>
|
|
||||||
|
|
||||||
val t1 = System.currentTimeMillis()
|
val t1 = System.currentTimeMillis()
|
||||||
|
|
||||||
Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms")
|
Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms")
|
||||||
|
|
||||||
try {
|
val importedSessions = MoshiProvider.providesMoshi()
|
||||||
val list = MoshiProvider.providesMoshi()
|
.adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
|
||||||
.adapter(List::class.java)
|
|
||||||
.fromJson(roomKeys)
|
.fromJson(roomKeys)
|
||||||
importedSessions = list as List<MegolmSessionData>
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "## importRoomKeys failed")
|
|
||||||
callback.onFailure(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val t2 = System.currentTimeMillis()
|
val t2 = System.currentTimeMillis()
|
||||||
|
|
||||||
Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms")
|
Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms")
|
||||||
|
|
||||||
megolmSessionDataImporter.handle(importedSessions, true, progressListener, callback)
|
if (importedSessions == null) {
|
||||||
|
throw Exception("Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
megolmSessionDataImporter.handle(importedSessions, true, uiHandler, progressListener)
|
||||||
|
}
|
||||||
|
}.foldToCallback(callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1056,10 +1053,7 @@ internal class CryptoManager @Inject constructor(
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
CoroutineScope(coroutineDispatchers.crypto).launch {
|
||||||
deviceListManager
|
deviceListManager
|
||||||
.downloadKeys(userIds, forceDownload)
|
.downloadKeys(userIds, forceDownload)
|
||||||
.fold(
|
.foldToCallback(callback)
|
||||||
{ callback.onFailure(it) },
|
|
||||||
{ callback.onSuccess(it) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
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.api.RoomKeysApi
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.*
|
||||||
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.store.IMXCryptoStore
|
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.RealmCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
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.RealmCryptoStoreModule
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.hash
|
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.*
|
||||||
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.di.CryptoDatabase
|
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
||||||
|
@ -103,14 +63,16 @@ internal abstract class CryptoModule {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@CryptoDatabase
|
@CryptoDatabase
|
||||||
fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask {
|
fun providesClearCacheTask(@CryptoDatabase
|
||||||
|
realmConfiguration: RealmConfiguration): ClearCacheTask {
|
||||||
return RealmClearCacheTask(realmConfiguration)
|
return RealmClearCacheTask(realmConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
fun providesCryptoStore(@CryptoDatabase realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
|
fun providesCryptoStore(@CryptoDatabase
|
||||||
|
realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
|
||||||
return RealmCryptoStore(false /* TODO*/,
|
return RealmCryptoStore(false /* TODO*/,
|
||||||
realmConfiguration,
|
realmConfiguration,
|
||||||
credentials)
|
credentials)
|
||||||
|
@ -205,7 +167,8 @@ internal abstract class CryptoModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
|
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import android.text.TextUtils
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
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.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
|
|
@ -65,7 +65,7 @@ open class IncomingRoomKeyRequest {
|
||||||
* @param event the event
|
* @param event the event
|
||||||
*/
|
*/
|
||||||
constructor(event: Event) {
|
constructor(event: Event) {
|
||||||
userId = event.sender
|
userId = event.senderId
|
||||||
val roomKeyShareRequest = event.getClearContent().toModel<RoomKeyShareRequest>()!!
|
val roomKeyShareRequest = event.getClearContent().toModel<RoomKeyShareRequest>()!!
|
||||||
deviceId = roomKeyShareRequest.requestingDeviceId
|
deviceId = roomKeyShareRequest.requestingDeviceId
|
||||||
requestId = roomKeyShareRequest.requestId
|
requestId = roomKeyShareRequest.requestId
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
@ -43,16 +44,6 @@ object MXMegolmExportEncryption {
|
||||||
// default iteration count to export the e2e keys
|
// default iteration count to export the e2e keys
|
||||||
const val DEFAULT_ITERATION_COUNT = 500000
|
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.
|
* Extract the AES key from the deriveKeys result.
|
||||||
*
|
*
|
||||||
|
@ -108,7 +99,8 @@ object MXMegolmExportEncryption {
|
||||||
|
|
||||||
val salt = Arrays.copyOfRange(body, 1, 1 + 16)
|
val salt = Arrays.copyOfRange(body, 1, 1 + 16)
|
||||||
val iv = Arrays.copyOfRange(body, 17, 17 + 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 ciphertext = Arrays.copyOfRange(body, 37, 37 + ciphertextLength)
|
||||||
val hmac = Arrays.copyOfRange(body, body.size - 32, body.size)
|
val hmac = Arrays.copyOfRange(body, body.size - 32, body.size)
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.actions
|
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.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.MXOlmDevice
|
||||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
|
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.
|
* Import a list of megolm session keys.
|
||||||
|
* Must be call on the crypto coroutine thread
|
||||||
*
|
*
|
||||||
* @param megolmSessionsData megolm sessions.
|
* @param megolmSessionsData megolm sessions.
|
||||||
* @param backUpKeys true to back up them to the homeserver.
|
* @param backUpKeys true to back up them to the homeserver.
|
||||||
* @param progressListener the progress listener
|
* @param progressListener the progress listener
|
||||||
* @param callback
|
* @return import room keys result
|
||||||
*/
|
*/
|
||||||
|
@WorkerThread
|
||||||
fun handle(megolmSessionsData: List<MegolmSessionData>,
|
fun handle(megolmSessionsData: List<MegolmSessionData>,
|
||||||
fromBackup: Boolean,
|
fromBackup: Boolean,
|
||||||
progressListener: ProgressListener?,
|
uiHandler: Handler,
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
progressListener: ProgressListener?): ImportRoomKeysResult {
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
|
|
||||||
val totalNumbersOfKeys = megolmSessionsData.size
|
val totalNumbersOfKeys = megolmSessionsData.size
|
||||||
var cpt = 0
|
|
||||||
var lastProgress = 0
|
var lastProgress = 0
|
||||||
var totalNumbersOfImportedKeys = 0
|
var totalNumbersOfImportedKeys = 0
|
||||||
|
|
||||||
if (progressListener != null) {
|
if (progressListener != null) {
|
||||||
CryptoAsyncHelper.getUiHandler().post {
|
uiHandler.post {
|
||||||
progressListener.onProgress(0, 100)
|
progressListener.onProgress(0, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData)
|
val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData)
|
||||||
|
|
||||||
for (megolmSessionData in megolmSessionsData) {
|
megolmSessionsData.forEachIndexed { cpt, megolmSessionData ->
|
||||||
cpt++
|
|
||||||
|
|
||||||
|
|
||||||
val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm)
|
val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm)
|
||||||
|
|
||||||
if (null != decrypting) {
|
if (null != decrypting) {
|
||||||
|
@ -92,7 +90,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressListener != null) {
|
if (progressListener != null) {
|
||||||
CryptoAsyncHelper.getUiHandler().post {
|
uiHandler.post {
|
||||||
val progress = 100 * cpt / totalNumbersOfKeys
|
val progress = 100 * cpt / totalNumbersOfKeys
|
||||||
|
|
||||||
if (lastProgress != progress) {
|
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)")
|
Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
|
||||||
|
|
||||||
val finalTotalNumbersOfImportedKeys = totalNumbersOfImportedKeys
|
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys)
|
||||||
|
|
||||||
CryptoAsyncHelper.getUiHandler().post {
|
|
||||||
callback.onSuccess(ImportRoomKeysResult(totalNumbersOfKeys, finalTotalNumbersOfImportedKeys))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -131,7 +131,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
||||||
* @param event the event
|
* @param event the event
|
||||||
*/
|
*/
|
||||||
private fun requestKeysForEvent(event: Event) {
|
private fun requestKeysForEvent(event: Event) {
|
||||||
val sender = event.sender!!
|
val sender = event.senderId!!
|
||||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
|
||||||
|
|
||||||
val recipients = ArrayList<Map<String, String>>()
|
val recipients = ArrayList<Map<String, String>>()
|
||||||
|
|
|
@ -121,9 +121,9 @@ internal class MXOlmDecryption(
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
|
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
|
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,
|
throw MXDecryptionException(MXCryptoError(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
|
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.KeysBackupService
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
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.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.actions.MegolmSessionDataImporter
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature
|
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.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
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.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.SessionScope
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.TaskThread
|
import im.vector.matrix.android.internal.task.TaskThread
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
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.OlmException
|
||||||
import org.matrix.olm.OlmPkDecryption
|
import org.matrix.olm.OlmPkDecryption
|
||||||
import org.matrix.olm.OlmPkEncryption
|
import org.matrix.olm.OlmPkEncryption
|
||||||
|
@ -91,7 +99,8 @@ internal class KeysBackup @Inject constructor(
|
||||||
private val storeSessionDataTask: StoreSessionsDataTask,
|
private val storeSessionDataTask: StoreSessionsDataTask,
|
||||||
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
|
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
|
||||||
// Task executor
|
// Task executor
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||||
) : KeysBackupService {
|
) : KeysBackupService {
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
@ -134,8 +143,9 @@ internal class KeysBackup @Inject constructor(
|
||||||
override fun prepareKeysBackupVersion(password: String?,
|
override fun prepareKeysBackupVersion(password: String?,
|
||||||
progressListener: ProgressListener?,
|
progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
try {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
val olmPkDecryption = OlmPkDecryption()
|
val olmPkDecryption = OlmPkDecryption()
|
||||||
val megolmBackupAuthData = MegolmBackupAuthData()
|
val megolmBackupAuthData = MegolmBackupAuthData()
|
||||||
|
|
||||||
|
@ -177,12 +187,9 @@ internal class KeysBackup @Inject constructor(
|
||||||
megolmBackupCreationInfo.authData = megolmBackupAuthData
|
megolmBackupCreationInfo.authData = megolmBackupAuthData
|
||||||
megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
|
megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
|
||||||
|
|
||||||
uiHandler.post { callback.onSuccess(megolmBackupCreationInfo) }
|
megolmBackupCreationInfo
|
||||||
} catch (e: OlmException) {
|
|
||||||
Timber.e(e, "OlmException")
|
|
||||||
|
|
||||||
uiHandler.post { callback.onFailure(e) }
|
|
||||||
}
|
}
|
||||||
|
}.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +232,8 @@ internal class KeysBackup @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
|
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
// If we're currently backing up to this backup... stop.
|
// If we're currently backing up to this backup... stop.
|
||||||
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
|
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
|
||||||
if (keysBackupVersion != null && version == keysBackupVersion!!.version) {
|
if (keysBackupVersion != null && version == keysBackupVersion!!.version) {
|
||||||
|
@ -258,6 +266,7 @@ internal class KeysBackup @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun canRestoreKeys(): Boolean {
|
override fun canRestoreKeys(): Boolean {
|
||||||
// Server contains more keys than locally
|
// Server contains more keys than locally
|
||||||
|
@ -430,24 +439,21 @@ internal class KeysBackup @Inject constructor(
|
||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
|
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
val myUserId = credentials.userId
|
|
||||||
|
|
||||||
// Get auth data to update it
|
// Get auth data to update it
|
||||||
val authData = getMegolmBackupAuthData(keysBackupVersion)
|
val authData = getMegolmBackupAuthData(keysBackupVersion)
|
||||||
|
|
||||||
if (authData == null) {
|
if (authData == null) {
|
||||||
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
|
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(IllegalArgumentException("Missing element"))
|
callback.onFailure(IllegalArgumentException("Missing element"))
|
||||||
}
|
} else {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
return@post
|
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
|
||||||
}
|
val myUserId = credentials.userId
|
||||||
|
|
||||||
// Get current signatures, or create an empty set
|
// Get current signatures, or create an empty set
|
||||||
val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() ?: HashMap())
|
val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap()
|
||||||
|
?: HashMap())
|
||||||
|
|
||||||
if (trust) {
|
if (trust) {
|
||||||
// Add current device signature
|
// Add current device signature
|
||||||
|
@ -478,9 +484,11 @@ internal class KeysBackup @Inject constructor(
|
||||||
val moshi = MoshiProvider.providesMoshi()
|
val moshi = MoshiProvider.providesMoshi()
|
||||||
val adapter = moshi.adapter(Map::class.java)
|
val adapter = moshi.adapter(Map::class.java)
|
||||||
|
|
||||||
|
|
||||||
updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map<String, Any>?
|
updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map<String, Any>?
|
||||||
|
|
||||||
|
updateKeysBackupVersionBody
|
||||||
|
}
|
||||||
|
|
||||||
// And send it to the homeserver
|
// And send it to the homeserver
|
||||||
updateKeysBackupVersionTask
|
updateKeysBackupVersionTask
|
||||||
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
|
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
|
||||||
|
@ -497,62 +505,58 @@ internal class KeysBackup @Inject constructor(
|
||||||
|
|
||||||
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
|
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onSuccess(data)
|
callback.onSuccess(data)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(failure)
|
callback.onFailure(failure)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
||||||
recoveryKey: String,
|
recoveryKey: String,
|
||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) {
|
val isValid = withContext(coroutineDispatchers.crypto) {
|
||||||
|
isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.")
|
Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.")
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
|
callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
|
||||||
}
|
} else {
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
trustKeysBackupVersion(keysBackupVersion, true, callback)
|
trustKeysBackupVersion(keysBackupVersion, true, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
||||||
password: String,
|
password: String,
|
||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
|
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, null)
|
val recoveryKey = withContext(coroutineDispatchers.crypto) {
|
||||||
|
recoveryKeyFromPassword(password, keysBackupVersion, null)
|
||||||
|
}
|
||||||
|
|
||||||
if (recoveryKey == null) {
|
if (recoveryKey == null) {
|
||||||
Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data")
|
Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data")
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
callback.onFailure(IllegalArgumentException("Missing element"))
|
callback.onFailure(IllegalArgumentException("Missing element"))
|
||||||
}
|
} else {
|
||||||
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check trust using the recovery key
|
// Check trust using the recovery key
|
||||||
trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
|
trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get public key from a Recovery key
|
* Get public key from a Recovery key
|
||||||
|
@ -595,12 +599,10 @@ internal class KeysBackup @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBackupProgress(progressListener: ProgressListener) {
|
override fun getBackupProgress(progressListener: ProgressListener) {
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true)
|
val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true)
|
||||||
val total = cryptoStore.inboundGroupSessionsCount(false)
|
val total = cryptoStore.inboundGroupSessionsCount(false)
|
||||||
|
|
||||||
uiHandler.post { progressListener.onProgress(backedUpKeys, total) }
|
progressListener.onProgress(backedUpKeys, total)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
||||||
|
@ -611,12 +613,13 @@ internal class KeysBackup @Inject constructor(
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post(Runnable {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
Try {
|
||||||
// Check if the recovery is valid before going any further
|
// Check if the recovery is valid before going any further
|
||||||
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
|
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
|
||||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
|
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
|
||||||
uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) }
|
throw InvalidParameterException("Invalid recovery key")
|
||||||
return@Runnable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a PK decryption instance
|
// Get a PK decryption instance
|
||||||
|
@ -624,17 +627,23 @@ internal class KeysBackup @Inject constructor(
|
||||||
if (decryption == null) {
|
if (decryption == null) {
|
||||||
// This should not happen anymore
|
// This should not happen anymore
|
||||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error")
|
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error")
|
||||||
uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) }
|
throw InvalidParameterException("Invalid recovery key")
|
||||||
return@Runnable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stepProgressListener != null) {
|
decryption!!
|
||||||
uiHandler.post { stepProgressListener.onStepProgress(StepProgressListener.Step.DownloadingKey) }
|
|
||||||
}
|
}
|
||||||
|
}.fold(
|
||||||
|
{
|
||||||
|
callback.onFailure(it)
|
||||||
|
},
|
||||||
|
{ decryption ->
|
||||||
|
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
|
||||||
|
|
||||||
// Get backed up keys from the homeserver
|
// Get backed up keys from the homeserver
|
||||||
getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback<KeysBackupData> {
|
getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback<KeysBackupData> {
|
||||||
override fun onSuccess(data: KeysBackupData) {
|
override fun onSuccess(data: KeysBackupData) {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
val importRoomKeysResult = withContext(coroutineDispatchers.crypto) {
|
||||||
val sessionsData = ArrayList<MegolmSessionData>()
|
val sessionsData = ArrayList<MegolmSessionData>()
|
||||||
// Restore that data
|
// Restore that data
|
||||||
var sessionsFromHsCount = 0
|
var sessionsFromHsCount = 0
|
||||||
|
@ -672,14 +681,18 @@ internal class KeysBackup @Inject constructor(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener, object : MatrixCallback<ImportRoomKeysResult> {
|
val result = megolmSessionDataImporter.handle(sessionsData, !backUp, uiHandler, progressListener)
|
||||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
|
||||||
// Do not back up the key if it comes from a backup recovery
|
// Do not back up the key if it comes from a backup recovery
|
||||||
if (backUp) {
|
if (backUp) {
|
||||||
maybeBackupKeys()
|
maybeBackupKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onSuccess(data)
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.onSuccess(importRoomKeysResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
|
@ -687,12 +700,8 @@ internal class KeysBackup @Inject constructor(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
)
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
uiHandler.post { callback.onFailure(failure) }
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
||||||
|
@ -703,7 +712,8 @@ internal class KeysBackup @Inject constructor(
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
val progressListener = if (stepProgressListener != null) {
|
val progressListener = if (stepProgressListener != null) {
|
||||||
object : ProgressListener {
|
object : ProgressListener {
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
override fun onProgress(progress: Int, total: Int) {
|
||||||
|
@ -716,20 +726,24 @@ internal class KeysBackup @Inject constructor(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, progressListener)
|
Try {
|
||||||
|
recoveryKeyFromPassword(password, keysBackupVersion, progressListener)
|
||||||
|
}
|
||||||
|
}.fold(
|
||||||
|
{
|
||||||
|
callback.onFailure(it)
|
||||||
|
},
|
||||||
|
{ recoveryKey ->
|
||||||
if (recoveryKey == null) {
|
if (recoveryKey == null) {
|
||||||
uiHandler.post {
|
|
||||||
Timber.v("backupKeys: Invalid configuration")
|
Timber.v("backupKeys: Invalid configuration")
|
||||||
callback.onFailure(IllegalStateException("Invalid configuration"))
|
callback.onFailure(IllegalStateException("Invalid configuration"))
|
||||||
}
|
} else {
|
||||||
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback)
|
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable
|
* Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable
|
||||||
|
@ -1194,7 +1208,8 @@ internal class KeysBackup @Inject constructor(
|
||||||
|
|
||||||
keysBackupStateManager.state = KeysBackupState.BackingUp
|
keysBackupStateManager.state = KeysBackupState.BackingUp
|
||||||
|
|
||||||
CryptoAsyncHelper.getEncryptBackgroundHandler().post {
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.v("backupKeys: 2 - Encrypting keys")
|
Timber.v("backupKeys: 2 - Encrypting keys")
|
||||||
|
|
||||||
// Gather data to send to the homeserver
|
// Gather data to send to the homeserver
|
||||||
|
@ -1284,6 +1299,7 @@ internal class KeysBackup @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
@ -1298,7 +1314,8 @@ internal class KeysBackup @Inject constructor(
|
||||||
"algorithm" to sessionData!!.algorithm,
|
"algorithm" to sessionData!!.algorithm,
|
||||||
"sender_key" to sessionData.senderKey,
|
"sender_key" to sessionData.senderKey,
|
||||||
"sender_claimed_keys" to sessionData.senderClaimedKeys,
|
"sender_claimed_keys" to sessionData.senderClaimedKeys,
|
||||||
"forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain ?: ArrayList<Any>()),
|
"forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain
|
||||||
|
?: ArrayList<Any>()),
|
||||||
"session_key" to sessionData.sessionKey)
|
"session_key" to sessionData.sessionKey)
|
||||||
|
|
||||||
var encryptedSessionBackupData: OlmPkMessage? = null
|
var encryptedSessionBackupData: OlmPkMessage? = null
|
||||||
|
|
|
@ -19,10 +19,10 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides the
|
* This class provides the authentication data to delete a device
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class DeleteDeviceAuth(
|
internal data class DeleteDeviceAuth(
|
||||||
|
|
||||||
// device device session id
|
// device device session id
|
||||||
@Json(name = "session")
|
@Json(name = "session")
|
||||||
|
@ -32,6 +32,9 @@ data class DeleteDeviceAuth(
|
||||||
@Json(name = "type")
|
@Json(name = "type")
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
|
|
||||||
|
@Json(name = "user")
|
||||||
var user: String? = null,
|
var user: String? = null,
|
||||||
|
|
||||||
|
@Json(name = "password")
|
||||||
var password: String? = null
|
var password: String? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,12 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.model.rest
|
package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides the parameter to delete a device
|
* This class provides the parameter to delete a device
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class DeleteDeviceParams(
|
internal data class DeleteDeviceParams(
|
||||||
var auth: DeleteDeviceAuth? = null
|
@Json(name = "auth")
|
||||||
|
var deleteDeviceAuth: DeleteDeviceAuth? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,8 +17,13 @@
|
||||||
package im.vector.matrix.android.internal.crypto.tasks
|
package im.vector.matrix.android.internal.crypto.tasks
|
||||||
|
|
||||||
import arrow.core.Try
|
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.api.CryptoApi
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
|
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.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
@ -26,8 +31,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val deviceId: String,
|
val deviceId: String
|
||||||
val accountPassword: String
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,11 +39,30 @@ internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi
|
||||||
: DeleteDeviceTask {
|
: DeleteDeviceTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> {
|
override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> {
|
||||||
return executeRequest {
|
return executeRequest<Unit> {
|
||||||
apiCall = cryptoApi.deleteDevice(params.deviceId,
|
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
||||||
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<DeleteDeviceWithUserPasswordTask.Params, Unit> {
|
||||||
|
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<Unit> {
|
||||||
|
return executeRequest {
|
||||||
|
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()
|
||||||
|
.apply {
|
||||||
|
deleteDeviceAuth = DeleteDeviceAuth()
|
||||||
|
.apply {
|
||||||
|
type = LoginFlowTypes.PASSWORD
|
||||||
|
session = params.authSession
|
||||||
|
user = credentials.userId
|
||||||
|
password = params.password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.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.DeviceListManager
|
||||||
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
|
||||||
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
|
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.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
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.*
|
||||||
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.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
@ -157,7 +152,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
private suspend fun onStartRequestReceived(event: Event) {
|
private suspend fun onStartRequestReceived(event: Event) {
|
||||||
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!
|
val startReq = event.getClearContent().toModel<KeyVerificationStart>()!!
|
||||||
|
|
||||||
val otherUserId = event.sender
|
val otherUserId = event.senderId
|
||||||
if (!startReq.isValid()) {
|
if (!startReq.isValid()) {
|
||||||
Timber.e("## received invalid verification request")
|
Timber.e("## received invalid verification request")
|
||||||
if (startReq.transactionID != null) {
|
if (startReq.transactionID != null) {
|
||||||
|
@ -245,7 +240,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val otherUserId = event.sender!!
|
val otherUserId = event.senderId!!
|
||||||
|
|
||||||
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}")
|
||||||
val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!)
|
||||||
|
@ -267,7 +262,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## Received invalid accept request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val otherUserId = event.sender!!
|
val otherUserId = event.senderId!!
|
||||||
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## Received invalid accept request")
|
||||||
|
@ -291,7 +286,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
Timber.e("## Received invalid key request")
|
Timber.e("## Received invalid key request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val otherUserId = event.sender!!
|
val otherUserId = event.senderId!!
|
||||||
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## Received invalid accept request")
|
||||||
|
@ -312,7 +307,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
Timber.e("## Received invalid key request")
|
Timber.e("## Received invalid key request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val otherUserId = event.sender!!
|
val otherUserId = event.senderId!!
|
||||||
val existing = getExistingTransaction(otherUserId, macReq.transactionID!!)
|
val existing = getExistingTransaction(otherUserId, macReq.transactionID!!)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
Timber.e("## Received invalid accept request")
|
Timber.e("## Received invalid accept request")
|
||||||
|
@ -376,9 +371,8 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
||||||
userId,
|
userId,
|
||||||
deviceID)
|
deviceID)
|
||||||
addTransaction(tx)
|
addTransaction(tx)
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
tx.start()
|
tx.start()
|
||||||
}
|
|
||||||
return txID
|
return txID
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Unknown verification method")
|
throw IllegalArgumentException("Unknown verification method")
|
||||||
|
|
|
@ -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.SasMode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
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.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.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.KeyVerificationAccept
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
|
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.KeyVerificationMac
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
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.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
@ -125,9 +124,7 @@ internal class IncomingSASVerificationTransaction(
|
||||||
//TODO force download keys!!
|
//TODO force download keys!!
|
||||||
//would be probably better to download the keys
|
//would be probably better to download the keys
|
||||||
//for now I cancel
|
//for now I cancel
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
cancel(CancelCode.User)
|
cancel(CancelCode.User)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// val otherKey = info.identityKey()
|
// val otherKey = info.identityKey()
|
||||||
//need to jump back to correct thread
|
//need to jump back to correct thread
|
||||||
|
@ -139,11 +136,9 @@ internal class IncomingSASVerificationTransaction(
|
||||||
shortAuthenticationStrings = agreedShortCode,
|
shortAuthenticationStrings = agreedShortCode,
|
||||||
commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT)
|
commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT)
|
||||||
)
|
)
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
doAccept(accept)
|
doAccept(accept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun doAccept(accept: KeyVerificationAccept) {
|
private fun doAccept(accept: KeyVerificationAccept) {
|
||||||
|
|
|
@ -86,7 +86,6 @@ internal class OutgoingSASVerificationRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
|
|
||||||
if (state != SasVerificationTxState.None) {
|
if (state != SasVerificationTxState.None) {
|
||||||
Timber.e("## start verification from invalid state")
|
Timber.e("## start verification from invalid state")
|
||||||
//should I cancel??
|
//should I cancel??
|
||||||
|
|
|
@ -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.SasMode
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
|
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.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.actions.SetDeviceVerificationAction
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
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.model.rest.*
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
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.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import org.matrix.olm.OlmSAS
|
import org.matrix.olm.OlmSAS
|
||||||
|
@ -223,13 +223,17 @@ internal abstract class SASVerificationTransaction(
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val verifiedDevices = ArrayList<String>()
|
||||||
|
|
||||||
//cannot be empty because it has been validated
|
//cannot be empty because it has been validated
|
||||||
theirMac!!.mac!!.keys.forEach {
|
theirMac!!.mac!!.keys.forEach {
|
||||||
val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it
|
val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it
|
||||||
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
|
||||||
if (otherDeviceKey == null) {
|
if (otherDeviceKey == null) {
|
||||||
cancel(CancelCode.MismatchedKeys)
|
Timber.e("Verification: Could not find device $keyIDNoPrefix to verify")
|
||||||
return
|
//just ignore and continue
|
||||||
|
return@forEach
|
||||||
}
|
}
|
||||||
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
|
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
|
||||||
if (mac != theirMac?.mac?.get(it)) {
|
if (mac != theirMac?.mac?.get(it)) {
|
||||||
|
@ -237,12 +241,21 @@ internal abstract class SASVerificationTransaction(
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
verifiedDevices.add(keyIDNoPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
setDeviceVerified(
|
// if none of the keys could be verified, then error because the app
|
||||||
otherDeviceId ?: "",
|
// should be informed about that
|
||||||
otherUserId)
|
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
|
state = SasVerificationTxState.Verified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,22 +291,18 @@ internal abstract class SASVerificationTransaction(
|
||||||
.dispatchTo(object : MatrixCallback<Unit> {
|
.dispatchTo(object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.")
|
Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.")
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
if (onDone != null) {
|
if (onDone != null) {
|
||||||
onDone()
|
onDone()
|
||||||
} else {
|
} else {
|
||||||
state = nextState
|
state = nextState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state")
|
Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state")
|
||||||
|
|
||||||
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
|
|
||||||
cancel(onErrorReason)
|
cancel(onErrorReason)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -359,11 +368,11 @@ internal abstract class SASVerificationTransaction(
|
||||||
* or with the three numbers on separate lines.
|
* or with the three numbers on separate lines.
|
||||||
*/
|
*/
|
||||||
fun getDecimalCodeRepresentation(byteArray: ByteArray): String {
|
fun getDecimalCodeRepresentation(byteArray: ByteArray): String {
|
||||||
val b0 = byteArray[0].toInt().and(0xff) //need unsigned byte
|
val b0 = byteArray[0].toUnsignedInt() //need unsigned byte
|
||||||
val b1 = byteArray[1].toInt().and(0xff) //need unsigned byte
|
val b1 = byteArray[1].toUnsignedInt() //need unsigned byte
|
||||||
val b2 = byteArray[2].toInt().and(0xff) //need unsigned byte
|
val b2 = byteArray[2].toUnsignedInt() //need unsigned byte
|
||||||
val b3 = byteArray[3].toInt().and(0xff) //need unsigned byte
|
val b3 = byteArray[3].toUnsignedInt() //need unsigned byte
|
||||||
val b4 = byteArray[4].toInt().and(0xff) //need unsigned byte
|
val b4 = byteArray[4].toUnsignedInt() //need unsigned byte
|
||||||
//(B0 << 5 | B1 >> 3) + 1000
|
//(B0 << 5 | B1 >> 3) + 1000
|
||||||
val first = (b0.shl(5) or b1.shr(3)) + 1000
|
val first = (b0.shl(5) or b1.shr(3)) + 1000
|
||||||
//((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 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)
|
* to that number 7 emoji are selected from a list of 64 emoji (see Appendix A)
|
||||||
*/
|
*/
|
||||||
fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
|
||||||
val b0 = byteArray[0].toInt().and(0xff)
|
val b0 = byteArray[0].toUnsignedInt()
|
||||||
val b1 = byteArray[1].toInt().and(0xff)
|
val b1 = byteArray[1].toUnsignedInt()
|
||||||
val b2 = byteArray[2].toInt().and(0xff)
|
val b2 = byteArray[2].toUnsignedInt()
|
||||||
val b3 = byteArray[3].toInt().and(0xff)
|
val b3 = byteArray[3].toUnsignedInt()
|
||||||
val b4 = byteArray[4].toInt().and(0xff)
|
val b4 = byteArray[4].toUnsignedInt()
|
||||||
val b5 = byteArray[5].toInt().and(0xff)
|
val b5 = byteArray[5].toUnsignedInt()
|
||||||
return listOf(
|
return listOf(
|
||||||
getEmojiForCode((b0 and 0xFC).shr(2)),
|
getEmojiForCode((b0 and 0xFC).shr(2)),
|
||||||
getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)),
|
getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)),
|
||||||
|
|
|
@ -37,7 +37,7 @@ internal object EventMapper {
|
||||||
eventEntity.prevContent = ContentMapper.map(resolvedPrevContent)
|
eventEntity.prevContent = ContentMapper.map(resolvedPrevContent)
|
||||||
eventEntity.stateKey = event.stateKey
|
eventEntity.stateKey = event.stateKey
|
||||||
eventEntity.type = event.getClearType()
|
eventEntity.type = event.getClearType()
|
||||||
eventEntity.sender = event.sender
|
eventEntity.sender = event.senderId
|
||||||
eventEntity.originServerTs = event.originServerTs
|
eventEntity.originServerTs = event.originServerTs
|
||||||
eventEntity.redacts = event.redacts
|
eventEntity.redacts = event.redacts
|
||||||
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
|
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
|
||||||
|
@ -63,7 +63,7 @@ internal object EventMapper {
|
||||||
content = ContentMapper.map(eventEntity.content),
|
content = ContentMapper.map(eventEntity.content),
|
||||||
prevContent = ContentMapper.map(eventEntity.prevContent),
|
prevContent = ContentMapper.map(eventEntity.prevContent),
|
||||||
originServerTs = eventEntity.originServerTs,
|
originServerTs = eventEntity.originServerTs,
|
||||||
sender = eventEntity.sender,
|
senderId = eventEntity.sender,
|
||||||
stateKey = eventEntity.stateKey,
|
stateKey = eventEntity.stateKey,
|
||||||
roomId = eventEntity.roomId,
|
roomId = eventEntity.roomId,
|
||||||
unsignedData = ud,
|
unsignedData = ud,
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<List<Any>>(Types.newParameterizedType(List::class.java, Any::class.java))
|
||||||
|
|
||||||
|
// private val listOfAnyAdapter: JsonAdapter<List<Any>> =
|
||||||
|
// moshi.adapter<List<Any>>(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<Any> {
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
|
import im.vector.matrix.android.api.session.pushers.PusherData
|
||||||
|
import im.vector.matrix.android.internal.database.model.PusherDataEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.PusherEntity
|
||||||
|
import im.vector.matrix.android.internal.session.pushers.JsonPusher
|
||||||
|
|
||||||
|
internal object PushersMapper {
|
||||||
|
|
||||||
|
fun map(pushEntity: PusherEntity): Pusher {
|
||||||
|
|
||||||
|
return Pusher(
|
||||||
|
userId = pushEntity.userId,
|
||||||
|
pushKey = pushEntity.pushKey,
|
||||||
|
kind = pushEntity.kind ?: "",
|
||||||
|
appId = pushEntity.appId,
|
||||||
|
appDisplayName = pushEntity.appDisplayName,
|
||||||
|
deviceDisplayName = pushEntity.deviceDisplayName,
|
||||||
|
profileTag = pushEntity.profileTag,
|
||||||
|
lang = pushEntity.lang,
|
||||||
|
data = PusherData(pushEntity.data?.url, pushEntity.data?.format),
|
||||||
|
state = pushEntity.state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(pusher: JsonPusher, userId: String): PusherEntity {
|
||||||
|
return PusherEntity(
|
||||||
|
userId = userId,
|
||||||
|
pushKey = pusher.pushKey,
|
||||||
|
kind = pusher.kind,
|
||||||
|
appId = pusher.appId,
|
||||||
|
appDisplayName = pusher.appDisplayName,
|
||||||
|
deviceDisplayName = pusher.deviceDisplayName,
|
||||||
|
profileTag = pusher.profileTag,
|
||||||
|
lang = pusher.lang,
|
||||||
|
data = PusherDataEntity(pusher.data?.url, pusher.data?.format)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun PusherEntity.asDomain(): Pusher {
|
||||||
|
return PushersMapper.map(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun JsonPusher.toEntity(userId: String): PusherEntity {
|
||||||
|
return PushersMapper.map(this, userId)
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* copyright 2019 new vector ltd
|
||||||
|
*
|
||||||
|
* licensed under the apache license, version 2.0 (the "license");
|
||||||
|
* you may not use this file except in compliance with the license.
|
||||||
|
* you may obtain a copy of the license at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/license-2.0
|
||||||
|
*
|
||||||
|
* unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the license is distributed on an "as is" basis,
|
||||||
|
* without warranties or conditions of any kind, either express or implied.
|
||||||
|
* see the license for the specific language governing permissions and
|
||||||
|
* limitations under the license.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
internal open class PushConditionEntity(
|
||||||
|
var kind: String = "",
|
||||||
|
var key: String? = null,
|
||||||
|
var pattern: String? = null,
|
||||||
|
var iz: String? = null
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
internal open class PushRuleEntity(
|
||||||
|
//Required. The 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<PushConditionEntity>? = RealmList(),
|
||||||
|
//The glob-style pattern to match against. Only applicable to content rules.
|
||||||
|
var pattern: String? = null
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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<PushRuleEntity> = RealmList()
|
||||||
|
) : RealmObject() {
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -13,14 +13,13 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home.room.detail.timeline.helper
|
import io.realm.RealmObject
|
||||||
|
|
||||||
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 {
|
|
||||||
|
|
||||||
|
|
||||||
|
internal open class PusherDataEntity(
|
||||||
|
var url: String? = null,
|
||||||
|
var format: String? = null
|
||||||
|
) : RealmObject() {
|
||||||
|
companion object
|
||||||
}
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.pushers.PusherState
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.Index
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
// at java.lang.Thread.run(Thread.java:764)
|
||||||
|
// Caused by: java.lang.IllegalArgumentException: 'value' is not a valid managed object.
|
||||||
|
// at io.realm.ProxyState.checkValidObject(ProxyState.java:213)
|
||||||
|
// at io.realm.im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.realmSet$data(im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.java:413)
|
||||||
|
// at im.vector.matrix.android.internal.database.model.PusherEntity.setData(PusherEntity.kt:16)
|
||||||
|
// at im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70)
|
||||||
|
// at io.realm.Realm.executeTransaction(Realm.java:1493)
|
||||||
|
internal open class PusherEntity(
|
||||||
|
@Index var userId: String = "",
|
||||||
|
var pushKey: String = "",
|
||||||
|
var kind: String? = 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
|
||||||
|
}
|
|
@ -36,6 +36,11 @@ import io.realm.annotations.RealmModule
|
||||||
UserEntity::class,
|
UserEntity::class,
|
||||||
EventAnnotationsSummaryEntity::class,
|
EventAnnotationsSummaryEntity::class,
|
||||||
ReactionAggregatedSummaryEntity::class,
|
ReactionAggregatedSummaryEntity::class,
|
||||||
EditAggregatedSummaryEntity::class
|
EditAggregatedSummaryEntity::class,
|
||||||
|
PushRulesEntity::class,
|
||||||
|
PushRuleEntity::class,
|
||||||
|
PushConditionEntity::class,
|
||||||
|
PusherEntity::class,
|
||||||
|
PusherDataEntity::class
|
||||||
])
|
])
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
|
|
@ -53,6 +53,14 @@ internal fun EventEntity.Companion.where(realm: Realm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun EventEntity.Companion.types(realm: Realm,
|
||||||
|
typeList: List<String> = emptyList()): RealmQuery<EventEntity> {
|
||||||
|
val query = realm.where<EventEntity>()
|
||||||
|
query.`in`(EventEntityFields.TYPE, typeList.toTypedArray())
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun EventEntity.Companion.latestEvent(realm: Realm,
|
internal fun EventEntity.Companion.latestEvent(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
includedTypes: List<String> = emptyList(),
|
includedTypes: List<String> = emptyList(),
|
||||||
|
|
|
@ -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<PusherEntity> {
|
||||||
|
return realm.where<PusherEntity>()
|
||||||
|
.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<PushRulesEntity> {
|
||||||
|
return realm.where<PushRulesEntity>()
|
||||||
|
.equalTo(PushRulesEntityFields.USER_ID, userId)
|
||||||
|
.equalTo(PushRulesEntityFields.SCOPE, scope)
|
||||||
|
.equalTo(PushRulesEntityFields.RULESET_KEY, ruleSetKey)
|
||||||
|
}
|
|
@ -18,9 +18,10 @@ package im.vector.matrix.android.internal.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.HandlerThread
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper
|
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||||
|
@ -32,11 +33,14 @@ internal object MatrixModule {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
|
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,
|
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
|
||||||
computation = Dispatchers.IO,
|
computation = Dispatchers.IO,
|
||||||
main = Dispatchers.Main,
|
main = Dispatchers.Main,
|
||||||
crypto = cryptoHandler.asCoroutineDispatcher("crypto")
|
crypto = Handler(handlerThread.looper).asCoroutineDispatcher("crypto")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.session.sync.model.UserAccountDataFallback
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
|
|
||||||
|
|
||||||
object MoshiProvider {
|
object MoshiProvider {
|
||||||
|
|
||||||
private val moshi: Moshi = Moshi.Builder()
|
private val moshi: Moshi = Moshi.Builder()
|
||||||
|
@ -42,6 +43,7 @@ object MoshiProvider {
|
||||||
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
||||||
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
||||||
)
|
)
|
||||||
|
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun providesMoshi(): Moshi {
|
fun providesMoshi(): Moshi {
|
||||||
|
@ -63,3 +65,4 @@ object MoshiProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package im.vector.matrix.android.internal.di
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.JsonQualifier
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.Types
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@JsonQualifier
|
||||||
|
annotation class SerializeNulls {
|
||||||
|
companion object {
|
||||||
|
val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory {
|
||||||
|
@Nullable
|
||||||
|
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||||
|
val nextAnnotations = Types.nextAnnotations(annotations, SerializeNulls::class.java)
|
||||||
|
?: return null
|
||||||
|
return moshi.nextAdapter<Any>(this, type, nextAnnotations).serializeNulls()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,23 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotredesign.core.services
|
package im.vector.matrix.android.internal.extensions
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.work.Worker
|
|
||||||
import androidx.work.WorkerParameters
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class simulate push event when FCM is not working/disabled
|
* Convert a signed byte to a int value
|
||||||
*/
|
*/
|
||||||
class PushSimulatorWorker(val context: Context,
|
fun Byte.toUnsignedInt() = toInt() and 0xff
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix()
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
f(it)
|
||||||
|
Failure(it)
|
||||||
|
},
|
||||||
|
{ Success(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <A> Try<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold(
|
||||||
|
{ callback.onFailure(it) },
|
||||||
|
{ callback.onSuccess(it) })
|
|
@ -17,7 +17,6 @@
|
||||||
package im.vector.matrix.android.internal.network
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
|
@ -23,44 +23,72 @@ import arrow.effects.IO
|
||||||
import arrow.effects.fix
|
import arrow.effects.fix
|
||||||
import arrow.effects.instances.io.async.async
|
import arrow.effects.instances.io.async.async
|
||||||
import arrow.integrations.retrofit.adapter.runAsync
|
import arrow.integrations.retrofit.adapter.runAsync
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
internal inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()
|
internal suspend inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()
|
||||||
|
|
||||||
internal class Request<DATA> {
|
internal class Request<DATA> {
|
||||||
|
|
||||||
private val moshi: Moshi = MoshiProvider.providesMoshi()
|
private val moshi: Moshi = MoshiProvider.providesMoshi()
|
||||||
lateinit var apiCall: Call<DATA>
|
lateinit var apiCall: Call<DATA>
|
||||||
|
|
||||||
fun execute(): Try<DATA> {
|
suspend fun execute(): Try<DATA> {
|
||||||
return Try {
|
return suspendCancellableCoroutine { continuation ->
|
||||||
|
continuation.invokeOnCancellation {
|
||||||
|
Timber.v("Request is canceled")
|
||||||
|
apiCall.cancel()
|
||||||
|
}
|
||||||
|
val result = Try {
|
||||||
val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync()
|
val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync()
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
response.body() ?: throw IllegalStateException("The request returned a null body")
|
response.body()
|
||||||
|
?: throw IllegalStateException("The request returned a null body")
|
||||||
} else {
|
} else {
|
||||||
throw manageFailure(response.errorBody())
|
throw manageFailure(response.errorBody(), response.code())
|
||||||
}
|
}
|
||||||
}.recoverWith {
|
}.recoverWith {
|
||||||
when (it) {
|
when (it) {
|
||||||
is IOException -> Failure.NetworkConnection(it)
|
is IOException -> Failure.NetworkConnection(it)
|
||||||
is Failure.ServerError -> it
|
is Failure.ServerError,
|
||||||
|
is Failure.OtherServerError -> it
|
||||||
else -> Failure.Unknown(it)
|
else -> Failure.Unknown(it)
|
||||||
}.failure()
|
}.failure()
|
||||||
}
|
}
|
||||||
|
continuation.resume(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun manageFailure(errorBody: ResponseBody?): Throwable {
|
}
|
||||||
val matrixError = errorBody?.let {
|
|
||||||
|
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)
|
val matrixErrorAdapter = moshi.adapter(MatrixError::class.java)
|
||||||
matrixErrorAdapter.fromJson(errorBody.source())
|
|
||||||
} ?: return RuntimeException("Matrix error should not be null")
|
try {
|
||||||
return Failure.ServerError(matrixError)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.network
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.di.MatrixScope
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
|
@ -16,18 +16,21 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session
|
package im.vector.matrix.android.internal.session
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
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.Session
|
||||||
import im.vector.matrix.android.api.session.cache.CacheService
|
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.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
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.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
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.RoomDirectoryService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
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.crypto.CryptoManager
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
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.SyncThread
|
||||||
|
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams,
|
internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams,
|
||||||
|
private val context: Context,
|
||||||
private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>,
|
private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val sessionListeners: SessionListeners,
|
private val sessionListeners: SessionListeners,
|
||||||
|
@ -53,6 +58,8 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
private val filterService: FilterService,
|
private val filterService: FilterService,
|
||||||
private val cacheService: CacheService,
|
private val cacheService: CacheService,
|
||||||
private val signOutService: SignOutService,
|
private val signOutService: SignOutService,
|
||||||
|
private val pushRuleService: PushRuleService,
|
||||||
|
private val pushersService: PushersService,
|
||||||
private val cryptoService: CryptoManager,
|
private val cryptoService: CryptoManager,
|
||||||
private val syncThread: SyncThread,
|
private val syncThread: SyncThread,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
|
@ -65,10 +72,13 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
CryptoService by cryptoService,
|
CryptoService by cryptoService,
|
||||||
CacheService by cacheService,
|
CacheService by cacheService,
|
||||||
SignOutService by signOutService,
|
SignOutService by signOutService,
|
||||||
FilterService by filterService {
|
FilterService by filterService,
|
||||||
|
PushRuleService by pushRuleService,
|
||||||
|
PushersService by pushersService {
|
||||||
|
|
||||||
private var isOpen = false
|
private var isOpen = false
|
||||||
|
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
override fun open() {
|
override fun open() {
|
||||||
assertMainThread()
|
assertMainThread()
|
||||||
|
@ -80,10 +90,27 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
liveEntityObservers.forEach { it.start() }
|
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
|
@MainThread
|
||||||
override fun startSync() {
|
override fun startSync() {
|
||||||
assert(isOpen)
|
assert(isOpen)
|
||||||
|
if (!syncThread.isAlive) {
|
||||||
syncThread.start()
|
syncThread.start()
|
||||||
|
} else {
|
||||||
|
syncThread.restart()
|
||||||
|
Timber.w("Attempt to start an already started thread")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
|
@ -113,8 +140,8 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
Timber.w("SIGN_OUT: start")
|
Timber.w("SIGN_OUT: start")
|
||||||
|
|
||||||
assert(isOpen)
|
assert(isOpen)
|
||||||
Timber.w("SIGN_OUT: kill sync thread")
|
//Timber.w("SIGN_OUT: kill sync thread")
|
||||||
syncThread.kill()
|
//syncThread.kill()
|
||||||
|
|
||||||
Timber.w("SIGN_OUT: call webservice")
|
Timber.w("SIGN_OUT: call webservice")
|
||||||
return signOutService.signOut(object : MatrixCallback<Unit> {
|
return signOutService.signOut(object : MatrixCallback<Unit> {
|
||||||
|
@ -164,4 +191,5 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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.filter.FilterModule
|
||||||
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
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.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.RoomModule
|
||||||
import im.vector.matrix.android.internal.session.room.relation.SendRelationWorker
|
import im.vector.matrix.android.internal.session.room.relation.SendRelationWorker
|
||||||
import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
|
import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
|
||||||
|
@ -35,6 +37,7 @@ 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.room.send.SendEventWorker
|
||||||
import im.vector.matrix.android.internal.session.signout.SignOutModule
|
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.SyncModule
|
||||||
|
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||||
import im.vector.matrix.android.internal.session.user.UserModule
|
import im.vector.matrix.android.internal.session.user.UserModule
|
||||||
|
|
||||||
@Component(dependencies = [MatrixComponent::class],
|
@Component(dependencies = [MatrixComponent::class],
|
||||||
|
@ -49,7 +52,8 @@ import im.vector.matrix.android.internal.session.user.UserModule
|
||||||
GroupModule::class,
|
GroupModule::class,
|
||||||
ContentModule::class,
|
ContentModule::class,
|
||||||
CacheModule::class,
|
CacheModule::class,
|
||||||
CryptoModule::class
|
CryptoModule::class,
|
||||||
|
PushersModule::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
@ -69,6 +73,10 @@ internal interface SessionComponent {
|
||||||
|
|
||||||
fun inject(uploadContentWorker: UploadContentWorker)
|
fun inject(uploadContentWorker: UploadContentWorker)
|
||||||
|
|
||||||
|
fun inject(syncWorker: SyncWorker)
|
||||||
|
|
||||||
|
fun inject(addHttpPusherWorker: AddHttpPusherWorker)
|
||||||
|
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
|
@ -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.AccessTokenInterceptor
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
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.EventRelationsAggregationUpdater
|
||||||
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
|
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
|
||||||
import im.vector.matrix.android.internal.session.user.UserEntityUpdater
|
import im.vector.matrix.android.internal.session.user.UserEntityUpdater
|
||||||
|
@ -126,6 +127,10 @@ internal abstract class SessionModule {
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver
|
abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindBingRuleWatcher(bingRuleWatcher: BingRuleWatcher): LiveEntityObserver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindUserEntityUpdater(groupSummaryUpdater: UserEntityUpdater): LiveEntityObserver
|
abstract fun bindUserEntityUpdater(groupSummaryUpdater: UserEntityUpdater): LiveEntityObserver
|
||||||
|
|
|
@ -20,7 +20,6 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
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.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
internal abstract class ContentModule {
|
internal abstract class ContentModule {
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.content
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
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 im.vector.matrix.android.internal.session.SessionScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
@ -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.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,7 @@ import arrow.core.Try.Companion.raise
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.internal.di.Authenticated
|
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.network.ProgressRequestBody
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
|
@ -19,20 +19,14 @@ package im.vector.matrix.android.internal.session.content
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.toContent
|
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.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
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.internal.network.ProgressRequestBody
|
import im.vector.matrix.android.internal.network.ProgressRequestBody
|
||||||
import im.vector.matrix.android.internal.session.room.send.SendEventWorker
|
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.SessionWorkerParams
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
import im.vector.matrix.android.internal.worker.getSessionComponent
|
import im.vector.matrix.android.internal.worker.getSessionComponent
|
||||||
|
|
|
@ -16,11 +16,10 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.filter
|
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.FilterEntity
|
||||||
import im.vector.matrix.android.internal.database.model.FilterEntityFields
|
import im.vector.matrix.android.internal.database.model.FilterEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.getFilter
|
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.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package im.vector.matrix.android.internal.session.filter
|
package im.vector.matrix.android.internal.session.filter
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.sync.FilterService
|
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.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.filter
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
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 im.vector.matrix.android.internal.task.Task
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
@ -42,5 +42,6 @@ internal interface FilterApi {
|
||||||
* @return Filter
|
* @return Filter
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}")
|
||||||
fun getFilterById(@Path("userId") userId: String, @Path("filterId") filterId: String): Call<FilterBody>
|
fun getFilterById(@Path("userId") userId: String, @Path("filterId")
|
||||||
|
filterId: String): Call<FilterBody>
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,13 @@ import arrow.core.fix
|
||||||
import arrow.instances.`try`.monad.monad
|
import arrow.instances.`try`.monad.monad
|
||||||
import arrow.typeclasses.binding
|
import arrow.typeclasses.binding
|
||||||
import com.zhuinden.monarchy.Monarchy
|
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.model.GroupSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
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.GroupRooms
|
||||||
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
|
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.session.group.model.GroupUsers
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionSync
|
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -46,21 +45,17 @@ internal class DefaultGetGroupDataTask @Inject constructor(
|
||||||
|
|
||||||
override suspend fun execute(params: GetGroupDataTask.Params): Try<Unit> {
|
override suspend fun execute(params: GetGroupDataTask.Params): Try<Unit> {
|
||||||
val groupId = params.groupId
|
val groupId = params.groupId
|
||||||
return Try.monad().binding {
|
|
||||||
|
|
||||||
val groupSummary = executeRequest<GroupSummaryResponse> {
|
val groupSummary = executeRequest<GroupSummaryResponse> {
|
||||||
apiCall = groupAPI.getSummary(groupId)
|
apiCall = groupAPI.getSummary(groupId)
|
||||||
}.bind()
|
}
|
||||||
|
|
||||||
val groupRooms = executeRequest<GroupRooms> {
|
val groupRooms = executeRequest<GroupRooms> {
|
||||||
apiCall = groupAPI.getRooms(groupId)
|
apiCall = groupAPI.getRooms(groupId)
|
||||||
}.bind()
|
}
|
||||||
|
|
||||||
val groupUsers = executeRequest<GroupUsers> {
|
val groupUsers = executeRequest<GroupUsers> {
|
||||||
apiCall = groupAPI.getUsers(groupId)
|
apiCall = groupAPI.getUsers(groupId)
|
||||||
}.bind()
|
}
|
||||||
|
return Try.monad().binding {
|
||||||
insertInDb(groupSummary, groupRooms, groupUsers, groupId).bind()
|
insertInDb(groupSummary.bind(), groupRooms.bind(), groupUsers.bind(), groupId).bind()
|
||||||
}.fix()
|
}.fix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +72,8 @@ internal class DefaultGetGroupDataTask @Inject constructor(
|
||||||
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
|
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
|
||||||
val name = groupSummary.profile?.name
|
val name = groupSummary.profile?.name
|
||||||
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else 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 }
|
val roomIds = groupRooms.rooms.map { it.roomId }
|
||||||
groupSummaryEntity.roomIds.clear()
|
groupSummaryEntity.roomIds.clear()
|
||||||
|
|
|
@ -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<EventEntity>(monarchy) {
|
||||||
|
|
||||||
|
override val query = Monarchy.Query<EventEntity> {
|
||||||
|
|
||||||
|
EventEntity.types(it, listOf(
|
||||||
|
EventType.REDACTION, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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<PushRuleService.PushRuleListener>()
|
||||||
|
|
||||||
|
|
||||||
|
override fun fetchPushRules(scope: String) {
|
||||||
|
pushRulesTask
|
||||||
|
.configureWith(Unit)
|
||||||
|
.dispatchTo(object : MatrixCallback<GetPushRulesResponse> {
|
||||||
|
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<PushRule> {
|
||||||
|
|
||||||
|
var contentRules: List<PushRule> = emptyList()
|
||||||
|
var overrideRules: List<PushRule> = emptyList()
|
||||||
|
var roomRules: List<PushRule> = emptyList()
|
||||||
|
var senderRules: List<PushRule> = emptyList()
|
||||||
|
var underrideRules: List<PushRule> = 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<Unit>) {
|
||||||
|
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<Event>) {
|
||||||
|
// var hasDoneSomething = false
|
||||||
|
// events.forEach { event ->
|
||||||
|
// fulfilledBingRule(event)?.let {
|
||||||
|
// hasDoneSomething = true
|
||||||
|
// dispatchBing(event, it)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (hasDoneSomething)
|
||||||
|
// dispatchFinish()
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun dispatchBing(event: Event, rule: PushRule) {
|
||||||
|
try {
|
||||||
|
listeners.forEach {
|
||||||
|
it.onMatchRule(event, Action.mapFrom(rule) ?: emptyList())
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "Error while dispatching bing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispatchFinish() {
|
||||||
|
try {
|
||||||
|
listeners.forEach {
|
||||||
|
it.batchFinish()
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ProcessEventForPushTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val events: List<Event>,
|
||||||
|
val rules: List<PushRule>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Unit> {
|
||||||
|
return Try {
|
||||||
|
params.events.forEach { event ->
|
||||||
|
fulfilledBingRule(event, params.rules)?.let {
|
||||||
|
Timber.v("Rule $it match for event ${event.eventId}")
|
||||||
|
defaultPushRuleService.dispatchBing(event, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defaultPushRuleService.dispatchFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
|
||||||
|
val conditionResolver = DefaultConditionResolver(event, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Params>(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<Unit> {
|
||||||
|
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()
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<GetPushersResponse> {
|
||||||
|
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<AddHttpPusherWorker>()
|
||||||
|
.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<Unit>) {
|
||||||
|
val params = RemovePusherTask.Params(sessionParam.credentials.userId, pushkey, appId)
|
||||||
|
removePusherTask
|
||||||
|
.configureWith(params)
|
||||||
|
.dispatchTo(callback)
|
||||||
|
//.enableRetry() ??
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun livePushers(): LiveData<List<Pusher>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm -> PusherEntity.where(realm, sessionParam.credentials.userId) },
|
||||||
|
{ it.asDomain() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Unit, GetPushRulesResponse>
|
||||||
|
|
||||||
|
internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi) : GetPushRulesTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: Unit): Try<GetPushRulesResponse> {
|
||||||
|
return executeRequest {
|
||||||
|
apiCall = pushRulesApi.getAllRules()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue