Compare commits

...

136 Commits

Author SHA1 Message Date
ec0974f72c Merge branch 'hotfix/dimensionConverter' 2019-09-24 14:28:51 +02:00
1e963bc0dc Fix crash: MergedHeaderItem was missing dimensionConverter 2019-09-24 14:23:13 +02:00
cc832633a5 Merge branch 'release/0.6.0' 2019-09-24 10:22:42 +02:00
eadea9016b Prepare release 0.6.0 2019-09-24 10:22:36 +02:00
6422d946c9 Merge pull request #584 from vector-im/feature/hasUnread
isEventRead() returns true if the event has been sent by the user
2019-09-24 10:17:52 +02:00
5cc3dc00e3 Merge pull request #581 from vector-im/feature/talk_back
Fix a few accessibility issues
2019-09-24 10:06:28 +02:00
5a2a9f908a isEventRead() returns true if the event has been sent by the user 2019-09-24 10:04:57 +02:00
c1f2e9f171 Fix a few accessibility issues - home menu (best compromise) 2019-09-23 17:48:13 +02:00
620ba279d8 Fix a few accessibility issues 2019-09-23 16:32:54 +02:00
3fcfa33364 Merge pull request #573 from vector-im/feature/notif_invit
Clean up push rules management and fixes several issues
2019-09-23 16:23:35 +02:00
546da0f173 Merge branch 'develop' into feature/notif_invit 2019-09-23 16:23:22 +02:00
001711d5a3 Merge pull request #574 from vector-im/feature/big_emoji
Embiggen messages with multiple emojis also for edited messages
2019-09-23 16:22:53 +02:00
8e1a964679 After Ganfra's review 2019-09-23 15:08:18 +02:00
b25a130db1 Rename DimensionUtils to DimensionConverter, and inject resources instead of context. 2019-09-23 14:39:52 +02:00
8a9e6497e8 Merge pull request #578 from vector-im/feature/fix_focus_login
Fix infinite focus on HS field
2019-09-23 10:05:43 +02:00
47e3797b7e Fix infinite focus on HS field 2019-09-23 09:44:32 +02:00
5cbc90e06a Embiggen messages with multiple emojis also for edited messages (#458)
And daggerize DimensionUtils
2019-09-20 19:22:42 +02:00
e04bf31faa Fix wrong "no network" banner 2019-09-20 18:18:55 +02:00
d25cf79b07 Cleanup 2019-09-20 17:50:57 +02:00
faa8e6bbb2 m.notice messages trigger push notifications (#238) 2019-09-20 17:50:57 +02:00
d3d4deb884 Rework Action (better kotlin code) 2019-09-20 17:50:57 +02:00
f6b8e0c479 Fix issue: push rules was not retrieved after a clear cache.
We now store push rules from the sync response
2019-09-20 17:50:57 +02:00
2a726f54a2 Remove userId from PushRulesEntity and PusherEntity objects 2019-09-20 17:50:15 +02:00
1197d4021d Fix regression on PushRulesApi 2019-09-20 17:50:15 +02:00
03f8120b7d Create enum for Push rules. Also add some TODOs 2019-09-20 17:50:15 +02:00
acd7a709de Dagger: create @UserId to inject userId 2019-09-20 17:50:15 +02:00
5651ea515b Merge pull request #570 from vector-im/feature/left_group
Handle left group from sync
2019-09-20 17:44:13 +02:00
9794b3a49d Fix compilation issue of F-Droid build 2019-09-20 17:35:10 +02:00
b3e1c3969d Little changes after review 2019-09-20 17:34:50 +02:00
f24bed17a2 Add missing issue number 2019-09-19 17:56:34 +02:00
a993a30203 Handle left group from sync 2019-09-19 17:08:22 +02:00
91cc78d2ad Merge pull request #552 from vector-im/feature/draft
Save draft of a message when exiting a room with non empty composer (#329)
2019-09-19 13:11:35 +02:00
562acc9702 Save Draft only when app goes to background. 2019-09-19 13:09:08 +02:00
dfab88ed95 Display room with draft in the Catchup screen 2019-09-19 13:09:08 +02:00
36866dd24e Save draft of a message when exiting a room with non empty composer (#329) 2019-09-19 13:09:08 +02:00
c728834273 Merge pull request #566 from vector-im/feature/redact_notification
Redact notification
2019-09-19 13:02:17 +02:00
f5020d0f63 Daggerization and cleanup of NotificationUtils 2019-09-19 13:01:00 +02:00
7da9cafcc2 Remove any notification of a redacted event (#563)
Also do some cleanup and kotlinification on the code
2019-09-19 13:01:00 +02:00
6f09eea248 Merge pull request #562 from vector-im/feature/notification_edited
Message Editing: Update notifications (#128)
2019-09-19 12:59:10 +02:00
468bd5bcc9 Message Editing: Update notifications (#128) 2019-09-19 12:57:58 +02:00
3169093c50 Quick fix on the no connection banner displayed when internet is available 2019-09-19 12:55:39 +02:00
d60d766354 Merge pull request #524 from vector-im/feature/indicate_unread_rooms
Add unread indent on room list
2019-09-19 12:50:55 +02:00
0ffb5e627e Cleanup injected constructors 2019-09-19 12:43:39 +02:00
b4a13f9504 Add unread indent on room list 2019-09-19 12:43:39 +02:00
ffa8b7e73a Better fix 2019-09-18 11:24:29 +02:00
528958b3de Avoid export on env variable 2019-09-18 10:58:03 +02:00
3ffe2f7d40 Fix (again) issue with bad versionCode generated by Buildkite (#553) 2019-09-18 10:29:29 +02:00
bf42b73713 Merge pull request #555 from vector-im/feature/room_search
Cleanup on the room search screen
2019-09-17 15:28:54 +02:00
ed93f4a6c1 Cancel any request properly 2019-09-17 14:55:57 +02:00
b3d649a4d9 Fix characters erased from the Search field when the result are coming (#545) 2019-09-17 14:55:57 +02:00
3739e50d46 Better error message for timeout 2019-09-17 14:55:48 +02:00
9bf484cf1e Create a Failure to handle cancellation, and use it to ignore cancellation on room search 2019-09-17 14:55:48 +02:00
6c2faff1f0 Version++ (0.6.0) 2019-09-17 14:53:50 +02:00
07fca0922b Merge branch 'release/0.5.0' 2019-09-17 14:50:55 +02:00
282de21708 Merge branch 'release/0.5.0' into develop 2019-09-17 14:50:55 +02:00
ba9d119892 Prepare release 0.5.0 2019-09-17 14:50:43 +02:00
4453f0ced9 Merge pull request #560 from vector-im/feature/no_network
Display a "No network" banner when the device has no network
2019-09-17 14:40:42 +02:00
77168bfd6a Merge pull request #558 from vector-im/feature/login_sso
Quick implementation of SSO login - Also handling of magic link
2019-09-17 14:28:04 +02:00
25e9a179d2 SyncThread: Fix issue when network is back and the app was in background: do not restart the thread 2019-09-17 14:26:30 +02:00
73ec0f5a83 NetworkConnectivityChecker: filter onConnected callbacks (several callback if Wifi and LTE is connected)
Also do not use merlinsBeard.isConnected, which return trus even if there is no internet access (ex: with Wifi hotspot)
2019-09-17 14:22:08 +02:00
993fa74252 Cleanup after BillCarsonFr's review 2019-09-17 11:24:37 +02:00
38fc4984fe Display a no network indicator when there is no network: Create a dedicated View 2019-09-17 11:13:00 +02:00
695d8cce00 Display a no network indicator when there is no network (#559) 2019-09-17 10:59:58 +02:00
07e99901e1 SecretStoringUtils -> move to internal package 2019-09-17 10:38:37 +02:00
20f53e9a58 Signout: propose the user to retry in case of error 2019-09-17 10:33:27 +02:00
ced72aff4f Revert change done to save alias for the client 2019-09-17 10:32:09 +02:00
fdaaca49c2 Code quality (bad import) 2019-09-16 19:27:13 +02:00
3485f023b0 All current notifications were dismissed by mistake when the app is launched from the launcher 2019-09-16 19:24:52 +02:00
384dd100e9 Daggerization and Kotlinification of SecretStoringUtils 2019-09-16 19:19:14 +02:00
1ba8a58219 Cleanup SecretStoringUtils, and delete keys when user signs out 2019-09-16 18:29:06 +02:00
c8010561fc Rework on sign out task 2019-09-16 17:45:26 +02:00
1f127335bc Daggerization of RealmKeysUtils 2019-09-16 15:50:56 +02:00
138a210a73 Dagger: Screen component now exposes ActiveSessionHolder instead of Session 2019-09-16 14:43:39 +02:00
ca6bcde82d Re add the remove CurlLoggingInterceptor 2019-09-16 14:43:08 +02:00
6bda437f5d Auto configure homeserver and identity server URLs of LoginActivity with a magic link 2019-09-16 10:58:51 +02:00
3e6b65e174 Handle M_CONSENT_NOT_GIVEN error (#64) 2019-09-13 18:21:56 +02:00
137dcab734 Curl login interceptor now log the AT (on debug mode) 2019-09-13 16:20:19 +02:00
b22b8fba02 Fix the mess up with OnBackPress support on Fragment 2019-09-13 15:55:33 +02:00
3ccdf4a244 Login: some cleanup 2019-09-13 15:35:44 +02:00
5fbd271b1c Login: add SSO support 2019-09-13 15:19:45 +02:00
db8ea0f5e8 Login: check login flow - step 1 2019-09-13 11:08:54 +02:00
a47a3ead1f Login: move login code to the ViewModel 2019-09-13 10:39:22 +02:00
05b2092ffc Login: move existing code to a Fragment, MvRx style 2019-09-13 10:07:55 +02:00
6249a59203 Merge pull request #554 from vector-im/feature/build_number
Fix issue with bad versionCode generated by Buildkite (#553)
2019-09-12 17:24:46 +02:00
618e9a4f52 Fix issue with bad versionCode generated by Buildkite (#553) 2019-09-12 16:17:44 +02:00
f2c8d4ad02 Merge pull request #549 from vector-im/feature/third_party_invite
Fix rendering issue of accepted third party invitation event
2019-09-06 16:36:30 +02:00
be524472ec Merge pull request #546 from vector-im/feature/cleanup
Cleanup
2019-09-06 16:25:08 +02:00
1b82a1a24d Cleanup 2019-09-06 15:52:29 +02:00
cf0b331c3b Handle invite to the current user rendering 2019-09-06 15:48:42 +02:00
2a92a3dc80 Fix rendering issue of accepted third party invitation event 2019-09-06 14:34:52 +02:00
012840abba Progress in initial sync dialog is decreasing for a step and should not (#532) 2019-09-05 18:14:05 +02:00
a5975a099e Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:23:09 +02:00
38da4b9ee5 Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:02:03 +02:00
242e60fcaa Rename CryptoManager to DefaultCryptoService 2019-09-05 16:14:34 +02:00
a23be05cbf Better type 2019-09-05 16:04:41 +02:00
ed39b02924 Avoid using keyword for variable names 2019-09-05 16:04:41 +02:00
fe931b5361 Merge pull request #418 from Dominaezzz/kotlinify-1
Some more kotlinification
2019-09-05 16:02:30 +02:00
90d9cd0587 Merge pull request #416 from Dominaezzz/kt-remove_java_util
Remove most usages of the java.util package
2019-09-05 15:33:03 +02:00
9cedb18921 Merge pull request #538 from vector-im/feature/log_mgmt
Reduce release build log level
2019-09-05 15:24:04 +02:00
e89ba7b87b Update wording 2019-09-05 15:23:38 +02:00
902657c22a Merge pull request #537 from vector-im/feature/fix_crash
Fix crash due to missing informationData (#535)
2019-09-02 15:31:28 +02:00
eec2abf164 Reduce release build log level 2019-09-02 14:33:53 +02:00
6879cc8ca8 Fix crash due to missing informationData (#535) 2019-09-02 14:24:36 +02:00
fd6bbbd3b5 Fix issue with version name (Fixes #533) 2019-08-30 15:57:39 +02:00
0ff0b014a9 Version++ (0.5.0) 2019-08-30 15:07:04 +02:00
a89f0ddd1d Merge branch 'release/0.4.0' 2019-08-30 15:04:43 +02:00
fdc9e84dd5 Merge branch 'release/0.4.0' into develop 2019-08-30 15:04:43 +02:00
58f878fca9 Prepare version 0.4.0 2019-08-30 15:04:28 +02:00
88095e4bd9 Add entry in change file 2019-08-30 14:54:15 +02:00
47d22a3d5e Import translation from Riot and MatrixSDK 2019-08-30 11:21:43 +02:00
28e82cb8ea Merge pull request #531 from vector-im/feature/fix_crash_530
Fix / EmojiCompat not initialized
2019-08-29 17:46:51 +02:00
35817245cb refactoring, code review 2019-08-29 17:27:49 +02:00
75266f42bb Fix / EmojiCompat not initialized 2019-08-29 16:49:22 +02:00
95c4c9ce56 Merge pull request #527 from vector-im/feature/privacy
Privacy: remove log of notifiable event (#519)
2019-08-29 12:16:34 +02:00
ce5570105d Privacy: remove log of notifiable event (#519) 2019-08-29 10:36:45 +02:00
188a9aebfa Merge pull request #525 from vector-im/feature/read_receipt_cleanup
Feature/read receipt cleanup
2019-08-29 10:19:06 +02:00
c95223f5d2 Add long click support on unsupported event 2019-08-28 18:17:37 +02:00
ef0362ba9c Display Read Receipt on unsupported events 2019-08-28 17:31:31 +02:00
ea242f6737 Hide ReadReceipt View when it is not relevant 2019-08-28 17:17:37 +02:00
cbc08d834b Merge pull request #522 from vector-im/feature/fix_e2e_reply
Fix / regression on e2e reply and edit of reply
2019-08-28 10:38:22 +02:00
0ab6b33fb6 Merge branch 'develop' into feature/fix_e2e_reply 2019-08-28 10:38:12 +02:00
1b394527b6 cleaning + code review 2019-08-28 10:22:51 +02:00
a8f1388721 Merge pull request #520 from vector-im/feature/read_receipts_511
Improve read receipt design
2019-08-28 10:17:56 +02:00
166be4e289 Improve read receipt design 2019-08-28 09:56:10 +02:00
b49ccefe63 Merge pull request #521 from vector-im/feature/fix_dome_video_wont_play
Some video won't play
2019-08-28 03:43:35 -04:00
825760d17e Fix / regression on e2e reply and edit of reply 2019-08-27 17:05:04 +02:00
b5af62c3ea Some video won't play
VideoView fails to play some remote uri video on some device. For now video is downloaded locally in internal cache then played. This offers basic support before full media preview implementation
2019-08-27 16:50:02 +02:00
a51d96bf00 Merge pull request #325 from vector-im/feature/non_unicode_reaction
Accept non unicode reactions
2019-08-27 08:10:51 -04:00
7e142d201d Use EmojiCompat to build EmojiSpans from text 2019-08-27 11:06:52 +02:00
2be6058971 accept non unicode reactions 2019-08-27 10:58:21 +02:00
49d73f360e Merge pull request #494 from vector-im/feature/fix_441
Fix text diff removed linebreak
2019-08-27 04:36:03 -04:00
9cd69d1e33 Merge branch 'release/0.3.0' 2019-08-08 16:45:03 +02:00
456908c851 Merge branch 'develop' into kt-remove_java_util 2019-08-06 18:27:39 +01:00
215324a03e Some kotlinification
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-08-06 11:36:39 +01:00
02e342849f Remove most usages of the java.util package
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-21 23:23:56 +01:00
df6080b1da Merge branch 'release/0.2.0' 2019-07-18 17:47:39 +02:00
313 changed files with 7569 additions and 2065 deletions

View File

@ -1,6 +1,7 @@
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk # Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
# Last docker plugin version can be found here: # Last docker plugin version can be found here:
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases # https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
# Build debug version of the RiotX application, from the develop branch and the features branches # Build debug version of the RiotX application, from the develop branch and the features branches
@ -18,6 +19,7 @@ steps:
plugins: plugins:
- docker#v3.1.0: - docker#v3.1.0:
image: "runmymind/docker-android-sdk" image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Assemble FDroid Debug version" - label: "Assemble FDroid Debug version"
agents: agents:
@ -32,6 +34,7 @@ steps:
plugins: plugins:
- docker#v3.1.0: - docker#v3.1.0:
image: "runmymind/docker-android-sdk" image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Build Google Play unsigned APK" - label: "Build Google Play unsigned APK"
agents: agents:
@ -46,6 +49,7 @@ steps:
plugins: plugins:
- docker#v3.1.0: - docker#v3.1.0:
image: "runmymind/docker-android-sdk" image: "runmymind/docker-android-sdk"
propagate-environment: true
# Code quality # Code quality

View File

@ -1,25 +1,73 @@
Changes in RiotX 0.4.0 (2019-XX-XX) Changes in RiotX 0.6.1 (2019-09-24)
===================================================
Bugfix:
- Fix crash: MergedHeaderItem was missing dimensionConverter
Changes in RiotX 0.6.0 (2019-09-24)
===================================================
Features:
- Save draft of a message when exiting a room with non empty composer (#329)
Improvements:
- Add unread indent on room list (#485)
- Message Editing: Update notifications (#128)
- Remove any notification of a redacted event (#563)
Other changes:
- Fix a few accessibility issues
Bugfix:
- Fix characters erased from the Search field when the result are coming (#545)
- "No connection" banner was displayed by mistake
- Leaving community (from another client) has no effect on RiotX (#497)
- Push rules was not retrieved after a clear cache
- m.notice messages trigger push notifications (#238)
- Embiggen messages with multiple emojis also for edited messages (#458)
Build:
- Fix (again) issue with bad versionCode generated by Buildkite (#553)
Changes in RiotX 0.5.0 (2019-09-17)
===================================================
Features:
- Implementation of login to homeserver with SSO (#557)
- Handle M_CONSENT_NOT_GIVEN error (#64)
- Auto configure homeserver and identity server URLs of LoginActivity with a magic link
Improvements:
- Reduce default release build log level, and lab option to enable more logs.
- Display a no network indicator when there is no network (#559)
Bugfix:
- Fix crash due to missing informationData (#535)
- Progress in initial sync dialog is decreasing for a step and should not (#532)
- Fix rendering issue of accepted third party invitation event
- All current notifications were dismissed by mistake when the app is launched from the launcher
Build:
- Fix issue with version name (#533)
- Fix issue with bad versionCode generated by Buildkite (#553)
Changes in RiotX 0.4.0 (2019-08-30)
=================================================== ===================================================
Features: Features:
- Display read receipts in timeline (#81) - Display read receipts in timeline (#81)
Improvements: Improvements:
- - Reactions: Reinstate the ability to react with non-unicode keys (#307)
Other changes:
-
Bugfix: Bugfix:
- Fix text diff linebreak display (#441) - Fix text diff linebreak display (#441)
- Date change message repeats for each redaction until a normal message (#358) - Date change message repeats for each redaction until a normal message (#358)
- Slide-in reply icon is distorted (#423) - Slide-in reply icon is distorted (#423)
- Regression / e2e replies not encrypted
Translations: - Some video won't play
- - Privacy: remove log of notifiable event (#519)
- Fix crash with EmojiCompat (#530)
Build:
-
Changes in RiotX 0.3.0 (2019-08-08) Changes in RiotX 0.3.0 (2019-08-08)
=================================================== ===================================================

View File

@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.room.Room
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.ReadReceipt import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
@ -54,6 +55,10 @@ class RxRoom(private val room: Room) {
return room.getEventReadReceiptsLive(eventId).asObservable() return room.getEventReadReceiptsLive(eventId).asObservable()
} }
fun liveDrafts(): Observable<List<UserDraft>> {
return room.getDraftsLive().asObservable()
}
} }
fun Room.rx(): RxRoom { fun Room.rx(): RxRoom {

View File

@ -139,6 +139,9 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0' implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
// Bus
implementation 'org.greenrobot:eventbus:3.1.1'
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0' debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0' releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0' androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0'

View File

@ -21,7 +21,7 @@ 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.RealmCryptoStoreModule import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.util.* import kotlin.random.Random
internal class CryptoStoreHelper { internal class CryptoStoreHelper {
@ -35,7 +35,7 @@ internal class CryptoStoreHelper {
} }
fun createCredential() = Credentials( fun createCredential() = Credentials(
userId = "userId_" + Random().nextInt(), userId = "userId_" + Random.nextInt(),
homeServer = "http://matrix.org", homeServer = "http://matrix.org",
accessToken = "access_token", accessToken = "access_token",
refreshToken = null, refreshToken = null,

View File

@ -17,16 +17,23 @@
package im.vector.matrix.android.api.auth package im.vector.matrix.android.api.auth
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.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
/** /**
* This interface defines methods to authenticate to a matrix server. * This interface defines methods to authenticate to a matrix server.
*/ */
interface Authenticator { interface Authenticator {
/**
* Request the supported login flows for this homeserver
*/
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResponse>): Cancelable
/** /**
* @param homeServerConnectionConfig this param is used to configure the Homeserver * @param homeServerConnectionConfig this param is used to configure the Homeserver
* @param login the login field * @param login the login field
@ -56,4 +63,9 @@ interface Authenticator {
* @return the associated session if any, or null * @return the associated session if any, or null
*/ */
fun getSession(sessionParams: SessionParams): Session? fun getSession(sessionParams: SessionParams): Session?
/**
* Create a session after a SSO successful login
*/
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
} }

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.api.comparators package im.vector.matrix.android.api.comparators
import im.vector.matrix.android.api.interfaces.DatedObject import im.vector.matrix.android.api.interfaces.DatedObject
import java.util.*
object DatedObjectComparators { object DatedObjectComparators {

View File

@ -19,7 +19,7 @@ package im.vector.matrix.android.api.extensions
import im.vector.matrix.android.api.comparators.DatedObjectComparators import im.vector.matrix.android.api.comparators.DatedObjectComparators
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.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import java.util.* import java.util.Collections
/* ========================================================================================== /* ==========================================================================================
* MXDeviceInfo * MXDeviceInfo

View File

@ -0,0 +1,22 @@
/*
* 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.failure
// This data class will be sent to the bus
data class ConsentNotGivenError(
val consentUri: String
)

View File

@ -31,6 +31,7 @@ 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 Cancelled(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, val httpCode: Int) : 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 // When server send an error, but it cannot be interpreted as a MatrixError

View File

@ -19,77 +19,92 @@ import im.vector.matrix.android.api.pushrules.rest.PushRule
import timber.log.Timber import timber.log.Timber
class Action(val type: Type) { sealed class Action {
object Notify : Action()
enum class Type(val value: String) { object DoNotNotify : Action()
NOTIFY("notify"), data class Sound(val sound: String) : Action()
DONT_NOTIFY("dont_notify"), data class Highlight(val highlight: Boolean) : Action()
COALESCE("coalesce"), }
SET_TWEAK("set_tweak");
companion object { private const val ACTION_NOTIFY = "notify"
private const val ACTION_DONT_NOTIFY = "dont_notify"
fun safeValueOf(value: String): Type? { private const val ACTION_COALESCE = "coalesce"
try {
return valueOf(value) // Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
} catch (e: IllegalArgumentException) { private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
return null
} private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
} private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
}
} private const val ACTION_OBJECT_VALUE_KEY = "value"
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
var tweak_action: String? = null
var stringValue: String? = null /**
var boolValue: Boolean? = null * Ref: https://matrix.org/docs/spec/client_server/latest#actions
*
companion object { * Convert
fun mapFrom(pushRule: PushRule): List<Action>? { * <pre>
val actions = ArrayList<Action>() * "actions": [
pushRule.actions.forEach { actionStrOrObj -> * "notify",
if (actionStrOrObj is String) { * {
when (actionStrOrObj) { * "set_tweak": "sound",
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY) * "value": "default"
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY) * },
else -> { * {
Timber.w("Unsupported action type ${actionStrOrObj}") * "set_tweak": "highlight"
null * }
} * ]
}?.let { *
actions.add(it) * To
} * [
} else if (actionStrOrObj is Map<*, *>) { * Action.Notify,
val tweakAction = actionStrOrObj["set_tweak"] as? String * Action.Sound("default"),
when (tweakAction) { * Action.Highlight(true)
"sound" -> { * ]
(actionStrOrObj["value"] as? String)?.let { stringValue -> *
Action(Action.Type.SET_TWEAK).also { * </pre>
it.tweak_action = "sound" */
it.stringValue = stringValue fun PushRule.getActions(): List<Action> {
actions.add(it) val result = ArrayList<Action>()
}
} actions.forEach { actionStrOrObj ->
} when (actionStrOrObj) {
"highlight" -> { ACTION_NOTIFY -> Action.Notify
(actionStrOrObj["value"] as? Boolean)?.let { boolValue -> ACTION_DONT_NOTIFY -> Action.DoNotNotify
Action(Action.Type.SET_TWEAK).also { is Map<*, *> -> {
it.tweak_action = "highlight" when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) {
it.boolValue = boolValue ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
actions.add(it) (actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
} Action.Sound(stringValue)
} }
} // When the value is not there, default sound (not specified by the spec)
else -> { ?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT)
Timber.w("Unsupported action type ${actionStrOrObj}")
} }
} ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
} else { (actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
Timber.w("Unsupported action type ${actionStrOrObj}") Action.Highlight(boolValue)
return null }
} // When the value is not there, default is true, says the spec
} ?: Action.Highlight(true)
return if (actions.isEmpty()) null else actions }
} else -> {
} Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}")
null
}
}
}
else -> {
Timber.w("Unsupported action type $actionStrOrObj")
null
}
}?.let {
result.add(it)
}
}
return result
} }

View File

@ -25,14 +25,14 @@ interface PushRuleService {
/** /**
* Fetch the push rules from the server * Fetch the push rules from the server
*/ */
fun fetchPushRules(scope: String = "global") fun fetchPushRules(scope: String = RuleScope.GLOBAL)
//TODO get push rule set //TODO get push rule set
fun getPushRules(scope: String = "global"): List<PushRule> fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
//TODO update rule //TODO update rule
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
fun addPushRuleListener(listener: PushRuleListener) fun addPushRuleListener(listener: PushRuleListener)
@ -43,6 +43,7 @@ interface PushRuleService {
interface PushRuleListener { interface PushRuleListener {
fun onMatchRule(event: Event, actions: List<Action>) fun onMatchRule(event: Event, actions: List<Action>)
fun onRoomLeft(roomId: String) fun onRoomLeft(roomId: String)
fun onEventRedacted(redactedEventId: String)
fun batchFinish() fun batchFinish()
} }
} }

View File

@ -18,18 +18,17 @@ package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import timber.log.Timber import timber.log.Timber
import java.util.regex.Pattern
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$") private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) { class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean { override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveRoomMemberCountCondition(this) return conditionResolver.resolveRoomMemberCountCondition(this)
} }
override fun technicalDescription(): String { override fun technicalDescription(): String {
return "Room member count is $`is`" return "Room member count is $iz"
} }
fun isSatisfied(event: Event, session: RoomService?): Boolean { fun isSatisfied(event: Event, session: RoomService?): Boolean {
@ -56,12 +55,9 @@ class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_co
*/ */
private fun parseIsField(): Pair<String?, Int>? { private fun parseIsField(): Pair<String?, Int>? {
try { try {
val match = regex.matcher(`is`) val match = regex.find(iz) ?: return null
if (match.find()) { val (prefix, count) = match.destructured
val prefix = match.group(1) return prefix to count.toInt()
val count = match.group(2).toInt()
return prefix to count
}
} catch (t: Throwable) { } catch (t: Throwable) {
Timber.d(t) Timber.d(t)
} }

View File

@ -37,7 +37,7 @@ object RuleIds {
// Default Underride Rules // Default Underride Rules
const val RULE_ID_CALL = ".m.rule.call" 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_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_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_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted" const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"

View File

@ -15,12 +15,6 @@
*/ */
package im.vector.matrix.android.api.pushrules package im.vector.matrix.android.api.pushrules
object RuleScope {
enum class RulesetKey(val value: String) { const val GLOBAL = "global"
CONTENT("content"), }
OVERRIDE("override"),
ROOM("room"),
SENDER("sender"),
UNDERRIDE("underride"),
UNKNOWN("")
}

View File

@ -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.api.pushrules
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
*/
enum class RuleSetKey(val value: String) {
CONTENT("content"),
OVERRIDE("override"),
ROOM("room"),
SENDER("sender"),
UNDERRIDE("underride")
}
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules-scope-kind-ruleid
*/
typealias RuleKind = RuleSetKey

View File

@ -71,7 +71,7 @@ data class PushCondition(
this.key?.let { SenderNotificationPermissionCondition(it) } this.key?.let { SenderNotificationPermissionCondition(it) }
} }
Condition.Kind.UNRECOGNIZE -> { Condition.Kind.UNRECOGNIZE -> {
Timber.e("Unknwon kind $kind") Timber.e("Unknown kind $kind")
null null
} }
} }

View File

@ -20,10 +20,10 @@ import androidx.lifecycle.LiveData
interface InitialSyncProgressService { interface InitialSyncProgressService {
fun getLiveStatus() : LiveData<Status?> fun getInitialSyncProgressStatus() : LiveData<Status?>
data class Status( data class Status(
@StringRes val statusText: Int?, @StringRes val statusText: Int,
val percentProgress: Int = 0 val percentProgress: Int = 0
) )
} }

View File

@ -29,6 +29,7 @@ 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.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.securestorage.SecureStorageService
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
@ -50,7 +51,8 @@ interface Session :
FileService, FileService,
PushRuleService, PushRuleService,
PushersService, PushersService,
InitialSyncProgressService { InitialSyncProgressService,
SecureStorageService {
/** /**
* The params associated to the session * The params associated to the session
@ -87,7 +89,7 @@ interface Session :
/** /**
* This method start the sync thread. * This method start the sync thread.
*/ */
fun startSync(fromForeground : Boolean) fun startSync(fromForeground: Boolean)
/** /**
* This method stop the sync thread. * This method stop the sync thread.

View File

@ -109,8 +109,6 @@ interface CryptoService {
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
fun clearCryptoCache(callback: MatrixCallback<Unit>)
fun addNewSessionListener(newSessionListener: NewSessionListener) fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener)

View File

@ -16,12 +16,15 @@
package im.vector.matrix.android.api.session.group.model package im.vector.matrix.android.api.session.group.model
import im.vector.matrix.android.api.session.room.model.Membership
/** /**
* This class holds some data of a group. * This class holds some data of a group.
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService] * It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
*/ */
data class GroupSummary( data class GroupSummary(
val groupId: String, val groupId: String,
val membership: Membership,
val displayName: String = "", val displayName: String = "",
val shortDescription: String = "", val shortDescription: String = "",
val avatarUrl: String = "", val avatarUrl: String = "",

View File

@ -16,9 +16,6 @@
package im.vector.matrix.android.api.session.pushers package im.vector.matrix.android.api.session.pushers
data class Pusher( data class Pusher(
val userId: String,
val pushKey: String, val pushKey: String,
val kind: String, val kind: String,
val appId: String, val appId: String,

View File

@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.pushers
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import java.util.* import java.util.UUID
interface PushersService { interface PushersService {

View File

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.relation.RelationService import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.DraftService
import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.state.StateService import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
@ -32,6 +33,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineService
interface Room : interface Room :
TimelineService, TimelineService,
SendService, SendService,
DraftService,
ReadService, ReadService,
MembershipService, MembershipService,
StateService, StateService,

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.room.model package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
/** /**
@ -29,13 +30,15 @@ data class RoomSummary(
val topic: String = "", val topic: String = "",
val avatarUrl: String = "", val avatarUrl: String = "",
val isDirect: Boolean = false, val isDirect: Boolean = false,
val latestEvent: TimelineEvent? = null, val latestPreviewableEvent: TimelineEvent? = null,
val otherMemberIds: List<String> = emptyList(), val otherMemberIds: List<String> = emptyList(),
val notificationCount: Int = 0, val notificationCount: Int = 0,
val highlightCount: Int = 0, val highlightCount: Int = 0,
val hasUnreadMessages: Boolean = false,
val tags: List<RoomTag> = emptyList(), val tags: List<RoomTag> = emptyList(),
val membership: Membership = Membership.NONE, val membership: Membership = Membership.NONE,
val versioningState: VersioningState = VersioningState.NONE val versioningState: VersioningState = VersioningState.NONE,
val userDrafts: List<UserDraft> = emptyList()
) { ) {
val isVersioned: Boolean val isVersioned: Boolean

View File

@ -20,7 +20,6 @@ import android.util.Patterns
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.MatrixPatterns.isUserId import im.vector.matrix.android.api.MatrixPatterns.isUserId
import im.vector.matrix.android.api.auth.data.Credentials
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.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
@ -29,7 +28,6 @@ import im.vector.matrix.android.api.session.room.model.PowerLevels
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import java.util.*
/** /**
* Parameter to create a room, with facilities functions to configure it * Parameter to create a room, with facilities functions to configure it
@ -133,7 +131,7 @@ class CreateRoomParams {
) )
if (null == initialStates) { if (null == initialStates) {
initialStates = Arrays.asList<Event>(algoEvent) initialStates = mutableListOf(algoEvent)
} else { } else {
initialStates!!.add(algoEvent) initialStates!!.add(algoEvent)
} }
@ -166,7 +164,7 @@ class CreateRoomParams {
content = contentMap.toContent()) content = contentMap.toContent())
if (null == initialStates) { if (null == initialStates) {
initialStates = Arrays.asList<Event>(historyVisibilityEvent) initialStates = mutableListOf(historyVisibilityEvent)
} else { } else {
initialStates!!.add(historyVisibilityEvent) initialStates!!.add(historyVisibilityEvent)
} }
@ -220,7 +218,7 @@ class CreateRoomParams {
* @param ids the participant ids to add. * @param ids the participant ids to add.
*/ */
fun addParticipantIds(hsConfig: HomeServerConnectionConfig, fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
credentials: Credentials, userId: String,
ids: List<String>) { ids: List<String>) {
for (id in ids) { for (id in ids) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) { if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
@ -234,7 +232,7 @@ class CreateRoomParams {
invite3pids!!.add(pid) invite3pids!!.add(pid)
} else if (isUserId(id)) { } else if (isUserId(id)) {
// do not invite oneself // do not invite oneself
if (credentials.userId != id) { if (userId != id) {
if (null == invitedUserIds) { if (null == invitedUserIds) {
invitedUserIds = ArrayList() invitedUserIds = ArrayList()
} }

View File

@ -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.api.session.room.send
import androidx.lifecycle.LiveData
interface DraftService {
/**
* Save or update a draft to the room
*/
fun saveDraft(draft: UserDraft)
/**
* Delete the last draft, basically just after sending the message
*/
fun deleteDraft()
/**
* Return the current drafts if any, as a live data
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
*/
fun getDraftsLive(): LiveData<List<UserDraft>>
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.send
/**
* Describes a user draft:
* REGULAR: draft of a classical message
* QUOTE: draft of a message which quotes another message
* EDIT: draft of an edition of a message
* REPLY: draft of a reply of another message
*/
sealed class UserDraft(open val text: String) {
data class REGULAR(override val text: String) : UserDraft(text)
data class QUOTE(val linkedEventId: String, override val text: String) : UserDraft(text)
data class EDIT(val linkedEventId: String, override val text: String) : UserDraft(text)
data class REPLY(val linkedEventId: String, override val text: String) : UserDraft(text)
fun isValid(): Boolean {
return when (this) {
is REGULAR -> text.isNotBlank()
else -> true
}
}
}

View File

@ -18,6 +18,7 @@ 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.events.model.RelationType
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.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.model.ReadReceipt
@ -92,6 +93,13 @@ data class TimelineEvent(
*/ */
fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
/**
* Get the eventId which was edited by this event if any
*/
fun TimelineEvent.getEditedEventId(): String? {
return root.getClearContent().toModel<MessageContent>()?.relatesTo?.takeIf { it.type == RelationType.REPLACE }?.eventId
}
/** /**
* Get last MessageContent, after a possible edition * Get last MessageContent, after a possible edition
*/ */
@ -99,6 +107,20 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSu
?: root.getClearContent().toModel() ?: root.getClearContent().toModel()
/**
* Get last Message body, after a possible edition
*/
fun TimelineEvent.getLastMessageBody(): String? {
val lastMessageContent = getLastMessageContent()
if (lastMessageContent != null) {
return lastMessageContent.newContent?.toModel<MessageContent>()?.body ?: lastMessageContent.body
}
return null
}
fun TimelineEvent.getTextEditableContent(): String? { fun TimelineEvent.getTextEditableContent(): String? {
val originalContent = root.getClearContent().toModel<MessageContent>() ?: return null val originalContent = root.getClearContent().toModel<MessageContent>() ?: return null
val isReply = originalContent.isReply() || root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId != null val isReply = originalContent.isReply() || root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId != null

View File

@ -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.session.securestorage
import java.io.InputStream
import java.io.OutputStream
interface SecureStorageService {
fun securelyStoreObject(any: Any, keyAlias: String, outputStream: OutputStream)
fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T?
}

View File

@ -22,4 +22,5 @@ sealed class SyncState {
object PAUSED : SyncState() object PAUSED : SyncState()
object KILLING : SyncState() object KILLING : SyncState()
object KILLED : SyncState() object KILLED : SyncState()
object NO_NETWORK : SyncState()
} }

View File

@ -17,10 +17,12 @@
package im.vector.matrix.android.internal.auth package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers import retrofit2.http.Headers
import retrofit2.http.POST import retrofit2.http.POST
@ -29,6 +31,13 @@ import retrofit2.http.POST
*/ */
internal interface AuthAPI { internal interface AuthAPI {
/**
* Get the supported login flow
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
fun getLoginFlows(): Call<LoginFlowResponse>
/** /**
* Pass params to the server for the current login phase. * Pass params to the server for the current login phase.
* Set all the timeouts to 1 minute * Set all the timeouts to 1 minute

View File

@ -23,7 +23,7 @@ import dagger.Provides
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.internal.auth.db.AuthRealmModule import im.vector.matrix.android.internal.auth.db.AuthRealmModule
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
import im.vector.matrix.android.internal.database.configureEncryption import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.AuthDatabase import im.vector.matrix.android.internal.di.AuthDatabase
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.io.File import java.io.File
@ -33,16 +33,21 @@ internal abstract class AuthModule {
@Module @Module
companion object { companion object {
private const val DB_ALIAS = "matrix-sdk-auth"
@JvmStatic @JvmStatic
@Provides @Provides
@AuthDatabase @AuthDatabase
fun providesRealmConfiguration(context: Context): RealmConfiguration { fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
val old = File(context.filesDir, "matrix-sdk-auth") val old = File(context.filesDir, "matrix-sdk-auth")
if (old.exists()) { if (old.exists()) {
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm")) old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
} }
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.configureEncryption("matrix-sdk-auth", context) .apply {
realmKeysUtils.configureEncryption(this, DB_ALIAS)
}
.name("matrix-sdk-auth.realm") .name("matrix-sdk-auth.realm")
.modules(AuthRealmModule()) .modules(AuthRealmModule())
.deleteRealmIfMigrationNeeded() .deleteRealmIfMigrationNeeded()

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable 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.LoginFlowResponse
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.Unauthenticated import im.vector.matrix.android.internal.di.Unauthenticated
@ -62,11 +63,20 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
return sessionManager.getOrCreateSession(sessionParams) return sessionManager.getOrCreateSession(sessionParams)
} }
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResponse>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
result.foldToCallback(callback)
}
return CancelableCoroutine(job)
}
override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String, login: String,
password: String, password: String,
callback: MatrixCallback<Session>): Cancelable { callback: MatrixCallback<Session>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) { val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = runCatching { val sessionOrFailure = runCatching {
authenticate(homeServerConnectionConfig, login, password) authenticate(homeServerConnectionConfig, login, password)
@ -74,7 +84,14 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
sessionOrFailure.foldToCallback(callback) sessionOrFailure.foldToCallback(callback)
} }
return CancelableCoroutine(job) return CancelableCoroutine(job)
}
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig) = withContext(coroutineDispatchers.io) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
executeRequest<LoginFlowResponse> {
apiCall = authAPI.getLoginFlows()
}
} }
private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
@ -95,6 +112,12 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
sessionManager.getOrCreateSession(sessionParams) sessionManager.getOrCreateSession(sessionParams)
} }
override fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
return sessionManager.getOrCreateSession(sessionParams)
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString()) val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java) return retrofit.create(AuthAPI::class.java)

View File

@ -30,4 +30,12 @@ data class InteractiveAuthenticationFlow(
@Json(name = "stages") @Json(name = "stages")
val stages: List<String>? = null val stages: List<String>? = null
) ) {
companion object {
// Possible values for type
const val TYPE_LOGIN_SSO = "m.login.sso"
const val TYPE_LOGIN_TOKEN = "m.login.token"
const val TYPE_LOGIN_PASSWORD = "m.login.password"
}
}

View File

@ -20,7 +20,7 @@ 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( data class LoginFlowResponse(
@Json(name = "flows") @Json(name = "flows")
val flows: List<InteractiveAuthenticationFlow> val flows: List<InteractiveAuthenticationFlow>
) )

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import android.content.Context
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -30,12 +29,13 @@ 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.tasks.* import im.vector.matrix.android.internal.crypto.tasks.*
import im.vector.matrix.android.internal.database.configureEncryption import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.UserCacheDirectory
import im.vector.matrix.android.internal.di.UserMd5
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.cache.RealmClearCacheTask import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import retrofit2.Retrofit import retrofit2.Retrofit
import java.io.File import java.io.File
@ -45,17 +45,20 @@ internal abstract class CryptoModule {
@Module @Module
companion object { companion object {
internal const val DB_ALIAS_PREFIX = "crypto_module_"
@JvmStatic @JvmStatic
@Provides @Provides
@CryptoDatabase @CryptoDatabase
@SessionScope @SessionScope
fun providesRealmConfiguration(context: Context, credentials: Credentials): RealmConfiguration { fun providesRealmConfiguration(@UserCacheDirectory directory: File,
val userIDHash = credentials.userId.md5() @UserMd5 userMd5: String,
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.directory(File(context.filesDir, userIDHash)) .directory(directory)
.configureEncryption("crypto_module_$userIDHash", context) .apply {
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
}
.name("crypto_store.realm") .name("crypto_store.realm")
.modules(RealmCryptoStoreModule()) .modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
@ -105,7 +108,7 @@ internal abstract class CryptoModule {
} }
@Binds @Binds
abstract fun bindCryptoService(cryptoManager: CryptoManager): CryptoService abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
@Binds @Binds
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask

View File

@ -62,11 +62,9 @@ import im.vector.matrix.android.internal.crypto.tasks.*
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
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.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.room.membership.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
@ -93,7 +91,7 @@ import kotlin.math.max
* Specially, it tracks all room membership changes events in order to do keys updates. * Specially, it tracks all room membership changes events in order to do keys updates.
*/ */
@SessionScope @SessionScope
internal class CryptoManager @Inject constructor( internal class DefaultCryptoService @Inject constructor(
// Olm Manager // Olm Manager
private val olmManager: OlmManager, private val olmManager: OlmManager,
// The credentials, // The credentials,
@ -135,7 +133,6 @@ internal class CryptoManager @Inject constructor(
private val setDeviceNameTask: SetDeviceNameTask, private val setDeviceNameTask: SetDeviceNameTask,
private val uploadKeysTask: UploadKeysTask, private val uploadKeysTask: UploadKeysTask,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
@CryptoDatabase private val clearCryptoDataTask: ClearCacheTask,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor private val taskExecutor: TaskExecutor
@ -1047,14 +1044,6 @@ internal class CryptoManager @Inject constructor(
} }
} }
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
clearCryptoDataTask
.configureWith {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun addNewSessionListener(newSessionListener: NewSessionListener) { override fun addNewSessionListener(newSessionListener: NewSessionListener) {
roomDecryptorProvider.addNewSessionListener(newSessionListener) roomDecryptorProvider.addNewSessionListener(newSessionListener)
} }
@ -1067,6 +1056,6 @@ internal class CryptoManager @Inject constructor(
* ========================================================================================== */ * ========================================================================================== */
override fun toString(): String { override fun toString(): String {
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")" return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")"
} }
} }

View File

@ -17,11 +17,10 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal class ObjectSigner @Inject constructor(private val credentials: Credentials, internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
private val olmDevice: MXOlmDevice) { private val olmDevice: MXOlmDevice) {
/** /**
* Sign Object * Sign Object

View File

@ -27,14 +27,14 @@ import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val olmDevice: MXOlmDevice, internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val olmDevice: MXOlmDevice,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) { private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) {
/** /**
* Try to make sure we have established olm sessions for the given users. * Try to make sure we have established olm sessions for the given users.
* @param users a list of user ids. * @param users a list of user ids.
*/ */
suspend fun handle(users: List<String>) : MXUsersDevicesMap<MXOlmSessionResult> { suspend fun handle(users: List<String>): MXUsersDevicesMap<MXOlmSessionResult> {
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>() val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()

View File

@ -16,15 +16,15 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore, internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
private val credentials: Credentials, @UserId private val userId: String,
private val keysBackup: KeysBackup) { private val keysBackup: KeysBackup) {
fun handle(verificationStatus: Int, deviceId: String, userId: String) { fun handle(verificationStatus: Int, deviceId: String, userId: String) {
val device = cryptoStore.getUserDevice(deviceId, userId) val device = cryptoStore.getUserDevice(deviceId, userId)
@ -39,7 +39,7 @@ internal class SetDeviceVerificationAction @Inject constructor(private val crypt
device.verified = verificationStatus device.verified = verificationStatus
cryptoStore.storeUserDevice(userId, device) cryptoStore.storeUserDevice(userId, device)
if (userId == credentials.userId) { if (userId == this.userId) {
// If one of the user's own devices is being marked as verified / unverified, // If one of the user's own devices is being marked as verified / unverified,
// check the key backup status, since whether or not we use this depends on // check the key backup status, since whether or not we use this depends on
// whether it has a signature from a verified device // whether it has a signature from a verified device

View File

@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
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
@ -40,7 +39,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
internal class MXMegolmDecryption(private val credentials: Credentials, internal class MXMegolmDecryption(private val userId: String,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
@ -146,11 +145,11 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val selfMap = HashMap<String, String>() val selfMap = HashMap<String, String>()
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager) // TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
selfMap["userId"] = credentials.userId selfMap["userId"] = userId
selfMap["deviceId"] = "*" selfMap["deviceId"] = "*"
recipients.add(selfMap) recipients.add(selfMap)
if (!TextUtils.equals(sender, credentials.userId)) { if (!TextUtils.equals(sender, userId)) {
val senderMap = HashMap<String, String>() val senderMap = HashMap<String, String>()
senderMap["userId"] = sender senderMap["userId"] = sender
senderMap["deviceId"] = encryptedEventContent.deviceId!! senderMap["deviceId"] = encryptedEventContent.deviceId!!

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
@ -24,22 +23,23 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
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.di.UserId
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import javax.inject.Inject import javax.inject.Inject
internal class MXMegolmDecryptionFactory @Inject constructor(private val credentials: Credentials, internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val userId: String,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter, private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask, private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers) { private val coroutineDispatchers: MatrixCoroutineDispatchers) {
fun create(): MXMegolmDecryption { fun create(): MXMegolmDecryption {
return MXMegolmDecryption( return MXMegolmDecryption(
credentials, userId,
olmDevice, olmDevice,
deviceListManager, deviceListManager,
outgoingRoomKeyRequestManager, outgoingRoomKeyRequestManager,

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
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.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -31,13 +30,12 @@ import im.vector.matrix.android.internal.crypto.model.event.OlmPayloadContent
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.util.convertFromUTF8 import im.vector.matrix.android.internal.util.convertFromUTF8
import timber.log.Timber import timber.log.Timber
import java.util.*
internal class MXOlmDecryption( internal class MXOlmDecryption(
// The olm device interface // The olm device interface
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
// the matrix credentials // the matrix userId
private val credentials: Credentials) private val userId: String)
: IMXDecrypting { : IMXDecrypting {
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
@ -98,9 +96,9 @@ internal class MXOlmDecryption(
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
} }
if (olmPayloadContent.recipient != credentials.userId) { if (olmPayloadContent.recipient != userId) {
Timber.e("## decryptEvent() : Event ${event.eventId}:" + Timber.e("## decryptEvent() : Event ${event.eventId}:" +
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}") " Intended recipient ${olmPayloadContent.recipient} does not match our id $userId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)) String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
} }
@ -158,33 +156,14 @@ internal class MXOlmDecryption(
* @return payload, if decrypted successfully. * @return payload, if decrypted successfully.
*/ */
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? { private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
val sessionIdsSet = olmDevice.getSessionIds(theirDeviceIdentityKey) val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
val sessionIds: List<String> val messageBody = message["body"] as? String ?: return null
val messageType = when (val typeAsVoid = message["type"]) {
if (null == sessionIdsSet) { is Double -> typeAsVoid.toInt()
sessionIds = ArrayList() is Int -> typeAsVoid
} else { is Long -> typeAsVoid.toInt()
sessionIds = ArrayList(sessionIdsSet) else -> return null
}
val messageBody = message["body"] as? String
var messageType: Int? = null
val typeAsVoid = message["type"]
if (null != typeAsVoid) {
if (typeAsVoid is Double) {
messageType = typeAsVoid.toInt()
} else if (typeAsVoid is Int) {
messageType = typeAsVoid
} else if (typeAsVoid is Long) {
messageType = typeAsVoid.toInt()
}
}
if (null == messageBody || null == messageType) {
return null
} }
// Try each session in turn // Try each session in turn

View File

@ -16,16 +16,16 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.di.UserId
import javax.inject.Inject import javax.inject.Inject
internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice,
private val credentials: Credentials) { @UserId private val userId: String) {
fun create(): MXOlmDecryption { fun create(): MXOlmDecryption {
return MXOlmDecryption( return MXOlmDecryption(
olmDevice, olmDevice,
credentials) userId)
} }
} }

View File

@ -26,7 +26,6 @@ import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.security.SecureRandom import java.security.SecureRandom
import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
@ -59,8 +58,7 @@ object MXEncryptedAttachments {
// Half of the IV is random, the lower order bits are zeroed // Half of the IV is random, the lower order bits are zeroed
// such that the counter never wraps. // such that the counter never wraps.
// See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75 // See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75
val initVectorBytes = ByteArray(16) val initVectorBytes = ByteArray(16) { 0.toByte() }
Arrays.fill(initVectorBytes, 0.toByte())
val ivRandomPart = ByteArray(8) val ivRandomPart = ByteArray(8)
secureRandom.nextBytes(ivRandomPart) secureRandom.nextBytes(ivRandomPart)
@ -115,7 +113,7 @@ object MXEncryptedAttachments {
encryptedByteArray = outStream.toByteArray() encryptedByteArray = outStream.toByteArray()
) )
Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms") Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms")
return Try.just(result) return Try.just(result)
} catch (oom: OutOfMemoryError) { } catch (oom: OutOfMemoryError) {
Timber.e(oom, "## encryptAttachment failed") Timber.e(oom, "## encryptAttachment failed")
@ -206,13 +204,13 @@ object MXEncryptedAttachments {
val decryptedStream = ByteArrayInputStream(outStream.toByteArray()) val decryptedStream = ByteArrayInputStream(outStream.toByteArray())
outStream.close() outStream.close()
Timber.v("Decrypt in " + (System.currentTimeMillis() - t0) + " ms") Timber.v("Decrypt in ${System.currentTimeMillis() - t0} ms")
return decryptedStream return decryptedStream
} catch (oom: OutOfMemoryError) { } catch (oom: OutOfMemoryError) {
Timber.e(oom, "## decryptAttachment() : failed " + oom.message) Timber.e(oom, "## decryptAttachment() : failed ${oom.message}")
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## decryptAttachment() : failed " + e.message) Timber.e(e, "## decryptAttachment() : failed ${e.message}")
} }
try { try {
@ -228,34 +226,20 @@ object MXEncryptedAttachments {
* Base64 URL conversion methods * Base64 URL conversion methods
*/ */
private fun base64UrlToBase64(base64Url: String?): String? { private fun base64UrlToBase64(base64Url: String): String {
var result = base64Url return base64Url.replace('-', '+')
if (null != result) { .replace('_', '/')
result = result.replace("-".toRegex(), "+")
result = result.replace("_".toRegex(), "/")
}
return result
} }
private fun base64ToBase64Url(base64: String?): String? { private fun base64ToBase64Url(base64: String): String {
var result = base64 return base64.replace("\n".toRegex(), "")
if (null != result) { .replace("\\+".toRegex(), "-")
result = result.replace("\n".toRegex(), "") .replace('/', '_')
result = result.replace("\\+".toRegex(), "-") .replace("=", "")
result = result.replace("/".toRegex(), "_")
result = result.replace("=".toRegex(), "")
}
return result
} }
private fun base64ToUnpaddedBase64(base64: String?): String? { private fun base64ToUnpaddedBase64(base64: String): String {
var result = base64 return base64.replace("\n".toRegex(), "")
if (null != result) { .replace("=", "")
result = result.replace("\n".toRegex(), "")
result = result.replace("=".toRegex(), "")
}
return result
} }
} }

View File

@ -66,9 +66,8 @@ import org.matrix.olm.OlmPkEncryption
import org.matrix.olm.OlmPkMessage import org.matrix.olm.OlmPkMessage
import timber.log.Timber import timber.log.Timber
import java.security.InvalidParameterException import java.security.InvalidParameterException
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.HashMap import kotlin.random.Random
/** /**
* A KeysBackup class instance manage incremental backup of e2e keys (megolm keys) * A KeysBackup class instance manage incremental backup of e2e keys (megolm keys)
@ -114,8 +113,6 @@ internal class KeysBackup @Inject constructor(
// The backup key being used. // The backup key being used.
private var backupOlmPkEncryption: OlmPkEncryption? = null private var backupOlmPkEncryption: OlmPkEncryption? = null
private val random = Random()
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
private var keysBackupStateListener: KeysBackupStateListener? = null private var keysBackupStateListener: KeysBackupStateListener? = null
@ -848,7 +845,7 @@ internal class KeysBackup @Inject constructor(
// Wait between 0 and 10 seconds, to avoid backup requests from // Wait between 0 and 10 seconds, to avoid backup requests from
// different clients hitting the server all at the same time when a // different clients hitting the server all at the same time when a
// new key is sent // new key is sent
val delayInMs = random.nextInt(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS).toLong() val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
uiHandler.postDelayed({ backupKeys() }, delayInMs) uiHandler.postDelayed({ backupKeys() }, delayInMs)
} }
@ -1307,7 +1304,7 @@ internal class KeysBackup @Inject constructor(
// Make the request // Make the request
storeSessionDataTask storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)){ .configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) {
this.callback = sendingRequestCallback this.callback = sendingRequestCallback
} }
.executeBy(taskExecutor) .executeBy(taskExecutor)
@ -1405,7 +1402,7 @@ internal class KeysBackup @Inject constructor(
companion object { companion object {
// Maximum delay in ms in {@link maybeBackupKeys} // Maximum delay in ms in {@link maybeBackupKeys}
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10000 private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
// Maximum number of keys to send at a time to the homeserver. // Maximum number of keys to send at a time to the homeserver.
private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100 private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100

View File

@ -22,7 +22,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.ProgressListener
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.UUID
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.xor import kotlin.experimental.xor

View File

@ -20,7 +20,6 @@ import android.os.Handler
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 timber.log.Timber import timber.log.Timber
import java.util.*
internal class KeysBackupStateManager(private val uiHandler: Handler) { internal class KeysBackupStateManager(private val uiHandler: Handler) {

View File

@ -23,7 +23,6 @@ import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys
import java.io.Serializable import java.io.Serializable
import java.util.*
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MXDeviceInfo( data class MXDeviceInfo(

View File

@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.model
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import timber.log.Timber import timber.log.Timber
import java.util.*
data class MXKey( data class MXKey(
/** /**
@ -46,11 +45,7 @@ data class MXKey(
* @return the signed data map * @return the signed data map
*/ */
fun signalableJSONDictionary(): Map<String, Any> { fun signalableJSONDictionary(): Map<String, Any> {
val map = HashMap<String, Any>() return mapOf("key" to value)
map["key"] = value
return map
} }
/** /**

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.model package im.vector.matrix.android.internal.crypto.model
import java.util.*
class MXUsersDevicesMap<E> { class MXUsersDevicesMap<E> {
@ -27,7 +26,7 @@ class MXUsersDevicesMap<E> {
* @return the user Ids * @return the user Ids
*/ */
val userIds: List<String> val userIds: List<String>
get() = ArrayList(map.keys) get() = map.keys.toList()
val isEmpty: Boolean val isEmpty: Boolean
get() = map.isEmpty() get() = map.isEmpty()
@ -40,7 +39,7 @@ class MXUsersDevicesMap<E> {
* @return the device ids list * @return the device ids list
*/ */
fun getUserDeviceIds(userId: String?): List<String>? { fun getUserDeviceIds(userId: String?): List<String>? {
return if (userId?.isNotBlank() == true && map.containsKey(userId)) { return if (!userId.isNullOrBlank() && map.containsKey(userId)) {
map[userId]!!.keys.toList() map[userId]!!.keys.toList()
} else null } else null
} }
@ -53,7 +52,7 @@ class MXUsersDevicesMap<E> {
* @return the object * @return the object
*/ */
fun getObject(userId: String?, deviceId: String?): E? { fun getObject(userId: String?, deviceId: String?): E? {
return if (userId?.isNotBlank() == true && deviceId?.isNotBlank() == true && map.containsKey(userId)) { return if (!userId.isNullOrBlank() && !deviceId.isNullOrBlank()) {
map[userId]?.get(deviceId) map[userId]?.get(deviceId)
} else null } else null
} }
@ -67,11 +66,8 @@ class MXUsersDevicesMap<E> {
*/ */
fun setObject(userId: String?, deviceId: String?, o: E?) { fun setObject(userId: String?, deviceId: String?, o: E?) {
if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) { if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) {
if (map[userId] == null) { val devices = map.getOrPut(userId) { HashMap() }
map[userId] = HashMap() devices[deviceId] = o
}
map[userId]?.put(deviceId, o)
} }
} }
@ -82,7 +78,7 @@ class MXUsersDevicesMap<E> {
* @param userId the user id * @param userId the user id
*/ */
fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) { fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) {
if (userId?.isNotBlank() == true) { if (!userId.isNullOrBlank()) {
if (null == objectsPerDevices) { if (null == objectsPerDevices) {
map.remove(userId) map.remove(userId)
} else { } else {
@ -97,7 +93,7 @@ class MXUsersDevicesMap<E> {
* @param userId the user id. * @param userId the user id.
*/ */
fun removeUserObjects(userId: String?) { fun removeUserObjects(userId: String?) {
if (userId?.isNotBlank() == true) { if (!userId.isNullOrBlank()) {
map.remove(userId) map.remove(userId)
} }
} }

View File

@ -16,11 +16,11 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes 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.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth 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.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -34,7 +34,7 @@ internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserP
} }
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(private val cryptoApi: CryptoApi, internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(private val cryptoApi: CryptoApi,
private val credentials: Credentials) @UserId private val userId: String)
: DeleteDeviceWithUserPasswordTask { : DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) { override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
@ -45,7 +45,7 @@ internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(priva
.apply { .apply {
type = LoginFlowTypes.PASSWORD type = LoginFlowTypes.PASSWORD
session = params.authSession session = params.authSession
user = credentials.userId user = userId
password = params.password password = params.password
} }
}) })

View File

@ -21,8 +21,8 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.random.Random
internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> { internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
data class Params( data class Params(
@ -45,7 +45,7 @@ internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi
return executeRequest { return executeRequest {
apiCall = cryptoApi.sendToDevice( apiCall = cryptoApi.sendToDevice(
params.eventType, params.eventType,
params.transactionId ?: Random().nextInt(Integer.MAX_VALUE).toString(), params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
sendToDeviceBody sendToDeviceBody
) )
} }

View File

@ -44,7 +44,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.HashMap import kotlin.collections.HashMap
@ -161,7 +161,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
cancelTransaction( cancelTransaction(
startReq.transactionID!!, startReq.transactionID!!,
otherUserId!!, otherUserId!!,
startReq?.fromDevice ?: event.getSenderKey()!!, startReq.fromDevice ?: event.getSenderKey()!!,
CancelCode.UnknownMethod CancelCode.UnknownMethod
) )
} }
@ -388,14 +388,13 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid * This string must be unique for the pair of users performing verification for the duration that the transaction is valid
*/ */
private fun createUniqueIDForTransaction(userId: String, deviceID: String): String { private fun createUniqueIDForTransaction(userId: String, deviceID: String): String {
val buff = StringBuffer() return buildString {
buff append(credentials.userId).append("|")
.append(credentials.userId).append("|") append(credentials.deviceId).append("|")
.append(credentials.deviceId).append("|") append(userId).append("|")
.append(userId).append("|") append(deviceID).append("|")
.append(deviceID).append("|") append(UUID.randomUUID().toString())
.append(UUID.randomUUID().toString()) }
return buff.toString()
} }

View File

@ -17,10 +17,12 @@ package im.vector.matrix.android.internal.database
import android.content.Context import android.content.Context
import android.util.Base64 import android.util.Base64
import im.vector.matrix.android.api.util.SecretStoringUtils import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import timber.log.Timber import timber.log.Timber
import java.security.SecureRandom import java.security.SecureRandom
import javax.inject.Inject
/** /**
* On creation a random key is generated, this key is then encrypted using the system KeyStore. * On creation a random key is generated, this key is then encrypted using the system KeyStore.
@ -34,12 +36,13 @@ import java.security.SecureRandom
* then we generate a random secret key. The database key is encrypted with the secret key; The secret * then we generate a random secret key. The database key is encrypted with the secret key; The secret
* key is encrypted with the public RSA key and stored with the encrypted key in the shared pref * key is encrypted with the public RSA key and stored with the encrypted key in the shared pref
*/ */
private object RealmKeysUtils { internal class RealmKeysUtils @Inject constructor(context: Context,
private val secretStoringUtils: SecretStoringUtils) {
private const val ENCRYPTED_KEY_PREFIX = "REALM_ENCRYPTED_KEY"
private val rng = SecureRandom() private val rng = SecureRandom()
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE)
private fun generateKeyForRealm(): ByteArray { private fun generateKeyForRealm(): ByteArray {
val keyForRealm = ByteArray(RealmConfiguration.KEY_LENGTH) val keyForRealm = ByteArray(RealmConfiguration.KEY_LENGTH)
rng.nextBytes(keyForRealm) rng.nextBytes(keyForRealm)
@ -49,8 +52,7 @@ private object RealmKeysUtils {
/** /**
* Check if there is already a key for this alias * Check if there is already a key for this alias
*/ */
fun hasKeyForDatabase(alias: String, context: Context): Boolean { private fun hasKeyForDatabase(alias: String): Boolean {
val sharedPreferences = getSharedPreferences(context)
return sharedPreferences.contains("${ENCRYPTED_KEY_PREFIX}_$alias") return sharedPreferences.contains("${ENCRYPTED_KEY_PREFIX}_$alias")
} }
@ -59,13 +61,12 @@ private object RealmKeysUtils {
* The random key is then encrypted by the keystore, and the encrypted key is stored * The random key is then encrypted by the keystore, and the encrypted key is stored
* in shared preferences. * in shared preferences.
* *
* @return the generate key (can be passed to Realm Configuration) * @return the generated key (can be passed to Realm Configuration)
*/ */
fun createAndSaveKeyForDatabase(alias: String, context: Context): ByteArray { private fun createAndSaveKeyForDatabase(alias: String): ByteArray {
val key = generateKeyForRealm() val key = generateKeyForRealm()
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING) val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
val toStore = SecretStoringUtils.securelyStoreString(encodedKey, alias, context) val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
val sharedPreferences = getSharedPreferences(context)
sharedPreferences sharedPreferences
.edit() .edit()
.putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING)) .putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING))
@ -77,30 +78,43 @@ private object RealmKeysUtils {
* Retrieves the key for this database * Retrieves the key for this database
* throws if something goes wrong * throws if something goes wrong
*/ */
fun extractKeyForDatabase(alias: String, context: Context): ByteArray { private fun extractKeyForDatabase(alias: String): ByteArray {
val sharedPreferences = getSharedPreferences(context)
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null) val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING) val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING)
val b64 = SecretStoringUtils.loadSecureSecret(encryptedKey, alias, context) val b64 = secretStoringUtils.loadSecureSecret(encryptedKey, alias)
return Base64.decode(b64!!, Base64.NO_PADDING) return Base64.decode(b64!!, Base64.NO_PADDING)
} }
private fun getSharedPreferences(context: Context) = fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE) val key = if (hasKeyForDatabase(alias)) {
} Timber.i("Found key for alias:$alias")
extractKeyForDatabase(alias)
} else {
fun RealmConfiguration.Builder.configureEncryption(alias: String, context: Context): RealmConfiguration.Builder { Timber.i("Create key for DB alias:$alias")
if (RealmKeysUtils.hasKeyForDatabase(alias, context)) { createAndSaveKeyForDatabase(alias)
Timber.i("Found key for alias:$alias")
RealmKeysUtils.extractKeyForDatabase(alias, context).also {
this.encryptionKey(it)
} }
} else {
Timber.i("Create key for DB alias:$alias") if (BuildConfig.LOG_PRIVATE_DATA) {
RealmKeysUtils.createAndSaveKeyForDatabase(alias, context).also { val log = key.joinToString("") { "%02x".format(it) }
this.encryptionKey(it) Timber.w("Database key for alias `$alias`: $log")
}
realmConfigurationBuilder.encryptionKey(key)
}
// Delete elements related to the alias
fun clear(alias: String) {
if (hasKeyForDatabase(alias)) {
secretStoringUtils.safeDeleteKey(alias)
sharedPreferences
.edit()
.remove("${ENCRYPTED_KEY_PREFIX}_$alias")
.apply()
} }
} }
return this
companion object {
private const val ENCRYPTED_KEY_PREFIX = "REALM_ENCRYPTED_KEY"
}
} }

View File

@ -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.internal.database.mapper
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.internal.database.model.DraftEntity
/**
* DraftEntity <-> UserDraft
*/
internal object DraftMapper {
fun map(entity: DraftEntity): UserDraft {
return when (entity.draftMode) {
DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content)
DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content)
DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content)
DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content)
else -> null
} ?: UserDraft.REGULAR("")
}
fun map(domain: UserDraft): DraftEntity {
return when (domain) {
is UserDraft.REGULAR -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
is UserDraft.EDIT -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
is UserDraft.QUOTE -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
is UserDraft.REPLY -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
}
}
}

View File

@ -22,14 +22,15 @@ import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
internal object GroupSummaryMapper { internal object GroupSummaryMapper {
fun map(roomSummaryEntity: GroupSummaryEntity): GroupSummary { fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary {
return GroupSummary( return GroupSummary(
roomSummaryEntity.groupId, groupSummaryEntity.groupId,
roomSummaryEntity.displayName, groupSummaryEntity.membership,
roomSummaryEntity.shortDescription, groupSummaryEntity.displayName,
roomSummaryEntity.avatarUrl, groupSummaryEntity.shortDescription,
roomSummaryEntity.roomIds.toList(), groupSummaryEntity.avatarUrl,
roomSummaryEntity.userIds.toList() groupSummaryEntity.roomIds.toList(),
groupSummaryEntity.userIds.toList()
) )
} }
} }

View File

@ -26,7 +26,6 @@ internal object PushersMapper {
fun map(pushEntity: PusherEntity): Pusher { fun map(pushEntity: PusherEntity): Pusher {
return Pusher( return Pusher(
userId = pushEntity.userId,
pushKey = pushEntity.pushKey, pushKey = pushEntity.pushKey,
kind = pushEntity.kind ?: "", kind = pushEntity.kind ?: "",
appId = pushEntity.appId, appId = pushEntity.appId,
@ -39,9 +38,8 @@ internal object PushersMapper {
) )
} }
fun map(pusher: JsonPusher, userId: String): PusherEntity { fun map(pusher: JsonPusher): PusherEntity {
return PusherEntity( return PusherEntity(
userId = userId,
pushKey = pusher.pushKey, pushKey = pusher.pushKey,
kind = pusher.kind, kind = pusher.kind,
appId = pusher.appId, appId = pusher.appId,
@ -58,6 +56,6 @@ internal fun PusherEntity.asDomain(): Pusher {
return PushersMapper.map(this) return PushersMapper.map(this)
} }
internal fun JsonPusher.toEntity(userId: String): PusherEntity { internal fun JsonPusher.toEntity(): PusherEntity {
return PushersMapper.map(this, userId) return PushersMapper.map(this)
} }

View File

@ -26,8 +26,8 @@ import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor( internal class RoomSummaryMapper @Inject constructor(
val cryptoService: CryptoService, private val cryptoService: CryptoService,
val timelineEventMapper: TimelineEventMapper private val timelineEventMapper: TimelineEventMapper
) { ) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
@ -35,7 +35,7 @@ internal class RoomSummaryMapper @Inject constructor(
RoomTag(it.tagName, it.tagOrder) RoomTag(it.tagName, it.tagOrder)
} }
val latestEvent = roomSummaryEntity.latestEvent?.let { val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it) timelineEventMapper.map(it)
} }
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) { if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
@ -43,29 +43,32 @@ internal class RoomSummaryMapper @Inject constructor(
//for now decrypt sync //for now decrypt sync
try { try {
val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString()) val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString())
latestEvent.root.mxDecryptionResult = OlmDecryptionResult( latestEvent.root.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent, payload = result.clearEvent,
senderKey = result.senderCurve25519Key, senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
) )
} catch (e: MXCryptoError) { } catch (e: MXCryptoError) {
} }
} }
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,
displayName = roomSummaryEntity.displayName ?: "", displayName = roomSummaryEntity.displayName ?: "",
topic = roomSummaryEntity.topic ?: "", topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect, isDirect = roomSummaryEntity.isDirect,
latestEvent = latestEvent, latestPreviewableEvent = latestEvent,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount, highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount, notificationCount = roomSummaryEntity.notificationCount,
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
tags = tags, tags = tags,
membership = roomSummaryEntity.membership, membership = roomSummaryEntity.membership,
versioningState = roomSummaryEntity.versioningState versioningState = roomSummaryEntity.versioningState,
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList()
) )
} }
} }

View File

@ -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.database.model
import io.realm.RealmObject
internal open class DraftEntity(var content: String = "",
var draftMode: String = MODE_REGULAR,
var linkedEventId: String = ""
) : RealmObject() {
companion object {
const val MODE_REGULAR = "REGULAR"
const val MODE_EDIT = "EDIT"
const val MODE_REPLY = "REPLY"
const val MODE_QUOTE = "QUOTE"
}
}

View File

@ -20,9 +20,13 @@ import im.vector.matrix.android.api.session.room.model.Membership
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
internal open class GroupEntity(@PrimaryKey var groupId: String = "" /**
* This class is used to store group info (groupId and membership) from the sync response.
) : RealmObject() { * Then [im.vector.matrix.android.internal.session.group.GroupSummaryUpdater] observes change and
* makes requests to fetch group information from the homeserver
*/
internal open class GroupEntity(@PrimaryKey var groupId: String = "")
: RealmObject() {
private var membershipStr: String = Membership.NONE.name private var membershipStr: String = Membership.NONE.name
var membership: Membership var membership: Membership

View File

@ -16,18 +16,28 @@
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.session.room.model.Membership
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "", internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "",
var displayName: String = "", var displayName: String = "",
var shortDescription: String = "", var shortDescription: String = "",
var avatarUrl: String = "", var avatarUrl: String = "",
var roomIds: RealmList<String> = RealmList(), var roomIds: RealmList<String> = RealmList(),
var userIds: RealmList<String> = RealmList() var userIds: RealmList<String> = RealmList()
) : RealmObject() { ) : RealmObject() {
private var membershipStr: String = Membership.NONE.name
var membership: Membership
get() {
return Membership.valueOf(membershipStr)
}
set(value) {
membershipStr = value.name
}
companion object companion object
} }

View File

@ -15,17 +15,24 @@
*/ */
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.pushrules.RuleKind
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Index
internal open class PushRulesEntity( internal open class PushRulesEntity(
@Index var userId: String = "",
var scope: String = "", var scope: String = "",
// "content", etc.
var rulesetKey: String = "",
var pushRules: RealmList<PushRuleEntity> = RealmList() var pushRules: RealmList<PushRuleEntity> = RealmList()
) : RealmObject() { ) : RealmObject() {
private var kindStr: String = RuleKind.CONTENT.name
var kind: RuleKind
get() {
return RuleKind.valueOf(kindStr)
}
set(value) {
kindStr = value.name
}
companion object companion object
} }

View File

@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.session.pushers.PusherState import im.vector.matrix.android.api.session.pushers.PusherState
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Index
//TODO //TODO
// at java.lang.Thread.run(Thread.java:764) // at java.lang.Thread.run(Thread.java:764)
@ -29,7 +28,6 @@ import io.realm.annotations.Index
// at im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70) // 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) // at io.realm.Realm.executeTransaction(Realm.java:1493)
internal open class PusherEntity( internal open class PusherEntity(
@Index var userId: String = "",
var pushKey: String = "", var pushKey: String = "",
var kind: String? = null, var kind: String? = null,
var appId: String = "", var appId: String = "",

View File

@ -26,7 +26,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "", var displayName: String? = "",
var avatarUrl: String? = "", var avatarUrl: String? = "",
var topic: String? = "", var topic: String? = "",
var latestEvent: TimelineEventEntity? = null, var latestPreviewableEvent: TimelineEventEntity? = null,
var heroes: RealmList<String> = RealmList(), var heroes: RealmList<String> = RealmList(),
var joinedMembersCount: Int? = 0, var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0, var invitedMembersCount: Int? = 0,
@ -35,7 +35,9 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var otherMemberIds: RealmList<String> = RealmList(), var otherMemberIds: RealmList<String> = RealmList(),
var notificationCount: Int = 0, var notificationCount: Int = 0,
var highlightCount: Int = 0, var highlightCount: Int = 0,
var tags: RealmList<RoomTagEntity> = RealmList() var hasUnreadMessages: Boolean = false,
var tags: RealmList<RoomTagEntity> = RealmList(),
var userDrafts: UserDraftsEntity? = null
) : RealmObject() { ) : RealmObject() {
private var membershipStr: String = Membership.NONE.name private var membershipStr: String = Membership.NONE.name

View File

@ -43,6 +43,8 @@ import io.realm.annotations.RealmModule
PushConditionEntity::class, PushConditionEntity::class,
PusherEntity::class, PusherEntity::class,
PusherDataEntity::class, PusherDataEntity::class,
ReadReceiptsSummaryEntity::class ReadReceiptsSummaryEntity::class,
UserDraftsEntity::class,
DraftEntity::class
]) ])
internal class SessionRealmModule internal class SessionRealmModule

View File

@ -0,0 +1,36 @@
/*
* 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.RealmResults
import io.realm.annotations.LinkingObjects
/**
* Create a specific table to be able to do direct query on it and keep the draft ordered
*/
internal open class UserDraftsEntity(var userDrafts: RealmList<DraftEntity> = RealmList()
) : RealmObject() {
// Link to RoomSummaryEntity
@LinkingObjects("userDrafts")
val roomSummaryEntity: RealmResults<RoomSummaryEntity>? = null
companion object
}

View File

@ -23,9 +23,9 @@ import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.kotlin.where import io.realm.kotlin.where
internal fun GroupEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<GroupEntity> { internal fun GroupEntity.Companion.where(realm: Realm, groupId: String): RealmQuery<GroupEntity> {
return realm.where<GroupEntity>() return realm.where<GroupEntity>()
.equalTo(GroupEntityFields.GROUP_ID, roomId) .equalTo(GroupEntityFields.GROUP_ID, groupId)
} }
internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<GroupEntity> { internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<GroupEntity> {

View File

@ -30,3 +30,7 @@ internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupId: String? =
return query return query
} }
internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupIds: List<String>): RealmQuery<GroupSummaryEntity> {
return realm.where<GroupSummaryEntity>()
.`in`(GroupSummaryEntityFields.GROUP_ID, groupIds.toTypedArray())
}

View File

@ -15,6 +15,7 @@
*/ */
package im.vector.matrix.android.internal.database.query package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.internal.database.model.PushRulesEntity 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.PushRulesEntityFields
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
@ -24,10 +25,8 @@ import io.realm.RealmQuery
import io.realm.kotlin.where import io.realm.kotlin.where
internal fun PusherEntity.Companion.where(realm: Realm, internal fun PusherEntity.Companion.where(realm: Realm,
userId: String,
pushKey: String? = null): RealmQuery<PusherEntity> { pushKey: String? = null): RealmQuery<PusherEntity> {
return realm.where<PusherEntity>() return realm.where<PusherEntity>()
.equalTo(PusherEntityFields.USER_ID, userId)
.apply { .apply {
if (pushKey != null) { if (pushKey != null) {
equalTo(PusherEntityFields.PUSH_KEY, pushKey) equalTo(PusherEntityFields.PUSH_KEY, pushKey)
@ -36,11 +35,9 @@ internal fun PusherEntity.Companion.where(realm: Realm,
} }
internal fun PushRulesEntity.Companion.where(realm: Realm, internal fun PushRulesEntity.Companion.where(realm: Realm,
userId: String,
scope: String, scope: String,
ruleSetKey: String): RealmQuery<PushRulesEntity> { kind: RuleKind): RealmQuery<PushRulesEntity> {
return realm.where<PushRulesEntity>() return realm.where<PushRulesEntity>()
.equalTo(PushRulesEntityFields.USER_ID, userId)
.equalTo(PushRulesEntityFields.SCOPE, scope) .equalTo(PushRulesEntityFields.SCOPE, scope)
.equalTo(PushRulesEntityFields.RULESET_KEY, ruleSetKey) .equalTo(PushRulesEntityFields.KIND_STR, kind.name)
} }

View File

@ -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.internal.database.query
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
internal fun isEventRead(monarchy: Monarchy,
userId: String?,
roomId: String?,
eventId: String?): Boolean {
if (userId.isNullOrBlank() || roomId.isNullOrBlank() || eventId.isNullOrBlank()) {
return false
}
var isEventRead = false
monarchy.doWithRealm { realm ->
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm
val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root
isEventRead = if (eventToCheck?.sender == userId) {
true
} else {
val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() ?: return@doWithRealm
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex ?: Int.MIN_VALUE
val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE
eventToCheckIndex <= readReceiptIndex
}
}
return isEventRead
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.model.UserDraftsEntity
import im.vector.matrix.android.internal.database.model.UserDraftsEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
internal fun UserDraftsEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<UserDraftsEntity> {
val query = realm.where<UserDraftsEntity>()
if (roomId != null) {
query.equalTo(UserDraftsEntityFields.ROOM_SUMMARY_ENTITY + "." + RoomSummaryEntityFields.ROOM_ID, roomId)
}
return query
}

View File

@ -0,0 +1,23 @@
/*
* 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.di
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class UserCacheDirectory

View File

@ -18,6 +18,9 @@ package im.vector.matrix.android.internal.di
import javax.inject.Scope import javax.inject.Scope
/**
* Use the annotation @MatrixScope to annotate classes we want the SDK to instantiate only once
*/
@Scope @Scope
@MustBeDocumented @MustBeDocumented
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)

View File

@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
import im.vector.matrix.android.internal.session.sync.model.UserAccountData import im.vector.matrix.android.internal.session.sync.model.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
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.session.sync.model.UserAccountDataPushRules
object MoshiProvider { object MoshiProvider {
@ -31,6 +32,7 @@ object MoshiProvider {
.add(UriMoshiAdapter()) .add(UriMoshiAdapter())
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java) .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java)
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
.registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
) )
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)

View File

@ -35,7 +35,7 @@ internal object NetworkModule {
@Provides @Provides
@JvmStatic @JvmStatic
fun providesHttpLogingInterceptor(): HttpLoggingInterceptor { fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor {
val logger = FormattedJsonHttpLogger() val logger = FormattedJsonHttpLogger()
val interceptor = HttpLoggingInterceptor(logger) val interceptor = HttpLoggingInterceptor(logger)
interceptor.level = BuildConfig.OKHTTP_LOGGING_LEVEL interceptor.level = BuildConfig.OKHTTP_LOGGING_LEVEL

View File

@ -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.di
import javax.inject.Qualifier
/**
* Used to inject the userId
*/
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class UserId
/**
* Used to inject the md5 of the userId
*/
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class UserMd5

View File

@ -20,44 +20,64 @@ import android.content.Context
import com.novoda.merlin.Merlin import com.novoda.merlin.Merlin
import com.novoda.merlin.MerlinsBeard import com.novoda.merlin.MerlinsBeard
import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.ArrayList
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@MatrixScope @MatrixScope
internal class NetworkConnectivityChecker @Inject constructor(context: Context) { internal class NetworkConnectivityChecker @Inject constructor(context: Context,
backgroundDetectionObserver: BackgroundDetectionObserver)
: BackgroundDetectionObserver.Listener {
private val merlin = Merlin.Builder() private val merlin = Merlin.Builder()
.withConnectableCallbacks() .withConnectableCallbacks()
.withDisconnectableCallbacks() .withDisconnectableCallbacks()
.build(context) .build(context)
private val merlinsBeard = MerlinsBeard.Builder().build(context) private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>())
private val listeners = Collections.synchronizedList(ArrayList<Listener>())
// True when internet is available
var hasInternetAccess = MerlinsBeard.Builder().build(context).isConnected
private set
init { init {
backgroundDetectionObserver.register(this)
}
override fun onMoveToForeground() {
merlin.bind() merlin.bind()
merlin.registerDisconnectable { merlin.registerDisconnectable {
Timber.v("On Disconnect") if (hasInternetAccess) {
val localListeners = listeners.toList() Timber.v("On Disconnect")
localListeners.forEach { hasInternetAccess = false
it.onDisconnect() val localListeners = listeners.toList()
localListeners.forEach {
it.onDisconnect()
}
} }
} }
merlin.registerConnectable { merlin.registerConnectable {
Timber.v("On Connect") if (!hasInternetAccess) {
val localListeners = listeners.toList() Timber.v("On Connect")
localListeners.forEach { hasInternetAccess = true
it.onConnect() val localListeners = listeners.toList()
localListeners.forEach {
it.onConnect()
}
} }
} }
} }
override fun onMoveToBackground() {
merlin.unbind()
}
suspend fun waitUntilConnected() { suspend fun waitUntilConnected() {
if (isConnected()) { if (hasInternetAccess) {
return return
} else { } else {
suspendCoroutine<Unit> { continuation -> suspendCoroutine<Unit> { continuation ->
@ -79,10 +99,6 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context)
listeners.remove(listener) listeners.remove(listener)
} }
fun isConnected(): Boolean {
return merlinsBeard.isConnected
}
interface Listener { interface Listener {
fun onConnect() { fun onConnect() {

View File

@ -18,10 +18,13 @@ package im.vector.matrix.android.internal.network
import com.squareup.moshi.JsonDataException import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.failure.ConsentNotGivenError
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.CancellationException
import okhttp3.ResponseBody import okhttp3.ResponseBody
import org.greenrobot.eventbus.EventBus
import retrofit2.Call import retrofit2.Call
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
@ -47,6 +50,7 @@ internal class Request<DATA> {
is IOException -> Failure.NetworkConnection(exception) is IOException -> Failure.NetworkConnection(exception)
is Failure.ServerError, is Failure.ServerError,
is Failure.OtherServerError -> exception is Failure.OtherServerError -> exception
is CancellationException -> Failure.Cancelled(exception)
else -> Failure.Unknown(exception) else -> Failure.Unknown(exception)
} }
} }
@ -65,6 +69,11 @@ internal class Request<DATA> {
val matrixError = matrixErrorAdapter.fromJson(errorBodyStr) val matrixError = matrixErrorAdapter.fromJson(errorBodyStr)
if (matrixError != null) { if (matrixError != null) {
if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) {
// Also send this error to the bus, for a global management
EventBus.getDefault().post(ConsentNotGivenError(matrixError.consentUri))
}
return Failure.ServerError(matrixError, httpCode) return Failure.ServerError(matrixError, httpCode)
} }
} catch (ex: JsonDataException) { } catch (ex: JsonDataException) {

View File

@ -24,7 +24,7 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@MatrixScope @MatrixScope
internal class UserAgentHolder @Inject constructor(val context: Context) { internal class UserAgentHolder @Inject constructor(private val context: Context) {
var userAgent: String = "" var userAgent: String = ""
private set private set

View File

@ -24,7 +24,6 @@ import java.security.KeyStore
import java.security.MessageDigest import java.security.MessageDigest
import java.security.cert.CertificateException import java.security.cert.CertificateException
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.*
import javax.net.ssl.* import javax.net.ssl.*
import kotlin.experimental.and import kotlin.experimental.and

View File

@ -25,7 +25,6 @@ import java.net.UnknownHostException
import java.security.KeyManagementException import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.*
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory import javax.net.ssl.SSLSocketFactory
@ -101,25 +100,16 @@ constructor(trustPinned: Array<TrustManager>, acceptedTlsVersions: List<TlsVersi
} }
private fun enableTLSOnSocket(socket: Socket?): Socket? { private fun enableTLSOnSocket(socket: Socket?): Socket? {
if (socket != null && socket is SSLSocket) { if (socket is SSLSocket) {
val sslSocket = socket as SSLSocket? val supportedProtocols = socket.supportedProtocols.toSet()
val filteredEnabledProtocols = enabledProtocols.filter { it in supportedProtocols }
val supportedProtocols = Arrays.asList(*sslSocket!!.supportedProtocols) if (filteredEnabledProtocols.isNotEmpty()) {
val filteredEnabledProtocols = ArrayList<String>()
for (protocol in enabledProtocols) {
if (supportedProtocols.contains(protocol)) {
filteredEnabledProtocols.add(protocol)
}
}
if (!filteredEnabledProtocols.isEmpty()) {
try { try {
sslSocket.enabledProtocols = filteredEnabledProtocols.toTypedArray() socket.enabledProtocols = filteredEnabledProtocols.toTypedArray()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
} }
} }
} }
return socket return socket

View File

@ -20,11 +20,11 @@ import android.content.Context
import android.os.Environment import android.os.Environment
import arrow.core.Try import arrow.core.Try
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.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.md5 import im.vector.matrix.android.internal.util.md5
@ -40,7 +40,7 @@ import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
internal class DefaultFileService @Inject constructor(private val context: Context, internal class DefaultFileService @Inject constructor(private val context: Context,
private val sessionParams: SessionParams, @UserMd5 private val userMd5: String,
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService { private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
@ -105,7 +105,7 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
// Create dir tree (MF stands for Matrix File): // Create dir tree (MF stands for Matrix File):
// <cache>/MF/<md5(userId)>/<md5(id)>/ // <cache>/MF/<md5(userId)>/<md5(id)>/
val tmpFolderRoot = File(context.cacheDir, "MF") val tmpFolderRoot = File(context.cacheDir, "MF")
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5()) val tmpFolderUser = File(tmpFolderRoot, userMd5)
File(tmpFolderUser, id.md5()) File(tmpFolderUser, id.md5())
} }
FileService.DownloadMode.TO_EXPORT -> { FileService.DownloadMode.TO_EXPORT -> {

View File

@ -15,6 +15,7 @@
*/ */
package im.vector.matrix.android.internal.session package im.vector.matrix.android.internal.session
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.InitialSyncProgressService
@ -25,31 +26,33 @@ import javax.inject.Inject
@SessionScope @SessionScope
class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgressService { class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgressService {
var status = MutableLiveData<InitialSyncProgressService.Status>() private var status = MutableLiveData<InitialSyncProgressService.Status>()
var rootTask: TaskInfo? = null private var rootTask: TaskInfo? = null
override fun getLiveStatus(): LiveData<InitialSyncProgressService.Status?> { override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status?> {
return status return status
} }
fun startTask(@StringRes nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) {
fun startTask(nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) { // Create a rootTask, or add a child to the leaf
if (rootTask == null) { if (rootTask == null) {
rootTask = TaskInfo(nameRes, totalProgress) rootTask = TaskInfo(nameRes, totalProgress)
} else { } else {
val currentLeaf = rootTask!!.leaf() val currentLeaf = rootTask!!.leaf()
val newTask = TaskInfo(nameRes, totalProgress)
newTask.parent = currentLeaf val newTask = TaskInfo(nameRes,
newTask.offset = currentLeaf.currentProgress totalProgress,
currentLeaf,
parentWeight)
currentLeaf.child = newTask currentLeaf.child = newTask
newTask.parentWeight = parentWeight
} }
reportProgress(0) reportProgress(0)
} }
fun reportProgress(progress: Int) { fun reportProgress(progress: Int) {
rootTask?.leaf()?.incrementProgress(progress) rootTask?.leaf()?.setProgress(progress)
} }
fun endTask(nameRes: Int) { fun endTask(nameRes: Int) {
@ -58,7 +61,7 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
//close it //close it
val parent = endedTask.parent val parent = endedTask.parent
parent?.child = null parent?.child = null
parent?.incrementProgress(endedTask.offset + (endedTask.totalProgress * endedTask.parentWeight).toInt()) parent?.setProgress(endedTask.offset + (endedTask.totalProgress * endedTask.parentWeight).toInt())
} }
if (endedTask?.parent == null) { if (endedTask?.parent == null) {
status.postValue(null) status.postValue(null)
@ -71,14 +74,17 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
} }
inner class TaskInfo(var nameRes: Int, private inner class TaskInfo(@StringRes var nameRes: Int,
var totalProgress: Int) { var totalProgress: Int,
var parent: TaskInfo? = null var parent: TaskInfo? = null,
var parentWeight: Float = 1f,
var offset: Int = parent?.currentProgress ?: 0) {
var child: TaskInfo? = null var child: TaskInfo? = null
var parentWeight: Float = 1f
var currentProgress: Int = 0 var currentProgress: Int = 0
var offset: Int = 0
/**
* Get the further child
*/
fun leaf(): TaskInfo { fun leaf(): TaskInfo {
var last = this var last = this
while (last.child != null) { while (last.child != null) {
@ -87,26 +93,27 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
return last return last
} }
fun incrementProgress(progress: Int) { /**
* Set progress of the parent if any (which will post value), or post the value
*/
fun setProgress(progress: Int) {
currentProgress = progress currentProgress = progress
// val newProgress = Math.min(currentProgress + progress, totalProgress) // val newProgress = Math.min(currentProgress + progress, totalProgress)
parent?.let { parent?.let {
val parentProgress = (currentProgress * parentWeight).toInt() val parentProgress = (currentProgress * parentWeight).toInt()
it.incrementProgress(offset + parentProgress) it.setProgress(offset + parentProgress)
} } ?: run {
if (parent == null) { Timber.e("--- ${leaf().nameRes}: $currentProgress")
Timber.e("--- ${leaf().nameRes}: ${currentProgress}")
status.postValue( status.postValue(
InitialSyncProgressService.Status(leaf().nameRes, currentProgress) InitialSyncProgressService.Status(leaf().nameRes, currentProgress)
) )
} }
} }
} }
} }
inline fun <T> reportSubtask(reporter: DefaultInitialSyncProgressService?, inline fun <T> reportSubtask(reporter: DefaultInitialSyncProgressService?,
nameRes: Int, @StringRes nameRes: Int,
totalProgress: Int, totalProgress: Int,
parentWeight: Float = 1f, parentWeight: Float = 1f,
block: () -> T): T { block: () -> T): T {
@ -121,11 +128,11 @@ inline fun <K, V, R> Map<out K, V>.mapWithProgress(reporter: DefaultInitialSyncP
taskId: Int, taskId: Int,
weight: Float, weight: Float,
transform: (Map.Entry<K, V>) -> R): List<R> { transform: (Map.Entry<K, V>) -> R): List<R> {
val total = count() val total = count().toFloat()
var current = 0 var current = 0
reporter?.startTask(taskId, 100, weight) reporter?.startTask(taskId, 100, weight)
return this.map { return map {
reporter?.reportProgress((current / total.toFloat() * 100).toInt()) reporter?.reportProgress((current / total * 100).toInt())
current++ current++
transform.invoke(it) transform.invoke(it)
}.also { }.also {

View File

@ -35,16 +35,15 @@ 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.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.securestorage.SecureStorageService
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.util.MatrixCallbackDelegate import im.vector.matrix.android.internal.crypto.DefaultCryptoService
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 im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
@ -63,8 +62,9 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val signOutService: Lazy<SignOutService>, private val signOutService: Lazy<SignOutService>,
private val pushRuleService: Lazy<PushRuleService>, private val pushRuleService: Lazy<PushRuleService>,
private val pushersService: Lazy<PushersService>, private val pushersService: Lazy<PushersService>,
private val cryptoService: Lazy<CryptoManager>, private val cryptoService: Lazy<DefaultCryptoService>,
private val fileService: Lazy<FileService>, private val fileService: Lazy<FileService>,
private val secureStorageService: Lazy<SecureStorageService>,
private val syncThreadProvider: Provider<SyncThread>, private val syncThreadProvider: Provider<SyncThread>,
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
private val contentUploadProgressTracker: ContentUploadStateTracker, private val contentUploadProgressTracker: ContentUploadStateTracker,
@ -75,13 +75,13 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
GroupService by groupService.get(), GroupService by groupService.get(),
UserService by userService.get(), UserService by userService.get(),
CryptoService by cryptoService.get(), CryptoService by cryptoService.get(),
CacheService by cacheService.get(),
SignOutService by signOutService.get(), SignOutService by signOutService.get(),
FilterService by filterService.get(), FilterService by filterService.get(),
PushRuleService by pushRuleService.get(), PushRuleService by pushRuleService.get(),
PushersService by pushersService.get(), PushersService by pushersService.get(),
FileService by fileService.get(), FileService by fileService.get(),
InitialSyncProgressService by initialSyncProgressService.get() { InitialSyncProgressService by initialSyncProgressService.get(),
SecureStorageService by secureStorageService.get() {
private var isOpen = false private var isOpen = false
@ -144,43 +144,6 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
} }
} }
@MainThread
override fun signOut(callback: MatrixCallback<Unit>) {
Timber.w("SIGN_OUT: start")
assert(isOpen)
Timber.w("SIGN_OUT: call webservice")
return signOutService.get().signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: call webservice -> SUCCESS: clear cache")
stopSync()
stopAnyBackgroundSync()
// Clear the cache
cacheService.get().clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: clear cache -> SUCCESS: clear crypto cache")
cryptoService.get().clearCryptoCache(MatrixCallbackDelegate(callback))
WorkManagerUtil.cancelAllWorks(context)
callback.onSuccess(Unit)
}
override fun onFailure(failure: Throwable) {
// ignore error
Timber.e("SIGN_OUT: clear cache -> ERROR: ignoring")
onSuccess(Unit)
}
})
}
override fun onFailure(failure: Throwable) {
// Ignore failure
Timber.e("SIGN_OUT: call webservice -> ERROR: ignoring")
onSuccess(Unit)
}
})
}
override fun clearCache(callback: MatrixCallback<Unit>) { override fun clearCache(callback: MatrixCallback<Unit>) {
stopSync() stopSync()
stopAnyBackgroundSync() stopAnyBackgroundSync()

View File

@ -27,21 +27,20 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.InitialSyncProgressService
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.database.configureEncryption import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.database.model.SessionRealmModule import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.di.Authenticated import im.vector.matrix.android.internal.di.*
import im.vector.matrix.android.internal.di.SessionDatabase
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.network.interceptors.CurlLoggingInterceptor
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.room.DefaultRoomFactory
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.RoomFactory
import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver
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.room.tombstone.RoomTombstoneEventLiveObserver import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver
import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStorageService
import im.vector.matrix.android.internal.util.md5 import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -53,6 +52,7 @@ internal abstract class SessionModule {
@Module @Module
companion object { companion object {
internal const val DB_ALIAS_PREFIX = "session_db_"
@JvmStatic @JvmStatic
@Provides @Provides
@ -67,18 +67,40 @@ internal abstract class SessionModule {
return sessionParams.credentials return sessionParams.credentials
} }
@JvmStatic
@UserId
@Provides
fun providesUserId(credentials: Credentials): String {
return credentials.userId
}
@JvmStatic
@UserMd5
@Provides
fun providesUserMd5(@UserId userId: String): String {
return userId.md5()
}
@JvmStatic
@Provides
@UserCacheDirectory
fun providesFilesDir(@UserMd5 userMd5: String, context: Context): File {
return File(context.filesDir, userMd5)
}
@JvmStatic @JvmStatic
@Provides @Provides
@SessionDatabase @SessionDatabase
@SessionScope @SessionScope
fun providesRealmConfiguration(sessionParams: SessionParams, context: Context): RealmConfiguration { fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils,
val childPath = sessionParams.credentials.userId.md5() @UserCacheDirectory directory: File,
val directory = File(context.filesDir, childPath) @UserMd5 userMd5: String): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.directory(directory) .directory(directory)
.name("disk_store.realm") .name("disk_store.realm")
.configureEncryption("session_db_$childPath", context) .apply {
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
}
.modules(SessionRealmModule()) .modules(SessionRealmModule())
.deleteRealmIfMigrationNeeded() .deleteRealmIfMigrationNeeded()
.build() .build()
@ -101,7 +123,18 @@ internal abstract class SessionModule {
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient, fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient { accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
return okHttpClient.newBuilder() return okHttpClient.newBuilder()
.addInterceptor(accessTokenInterceptor) .apply {
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
interceptors().removeAll(existingCurlInterceptors)
addInterceptor(accessTokenInterceptor)
// Re add eventually the curl logging interceptors
existingCurlInterceptors.forEach {
addInterceptor(it)
}
}
.build() .build()
} }
@ -142,4 +175,7 @@ internal abstract class SessionModule {
@Binds @Binds
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService
@Binds
abstract fun bindSecureStorageService(secureStorageService: DefaultSecureStorageService): SecureStorageService
} }

View File

@ -30,9 +30,8 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
private val listeners = mutableMapOf<String, MutableList<ContentUploadStateTracker.UpdateListener>>() private val listeners = mutableMapOf<String, MutableList<ContentUploadStateTracker.UpdateListener>>()
override fun track(key: String, updateListener: ContentUploadStateTracker.UpdateListener) { override fun track(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
val listeners = listeners[key] ?: ArrayList() val listeners = listeners.getOrPut(key) { ArrayList() }
listeners.add(updateListener) listeners.add(updateListener)
this.listeners[key] = listeners
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
mainHandler.post { updateListener.onUpdate(currentState) } mainHandler.post { updateListener.onUpdate(currentState) }
} }

View File

@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.session.filter package im.vector.matrix.android.internal.session.filter
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -33,7 +33,7 @@ internal interface SaveFilterTask : Task<SaveFilterTask.Params, Unit> {
} }
internal class DefaultSaveFilterTask @Inject constructor(private val sessionParams: SessionParams, internal class DefaultSaveFilterTask @Inject constructor(@UserId private val userId: String,
private val filterAPI: FilterApi, private val filterAPI: FilterApi,
private val filterRepository: FilterRepository private val filterRepository: FilterRepository
) : SaveFilterTask { ) : SaveFilterTask {
@ -41,7 +41,7 @@ internal class DefaultSaveFilterTask @Inject constructor(private val sessionPara
override suspend fun execute(params: SaveFilterTask.Params) { override suspend fun execute(params: SaveFilterTask.Params) {
val filterResponse = executeRequest<FilterResponse> { val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry // TODO auto retry
apiCall = filterAPI.uploadFilter(sessionParams.credentials.userId, params.filter) apiCall = filterAPI.uploadFilter(userId, params.filter)
} }
filterRepository.storeFilterId(params.filter, filterResponse.filterId) filterRepository.storeFilterId(params.filter, filterResponse.filterId)
} }

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.group package im.vector.matrix.android.internal.session.group
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership
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
@ -64,8 +65,7 @@ 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()
@ -74,8 +74,12 @@ internal class DefaultGetGroupDataTask @Inject constructor(
val userIds = groupUsers.users.map { it.userId } val userIds = groupUsers.users.map { it.userId }
groupSummaryEntity.userIds.clear() groupSummaryEntity.userIds.clear()
groupSummaryEntity.userIds.addAll(userIds) groupSummaryEntity.userIds.addAll(userIds)
groupSummaryEntity.membership = when (groupSummary.user?.membership) {
Membership.JOIN.value -> Membership.JOIN
Membership.INVITE.value -> Membership.INVITE
else -> Membership.LEAVE
}
} }
} }
} }

View File

@ -20,35 +20,48 @@ import android.content.Context
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
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.di.SessionDatabase import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import io.realm.OrderedCollectionChangeSet import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults import io.realm.RealmResults
import javax.inject.Inject import javax.inject.Inject
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class GroupSummaryUpdater @Inject constructor(private val context: Context, internal class GroupSummaryUpdater @Inject constructor(private val context: Context,
private val credentials: Credentials, @UserId private val userId: String,
@SessionDatabase realmConfiguration: RealmConfiguration) private val monarchy: Monarchy)
: RealmLiveEntityObserver<GroupEntity>(realmConfiguration) { : RealmLiveEntityObserver<GroupEntity>(monarchy.realmConfiguration) {
override val query = Monarchy.Query<GroupEntity> { GroupEntity.where(it) } override val query = Monarchy.Query { GroupEntity.where(it) }
override fun onChange(results: RealmResults<GroupEntity>, changeSet: OrderedCollectionChangeSet) { override fun onChange(results: RealmResults<GroupEntity>, changeSet: OrderedCollectionChangeSet) {
val newGroupIds = changeSet.insertions // `insertions` for new groups and `changes` to handle left groups
val modifiedGroupEntity = (changeSet.insertions + changeSet.changes)
.asSequence() .asSequence()
.mapNotNull { results[it]?.groupId} .mapNotNull { results[it] }
.toList()
fetchGroupsData(modifiedGroupEntity
.filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
.map { it.groupId }
.toList())
deleteGroups(modifiedGroupEntity
.filter { it.membership == Membership.LEAVE }
.map { it.groupId }
.toList())
}
private fun fetchGroupsData(groupIds: List<String>) {
val getGroupDataWorkerParams = GetGroupDataWorker.Params(userId, groupIds)
val getGroupDataWorkerParams = GetGroupDataWorker.Params(credentials.userId, newGroupIds)
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
val sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>() val sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
@ -61,4 +74,15 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
.enqueue() .enqueue()
} }
/**
* Delete the GroupSummaryEntity of left groups
*/
private fun deleteGroups(groupIds: List<String>) {
monarchy
.writeAsync { realm ->
GroupSummaryEntity.where(realm, groupIds)
.findAll()
.deleteAllFromRealm()
}
}
} }

View File

@ -17,9 +17,10 @@ package im.vector.matrix.android.internal.session.notification
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.pushrules.Action
import im.vector.matrix.android.api.pushrules.PushRuleService import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.RuleSetKey
import im.vector.matrix.android.api.pushrules.getActions
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -35,53 +36,60 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
internal class DefaultPushRuleService @Inject constructor( internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
private val sessionParams: SessionParams, private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
private val pushRulesTask: GetPushRulesTask, private val taskExecutor: TaskExecutor,
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask, private val monarchy: Monarchy
private val taskExecutor: TaskExecutor,
private val monarchy: Monarchy
) : PushRuleService { ) : PushRuleService {
private var listeners = ArrayList<PushRuleService.PushRuleListener>() private var listeners = ArrayList<PushRuleService.PushRuleListener>()
override fun fetchPushRules(scope: String) { override fun fetchPushRules(scope: String) {
pushRulesTask getPushRulesTask
.configureWith(GetPushRulesTask.Params(scope)) .configureWith(GetPushRulesTask.Params(scope))
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override fun getPushRules(scope: String): List<PushRule> { override fun getPushRules(scope: String): List<PushRule> {
var contentRules: List<PushRule> = emptyList() var contentRules: List<PushRule> = emptyList()
var overrideRules: List<PushRule> = emptyList() var overrideRules: List<PushRule> = emptyList()
var roomRules: List<PushRule> = emptyList() var roomRules: List<PushRule> = emptyList()
var senderRules: List<PushRule> = emptyList() var senderRules: List<PushRule> = emptyList()
var underrideRules: List<PushRule> = emptyList() var underrideRules: List<PushRule> = emptyList()
// TODO Create const for ruleSetKey
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "content").findFirst()?.let { re -> PushRulesEntity.where(realm, scope, RuleSetKey.CONTENT)
contentRules = re.pushRules.map { PushRulesMapper.mapContentRule(it) } .findFirst()
} ?.let { pushRulesEntity ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "override").findFirst()?.let { re -> contentRules = pushRulesEntity.pushRules.map { PushRulesMapper.mapContentRule(it) }
overrideRules = re.pushRules.map { PushRulesMapper.map(it) } }
} PushRulesEntity.where(realm, scope, RuleSetKey.OVERRIDE)
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "room").findFirst()?.let { re -> .findFirst()
roomRules = re.pushRules.map { PushRulesMapper.mapRoomRule(it) } ?.let { pushRulesEntity ->
} overrideRules = pushRulesEntity.pushRules.map { PushRulesMapper.map(it) }
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "sender").findFirst()?.let { re -> }
senderRules = re.pushRules.map { PushRulesMapper.mapSenderRule(it) } PushRulesEntity.where(realm, scope, RuleSetKey.ROOM)
} .findFirst()
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "underride").findFirst()?.let { re -> ?.let { pushRulesEntity ->
underrideRules = re.pushRules.map { PushRulesMapper.map(it) } roomRules = pushRulesEntity.pushRules.map { PushRulesMapper.mapRoomRule(it) }
} }
PushRulesEntity.where(realm, scope, RuleSetKey.SENDER)
.findFirst()
?.let { pushRulesEntity ->
senderRules = pushRulesEntity.pushRules.map { PushRulesMapper.mapSenderRule(it) }
}
PushRulesEntity.where(realm, scope, RuleSetKey.UNDERRIDE)
.findFirst()
?.let { pushRulesEntity ->
underrideRules = pushRulesEntity.pushRules.map { PushRulesMapper.map(it) }
}
} }
return contentRules + overrideRules + roomRules + senderRules + underrideRules // Ref. for the order: https://matrix.org/docs/spec/client_server/latest#push-rules
return overrideRules + contentRules + roomRules + senderRules + underrideRules
} }
override fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable { override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable {
return updatePushRuleEnableStatusTask return updatePushRuleEnableStatusTask
.configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) { .configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) {
this.callback = callback this.callback = callback
@ -114,8 +122,9 @@ internal class DefaultPushRuleService @Inject constructor(
fun dispatchBing(event: Event, rule: PushRule) { fun dispatchBing(event: Event, rule: PushRule) {
try { try {
val actionsList = rule.getActions()
listeners.forEach { listeners.forEach {
it.onMatchRule(event, Action.mapFrom(rule) ?: emptyList()) it.onMatchRule(event, actionsList)
} }
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "Error while dispatching bing") Timber.e(e, "Error while dispatching bing")
@ -132,6 +141,16 @@ internal class DefaultPushRuleService @Inject constructor(
} }
} }
fun dispatchRedactedEventId(redactedEventId: String) {
try {
listeners.forEach {
it.onEventRedacted(redactedEventId)
}
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching room left")
}
}
fun dispatchFinish() { fun dispatchFinish() {
try { try {
listeners.forEach { listeners.forEach {

View File

@ -16,11 +16,11 @@
package im.vector.matrix.android.internal.session.notification package im.vector.matrix.android.internal.session.notification
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.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.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver
import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
@ -37,7 +37,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
internal class DefaultProcessEventForPushTask @Inject constructor( internal class DefaultProcessEventForPushTask @Inject constructor(
private val defaultPushRuleService: DefaultPushRuleService, private val defaultPushRuleService: DefaultPushRuleService,
private val roomService: RoomService, private val roomService: RoomService,
private val sessionParams: SessionParams @UserId private val userId: String
) : ProcessEventForPushTask { ) : ProcessEventForPushTask {
override suspend fun execute(params: ProcessEventForPushTask.Params) { override suspend fun execute(params: ProcessEventForPushTask.Params) {
@ -68,7 +68,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
else -> false else -> false
} }
}.filter { }.filter {
it.senderId != sessionParams.credentials.userId it.senderId != userId
} }
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" + Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
" to check for push rules with ${params.rules.size} rules") " to check for push rules with ${params.rules.size} rules")
@ -78,11 +78,31 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
defaultPushRuleService.dispatchBing(event, it) defaultPushRuleService.dispatchBing(event, it)
} }
} }
val allRedactedEvents = params.syncResponse.join
.map { entries ->
entries.value.timeline?.events?.filter {
it.type == EventType.REDACTION
}
.orEmpty()
.mapNotNull { it.redacts }
}
.fold(emptyList<String>(), { acc, next ->
acc + next
})
Timber.v("[PushRules] Found ${allRedactedEvents.size} redacted events")
allRedactedEvents.forEach { redactedEventId ->
defaultPushRuleService.dispatchRedactedEventId(redactedEventId)
}
defaultPushRuleService.dispatchFinish() defaultPushRuleService.dispatchFinish()
} }
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? { private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
val conditionResolver = DefaultConditionResolver(event, roomService, sessionParams) // TODO This should be injected
val conditionResolver = DefaultConditionResolver(event, roomService, userId)
rules.filter { it.enabled }.forEach { rule -> rules.filter { it.enabled }.forEach { rule ->
val isFullfilled = rule.conditions?.map { val isFullfilled = rule.conditions?.map {
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false

View File

@ -57,14 +57,14 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
return Result.failure() return Result.failure()
} }
return try { return try {
setPusher(pusher, params.userId) setPusher(pusher)
Result.success() Result.success()
} catch (exception: Throwable) { } catch (exception: Throwable) {
when (exception) { when (exception) {
is Failure.NetworkConnection -> Result.retry() is Failure.NetworkConnection -> Result.retry()
else -> { else -> {
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
PusherEntity.where(realm, params.userId, pusher.pushKey).findFirst()?.let { PusherEntity.where(realm, pusher.pushKey).findFirst()?.let {
//update it //update it
it.state = PusherState.FAILED_TO_REGISTER it.state = PusherState.FAILED_TO_REGISTER
} }
@ -76,12 +76,12 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
} }
} }
private suspend fun setPusher(pusher: JsonPusher, userId: String) { private suspend fun setPusher(pusher: JsonPusher) {
executeRequest<Unit> { executeRequest<Unit> {
apiCall = pushersAPI.setPusher(pusher) apiCall = pushersAPI.setPusher(pusher)
} }
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
val echo = PusherEntity.where(realm, userId, pusher.pushKey).findFirst() val echo = PusherEntity.where(realm, pusher.pushKey).findFirst()
if (echo != null) { if (echo != null) {
//update it //update it
echo.appDisplayName = pusher.appDisplayName echo.appDisplayName = pusher.appDisplayName
@ -93,7 +93,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
echo.data?.url = pusher.data?.url echo.data?.url = pusher.data?.url
echo.state = PusherState.REGISTERED echo.state = PusherState.REGISTERED
} else { } else {
pusher.toEntity(userId).also { pusher.toEntity().also {
it.state = PusherState.REGISTERED it.state = PusherState.REGISTERED
realm.insertOrUpdate(it) realm.insertOrUpdate(it)
} }

View File

@ -15,15 +15,16 @@
*/ */
package im.vector.matrix.android.internal.session.pushers 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.pushrules.*
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.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber import timber.log.Timber
// TODO Inject constructor
internal class DefaultConditionResolver(private val event: Event, internal class DefaultConditionResolver(private val event: Event,
private val roomService: RoomService, private val roomService: RoomService,
private val sessionParams: SessionParams) : ConditionResolver { @UserId private val userId: String) : ConditionResolver {
override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean { override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean {
@ -45,8 +46,7 @@ internal class DefaultConditionResolver(private val event: Event,
override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean { override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean {
val roomId = event.roomId ?: return false val roomId = event.roomId ?: return false
val room = roomService.getRoom(roomId) ?: return false val room = roomService.getRoom(roomId) ?: return false
val myDisplayName = room.getRoomMember(sessionParams.credentials.userId)?.displayName val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false
?: return false
return containsDisplayNameCondition.isSatisfied(event, myDisplayName) return containsDisplayNameCondition.isSatisfied(event, myDisplayName)
} }
} }

View File

@ -21,12 +21,12 @@ import androidx.work.BackoffPolicy
import androidx.work.WorkManager import androidx.work.WorkManager
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.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
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.UserId
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 im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil
@ -37,13 +37,12 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal class DefaultPusherService @Inject constructor( internal class DefaultPusherService @Inject constructor(private val context: Context,
private val context: Context, private val monarchy: Monarchy,
private val monarchy: Monarchy, @UserId private val userId: String,
private val sessionParam: SessionParams, private val getPusherTask: GetPushersTask,
private val getPusherTask: GetPushersTask, private val removePusherTask: RemovePusherTask,
private val removePusherTask: RemovePusherTask, private val taskExecutor: TaskExecutor
private val taskExecutor: TaskExecutor
) : PushersService { ) : PushersService {
@ -70,7 +69,7 @@ internal class DefaultPusherService @Inject constructor(
append = append) append = append)
val params = AddHttpPusherWorker.Params(pusher, sessionParam.credentials.userId) val params = AddHttpPusherWorker.Params(pusher, userId)
val request = matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>() val request = matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerUtil.workConstraints)
@ -82,7 +81,7 @@ internal class DefaultPusherService @Inject constructor(
} }
override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>) { override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>) {
val params = RemovePusherTask.Params(sessionParam.credentials.userId, pushkey, appId) val params = RemovePusherTask.Params(pushkey, appId)
removePusherTask removePusherTask
.configureWith(params) { .configureWith(params) {
this.callback = callback this.callback = callback
@ -93,12 +92,12 @@ internal class DefaultPusherService @Inject constructor(
override fun livePushers(): LiveData<List<Pusher>> { override fun livePushers(): LiveData<List<Pusher>> {
return monarchy.findAllMappedWithChanges( return monarchy.findAllMappedWithChanges(
{ realm -> PusherEntity.where(realm, sessionParam.credentials.userId) }, { realm -> PusherEntity.where(realm) },
{ it.asDomain() } { it.asDomain() }
) )
} }
override fun pushers(): List<Pusher> { override fun pushers(): List<Pusher> {
return monarchy.fetchAllCopiedSync { PusherEntity.where(it, sessionParam.credentials.userId) }.map { it.asDomain() } return monarchy.fetchAllCopiedSync { PusherEntity.where(it) }.map { it.asDomain() }
} }
} }

View File

@ -15,80 +15,27 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
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.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import javax.inject.Inject import javax.inject.Inject
internal interface GetPushRulesTask : Task<GetPushRulesTask.Params, Unit> { internal interface GetPushRulesTask : Task<GetPushRulesTask.Params, Unit> {
data class Params(val scope: String) data class Params(val scope: String)
} }
/**
* We keep this task, but it should not be used anymore, the push rules comes from the sync response
*/
internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi, internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi,
private val monarchy: Monarchy, private val savePushRulesTask: SavePushRulesTask) : GetPushRulesTask {
private val sessionParams: SessionParams) : GetPushRulesTask {
override suspend fun execute(params: GetPushRulesTask.Params) { override suspend fun execute(params: GetPushRulesTask.Params) {
val response = executeRequest<GetPushRulesResponse> { val response = executeRequest<GetPushRulesResponse> {
apiCall = pushRulesApi.getAllRules() apiCall = pushRulesApi.getAllRules()
} }
val scope = params.scope
monarchy.awaitTransaction { 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") savePushRulesTask.execute(SavePushRulesTask.Params(response))
response.global.content?.forEach { rule ->
PushRulesMapper.map(rule).also {
content.pushRules.add(it)
}
}
realm.insertOrUpdate(content)
val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override")
response.global.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)
val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room")
response.global.room?.forEach { rule ->
PushRulesMapper.map(rule).also {
rooms.pushRules.add(it)
}
}
realm.insertOrUpdate(rooms)
val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender")
response.global.sender?.forEach { rule ->
PushRulesMapper.map(rule).also {
senders.pushRules.add(it)
}
}
realm.insertOrUpdate(senders)
val underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride")
response.global.underride?.forEach { rule ->
PushRulesMapper.map(rule).also {
underrides.pushRules.add(it)
}
}
realm.insertOrUpdate(underrides)
}
} }
} }

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.pushers.PusherState 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.mapper.toEntity
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction import im.vector.matrix.android.internal.util.awaitTransaction
@ -29,8 +27,7 @@ import javax.inject.Inject
internal interface GetPushersTask : Task<Unit, Unit> internal interface GetPushersTask : Task<Unit, Unit>
internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI: PushersAPI, internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI: PushersAPI,
private val monarchy: Monarchy, private val monarchy: Monarchy) : GetPushersTask {
private val sessionParams: SessionParams) : GetPushersTask {
override suspend fun execute(params: Unit) { override suspend fun execute(params: Unit) {
val response = executeRequest<GetPushersResponse> { val response = executeRequest<GetPushersResponse> {
@ -39,10 +36,9 @@ internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI:
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
//clear existings? //clear existings?
realm.where(PusherEntity::class.java) realm.where(PusherEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm() .findAll().deleteAllFromRealm()
response.pushers?.forEach { jsonPusher -> response.pushers?.forEach { jsonPusher ->
jsonPusher.toEntity(sessionParams.credentials.userId).also { jsonPusher.toEntity().also {
it.state = PusherState.REGISTERED it.state = PusherState.REGISTERED
realm.insertOrUpdate(it) realm.insertOrUpdate(it)
} }

View File

@ -59,6 +59,9 @@ internal abstract class PushersModule {
@Binds @Binds
abstract fun bindGetPushRulesTask(getPushRulesTask: DefaultGetPushRulesTask): GetPushRulesTask abstract fun bindGetPushRulesTask(getPushRulesTask: DefaultGetPushRulesTask): GetPushRulesTask
@Binds
abstract fun bindSavePushRulesTask(savePushRulesTask: DefaultSavePushRulesTask): SavePushRulesTask
@Binds @Binds
abstract fun bindRemovePusherTask(removePusherTask: DefaultRemovePusherTask): RemovePusherTask abstract fun bindRemovePusherTask(removePusherTask: DefaultRemovePusherTask): RemovePusherTask

View File

@ -28,8 +28,7 @@ import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> { internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> {
data class Params(val userId: String, data class Params(val pushKey: String,
val pushKey: String,
val pushAppId: String) val pushAppId: String)
} }
@ -40,12 +39,12 @@ internal class DefaultRemovePusherTask @Inject constructor(
override suspend fun execute(params: RemovePusherTask.Params) { override suspend fun execute(params: RemovePusherTask.Params) {
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
val existingEntity = PusherEntity.where(realm, params.userId, params.pushKey).findFirst() val existingEntity = PusherEntity.where(realm, params.pushKey).findFirst()
existingEntity?.state = PusherState.UNREGISTERING existingEntity?.state = PusherState.UNREGISTERING
} }
val existing = Realm.getInstance(monarchy.realmConfiguration).use { realm -> val existing = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
PusherEntity.where(realm, params.userId, params.pushKey).findFirst()?.asDomain() PusherEntity.where(realm, params.pushKey).findFirst()?.asDomain()
} ?: throw Exception("No existing pusher") } ?: throw Exception("No existing pusher")
val deleteBody = JsonPusher( val deleteBody = JsonPusher(
@ -64,7 +63,7 @@ internal class DefaultRemovePusherTask @Inject constructor(
apiCall = pushersAPI.setPusher(deleteBody) apiCall = pushersAPI.setPusher(deleteBody)
} }
monarchy.awaitTransaction { monarchy.awaitTransaction {
PusherEntity.where(it, params.userId, params.pushKey).findFirst()?.deleteFromRealm() PusherEntity.where(it, params.pushKey).findFirst()?.deleteFromRealm()
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More