Compare commits

..

107 Commits

Author SHA1 Message Date
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
fdc9e84dd5 Merge branch 'release/0.4.0' into develop 2019-08-30 15:04:43 +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
248 changed files with 4709 additions and 1825 deletions

View File

@ -1,6 +1,7 @@
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
# Last docker plugin version can be found here:
# 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
@ -18,6 +19,7 @@ steps:
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Assemble FDroid Debug version"
agents:
@ -32,6 +34,7 @@ steps:
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Build Google Play unsigned APK"
agents:
@ -46,6 +49,7 @@ steps:
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
# Code quality

View File

@ -1,4 +1,51 @@
Changes in RiotX 0.4.0 (2019-XX-XX)
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:

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.ReadReceipt
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 io.reactivex.Observable
import io.reactivex.Single
@ -54,6 +55,10 @@ class RxRoom(private val room: Room) {
return room.getEventReadReceiptsLive(eventId).asObservable()
}
fun liveDrafts(): Observable<List<UserDraft>> {
return room.getDraftsLive().asObservable()
}
}
fun Room.rx(): RxRoom {

View File

@ -139,6 +139,9 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
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'
releaseImplementation 'com.airbnb.okreplay:noop: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.RealmCryptoStoreModule
import io.realm.RealmConfiguration
import java.util.*
import kotlin.random.Random
internal class CryptoStoreHelper {
@ -35,7 +35,7 @@ internal class CryptoStoreHelper {
}
fun createCredential() = Credentials(
userId = "userId_" + Random().nextInt(),
userId = "userId_" + Random.nextInt(),
homeServer = "http://matrix.org",
accessToken = "access_token",
refreshToken = null,

View File

@ -17,16 +17,23 @@
package im.vector.matrix.android.api.auth
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.SessionParams
import im.vector.matrix.android.api.session.Session
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.
*/
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 login the login field
@ -56,4 +63,9 @@ interface Authenticator {
* @return the associated session if any, or null
*/
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
import im.vector.matrix.android.api.interfaces.DatedObject
import java.util.*
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.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import java.util.*
import java.util.Collections
/* ==========================================================================================
* 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) {
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 ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
// 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
class Action(val type: Type) {
enum class Type(val value: String) {
NOTIFY("notify"),
DONT_NOTIFY("dont_notify"),
COALESCE("coalesce"),
SET_TWEAK("set_tweak");
companion object {
fun safeValueOf(value: String): Type? {
try {
return valueOf(value)
} catch (e: IllegalArgumentException) {
return null
}
}
}
}
var tweak_action: String? = null
var stringValue: String? = null
var boolValue: Boolean? = null
companion object {
fun mapFrom(pushRule: PushRule): List<Action>? {
val actions = ArrayList<Action>()
pushRule.actions.forEach { actionStrOrObj ->
if (actionStrOrObj is String) {
when (actionStrOrObj) {
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
else -> {
Timber.w("Unsupported action type ${actionStrOrObj}")
null
}
}?.let {
actions.add(it)
}
} else if (actionStrOrObj is Map<*, *>) {
val tweakAction = actionStrOrObj["set_tweak"] as? String
when (tweakAction) {
"sound" -> {
(actionStrOrObj["value"] as? String)?.let { stringValue ->
Action(Action.Type.SET_TWEAK).also {
it.tweak_action = "sound"
it.stringValue = stringValue
actions.add(it)
}
}
}
"highlight" -> {
(actionStrOrObj["value"] as? Boolean)?.let { boolValue ->
Action(Action.Type.SET_TWEAK).also {
it.tweak_action = "highlight"
it.boolValue = boolValue
actions.add(it)
}
}
}
else -> {
Timber.w("Unsupported action type ${actionStrOrObj}")
}
}
} else {
Timber.w("Unsupported action type ${actionStrOrObj}")
return null
}
}
return if (actions.isEmpty()) null else actions
}
}
sealed class Action {
object Notify : Action()
object DoNotNotify : Action()
data class Sound(val sound: String) : Action()
data class Highlight(val highlight: Boolean) : Action()
}
private const val ACTION_NOTIFY = "notify"
private const val ACTION_DONT_NOTIFY = "dont_notify"
private const val ACTION_COALESCE = "coalesce"
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
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"
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#actions
*
* Convert
* <pre>
* "actions": [
* "notify",
* {
* "set_tweak": "sound",
* "value": "default"
* },
* {
* "set_tweak": "highlight"
* }
* ]
*
* To
* [
* Action.Notify,
* Action.Sound("default"),
* Action.Highlight(true)
* ]
*
* </pre>
*/
fun PushRule.getActions(): List<Action> {
val result = ArrayList<Action>()
actions.forEach { actionStrOrObj ->
when (actionStrOrObj) {
ACTION_NOTIFY -> Action.Notify
ACTION_DONT_NOTIFY -> Action.DoNotNotify
is Map<*, *> -> {
when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) {
ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
(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)
?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT)
}
ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
Action.Highlight(boolValue)
}
// When the value is not there, default is true, says the spec
?: Action.Highlight(true)
}
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
*/
fun fetchPushRules(scope: String = "global")
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
//TODO get push rule set
fun getPushRules(scope: String = "global"): List<PushRule>
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
//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)
@ -43,6 +43,7 @@ interface PushRuleService {
interface PushRuleListener {
fun onMatchRule(event: Event, actions: List<Action>)
fun onRoomLeft(roomId: String)
fun onEventRedacted(redactedEventId: String)
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.room.RoomService
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 {
return conditionResolver.resolveRoomMemberCountCondition(this)
}
override fun technicalDescription(): String {
return "Room member count is $`is`"
return "Room member count is $iz"
}
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>? {
try {
val match = regex.matcher(`is`)
if (match.find()) {
val prefix = match.group(1)
val count = match.group(2).toInt()
return prefix to count
}
val match = regex.find(iz) ?: return null
val (prefix, count) = match.destructured
return prefix to count.toInt()
} catch (t: Throwable) {
Timber.d(t)
}

View File

@ -37,7 +37,7 @@ object RuleIds {
// Default Underride Rules
const val RULE_ID_CALL = ".m.rule.call"
const val RULE_ID_one_to_one_encrypted_room = ".m.rule.encrypted_room_one_to_one"
const val RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM = ".m.rule.encrypted_room_one_to_one"
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"

View File

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

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) }
}
Condition.Kind.UNRECOGNIZE -> {
Timber.e("Unknwon kind $kind")
Timber.e("Unknown kind $kind")
null
}
}

View File

@ -20,10 +20,10 @@ import androidx.lifecycle.LiveData
interface InitialSyncProgressService {
fun getLiveStatus() : LiveData<Status?>
fun getInitialSyncProgressStatus() : LiveData<Status?>
data class Status(
@StringRes val statusText: Int?,
@StringRes val statusText: Int,
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.room.RoomDirectoryService
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.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState
@ -50,7 +51,8 @@ interface Session :
FileService,
PushRuleService,
PushersService,
InitialSyncProgressService {
InitialSyncProgressService,
SecureStorageService {
/**
* The params associated to the session
@ -87,7 +89,7 @@ interface Session :
/**
* This method start the sync thread.
*/
fun startSync(fromForeground : Boolean)
fun startSync(fromForeground: Boolean)
/**
* 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 clearCryptoCache(callback: MatrixCallback<Unit>)
fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener)

View File

@ -16,12 +16,15 @@
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.
* It can be retrieved through [im.vector.matrix.android.api.session.group.GroupService]
*/
data class GroupSummary(
val groupId: String,
val membership: Membership,
val displayName: String = "",
val shortDescription: String = "",
val avatarUrl: String = "",

View File

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

View File

@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.pushers
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import java.util.*
import java.util.UUID
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.relation.RelationService
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.state.StateService
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 :
TimelineService,
SendService,
DraftService,
ReadService,
MembershipService,
StateService,

View File

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

View File

@ -20,7 +20,6 @@ import android.util.Patterns
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
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.session.events.model.Event
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.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import java.util.*
/**
* Parameter to create a room, with facilities functions to configure it
@ -133,7 +131,7 @@ class CreateRoomParams {
)
if (null == initialStates) {
initialStates = Arrays.asList<Event>(algoEvent)
initialStates = mutableListOf(algoEvent)
} else {
initialStates!!.add(algoEvent)
}
@ -166,7 +164,7 @@ class CreateRoomParams {
content = contentMap.toContent())
if (null == initialStates) {
initialStates = Arrays.asList<Event>(historyVisibilityEvent)
initialStates = mutableListOf(historyVisibilityEvent)
} else {
initialStates!!.add(historyVisibilityEvent)
}
@ -220,7 +218,7 @@ class CreateRoomParams {
* @param ids the participant ids to add.
*/
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
credentials: Credentials,
userId: String,
ids: List<String>) {
for (id in ids) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
@ -234,7 +232,7 @@ class CreateRoomParams {
invite3pids!!.add(pid)
} else if (isUserId(id)) {
// do not invite oneself
if (credentials.userId != id) {
if (userId != id) {
if (null == invitedUserIds) {
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.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.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
@ -92,6 +93,13 @@ data class TimelineEvent(
*/
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
*/
@ -99,6 +107,20 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSu
?: 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? {
val originalContent = root.getClearContent().toModel<MessageContent>() ?: return 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 KILLING : SyncState()
object KILLED : SyncState()
object NO_NETWORK : SyncState()
}

View File

@ -17,10 +17,12 @@
package im.vector.matrix.android.internal.auth
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.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
@ -29,6 +31,13 @@ import retrofit2.http.POST
*/
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.
* 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.internal.auth.db.AuthRealmModule
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 io.realm.RealmConfiguration
import java.io.File
@ -33,16 +33,21 @@ internal abstract class AuthModule {
@Module
companion object {
private const val DB_ALIAS = "matrix-sdk-auth"
@JvmStatic
@Provides
@AuthDatabase
fun providesRealmConfiguration(context: Context): RealmConfiguration {
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
val old = File(context.filesDir, "matrix-sdk-auth")
if (old.exists()) {
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
}
return RealmConfiguration.Builder()
.configureEncryption("matrix-sdk-auth", context)
.apply {
realmKeysUtils.configureEncryption(this, DB_ALIAS)
}
.name("matrix-sdk-auth.realm")
.modules(AuthRealmModule())
.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.util.Cancelable
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.ThreePidMedium
import im.vector.matrix.android.internal.di.Unauthenticated
@ -62,11 +63,20 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
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,
login: String,
password: String,
callback: MatrixCallback<Session>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = runCatching {
authenticate(homeServerConnectionConfig, login, password)
@ -74,7 +84,14 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
sessionOrFailure.foldToCallback(callback)
}
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,
@ -95,6 +112,12 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
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 {
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)

View File

@ -30,4 +30,12 @@ data class InteractiveAuthenticationFlow(
@Json(name = "stages")
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
@JsonClass(generateAdapter = true)
internal data class LoginFlowResponse(
data class LoginFlowResponse(
@Json(name = "flows")
val flows: List<InteractiveAuthenticationFlow>
)

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto
import android.content.Context
import dagger.Binds
import dagger.Module
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.RealmCryptoStoreModule
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.UserCacheDirectory
import im.vector.matrix.android.internal.di.UserMd5
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.RealmClearCacheTask
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration
import retrofit2.Retrofit
import java.io.File
@ -45,17 +45,20 @@ internal abstract class CryptoModule {
@Module
companion object {
internal const val DB_ALIAS_PREFIX = "crypto_module_"
@JvmStatic
@Provides
@CryptoDatabase
@SessionScope
fun providesRealmConfiguration(context: Context, credentials: Credentials): RealmConfiguration {
val userIDHash = credentials.userId.md5()
fun providesRealmConfiguration(@UserCacheDirectory directory: File,
@UserMd5 userMd5: String,
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(File(context.filesDir, userIDHash))
.configureEncryption("crypto_module_$userIDHash", context)
.directory(directory)
.apply {
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
}
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
@ -105,7 +108,7 @@ internal abstract class CryptoModule {
}
@Binds
abstract fun bindCryptoService(cryptoManager: CryptoManager): CryptoService
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
@Binds
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.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
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.
*/
@SessionScope
internal class CryptoManager @Inject constructor(
internal class DefaultCryptoService @Inject constructor(
// Olm Manager
private val olmManager: OlmManager,
// The credentials,
@ -135,7 +133,6 @@ internal class CryptoManager @Inject constructor(
private val setDeviceNameTask: SetDeviceNameTask,
private val uploadKeysTask: UploadKeysTask,
private val loadRoomMembersTask: LoadRoomMembersTask,
@CryptoDatabase private val clearCryptoDataTask: ClearCacheTask,
private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
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) {
roomDecryptorProvider.addNewSessionListener(newSessionListener)
}
@ -1067,6 +1056,6 @@ internal class CryptoManager @Inject constructor(
* ========================================================================================== */
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
import im.vector.matrix.android.api.auth.data.Credentials
import java.util.*
import javax.inject.Inject
internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
private val olmDevice: MXOlmDevice) {
private val olmDevice: MXOlmDevice) {
/**
* Sign Object

View File

@ -27,14 +27,14 @@ import java.util.*
import javax.inject.Inject
internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val olmDevice: MXOlmDevice,
private val cryptoStore: IMXCryptoStore,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) {
private val cryptoStore: IMXCryptoStore,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) {
/**
* Try to make sure we have established olm sessions for the given users.
* @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")
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()

View File

@ -16,15 +16,15 @@
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.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.UserId
import timber.log.Timber
import javax.inject.Inject
internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore,
private val credentials: Credentials,
private val keysBackup: KeysBackup) {
@UserId private val userId: String,
private val keysBackup: KeysBackup) {
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
val device = cryptoStore.getUserDevice(deviceId, userId)
@ -39,7 +39,7 @@ internal class SetDeviceVerificationAction @Inject constructor(private val crypt
device.verified = verificationStatus
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,
// check the key backup status, since whether or not we use this depends on
// whether it has a signature from a verified device

View File

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

View File

@ -16,7 +16,6 @@
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.MXOlmDevice
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.store.IMXCryptoStore
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 javax.inject.Inject
internal class MXMegolmDecryptionFactory @Inject constructor(private val credentials: Credentials,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
fun create(): MXMegolmDecryption {
return MXMegolmDecryption(
credentials,
userId,
olmDevice,
deviceListManager,
outgoingRoomKeyRequestManager,

View File

@ -17,7 +17,6 @@
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.events.model.Event
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.util.convertFromUTF8
import timber.log.Timber
import java.util.*
internal class MXOlmDecryption(
// The olm device interface
private val olmDevice: MXOlmDevice,
// the matrix credentials
private val credentials: Credentials)
// the matrix userId
private val userId: String)
: IMXDecrypting {
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
@ -98,9 +96,9 @@ internal class MXOlmDecryption(
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
}
if (olmPayloadContent.recipient != credentials.userId) {
if (olmPayloadContent.recipient != userId) {
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,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
}
@ -158,33 +156,14 @@ internal class MXOlmDecryption(
* @return payload, if decrypted successfully.
*/
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
val sessionIdsSet = olmDevice.getSessionIds(theirDeviceIdentityKey)
val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
val sessionIds: List<String>
if (null == sessionIdsSet) {
sessionIds = ArrayList()
} else {
sessionIds = ArrayList(sessionIdsSet)
}
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
val messageBody = message["body"] as? String ?: return null
val messageType = when (val typeAsVoid = message["type"]) {
is Double -> typeAsVoid.toInt()
is Int -> typeAsVoid
is Long -> typeAsVoid.toInt()
else -> return null
}
// Try each session in turn

View File

@ -16,16 +16,16 @@
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.di.UserId
import javax.inject.Inject
internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice,
private val credentials: Credentials) {
@UserId private val userId: String) {
fun create(): MXOlmDecryption {
return MXOlmDecryption(
olmDevice,
credentials)
userId)
}
}

View File

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

View File

@ -66,9 +66,8 @@ import org.matrix.olm.OlmPkEncryption
import org.matrix.olm.OlmPkMessage
import timber.log.Timber
import java.security.InvalidParameterException
import java.util.*
import javax.inject.Inject
import kotlin.collections.HashMap
import kotlin.random.Random
/**
* 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.
private var backupOlmPkEncryption: OlmPkEncryption? = null
private val random = Random()
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = 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
// different clients hitting the server all at the same time when a
// 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)
}
@ -1307,7 +1304,7 @@ internal class KeysBackup @Inject constructor(
// Make the request
storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)){
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) {
this.callback = sendingRequestCallback
}
.executeBy(taskExecutor)
@ -1405,7 +1402,7 @@ internal class KeysBackup @Inject constructor(
companion object {
// 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.
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 im.vector.matrix.android.api.listeners.ProgressListener
import timber.log.Timber
import java.util.*
import java.util.UUID
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
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.KeysBackupStateListener
import timber.log.Timber
import java.util.*
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.internal.crypto.model.rest.DeviceKeys
import java.io.Serializable
import java.util.*
@JsonClass(generateAdapter = true)
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 timber.log.Timber
import java.util.*
data class MXKey(
/**
@ -46,11 +45,7 @@ data class MXKey(
* @return the signed data map
*/
fun signalableJSONDictionary(): Map<String, Any> {
val map = HashMap<String, Any>()
map["key"] = value
return map
return mapOf("key" to value)
}
/**

View File

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

View File

@ -16,11 +16,11 @@
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.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
@ -34,7 +34,7 @@ internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserP
}
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(private val cryptoApi: CryptoApi,
private val credentials: Credentials)
@UserId private val userId: String)
: DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
@ -45,7 +45,7 @@ internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(priva
.apply {
type = LoginFlowTypes.PASSWORD
session = params.authSession
user = credentials.userId
user = userId
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.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import java.util.*
import javax.inject.Inject
import kotlin.random.Random
internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
data class Params(
@ -45,7 +45,7 @@ internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi
return executeRequest {
apiCall = cryptoApi.sendToDevice(
params.eventType,
params.transactionId ?: Random().nextInt(Integer.MAX_VALUE).toString(),
params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
sendToDeviceBody
)
}

View File

@ -44,7 +44,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.*
import java.util.UUID
import javax.inject.Inject
import kotlin.collections.HashMap
@ -161,7 +161,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
cancelTransaction(
startReq.transactionID!!,
otherUserId!!,
startReq?.fromDevice ?: event.getSenderKey()!!,
startReq.fromDevice ?: event.getSenderKey()!!,
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
*/
private fun createUniqueIDForTransaction(userId: String, deviceID: String): String {
val buff = StringBuffer()
buff
.append(credentials.userId).append("|")
.append(credentials.deviceId).append("|")
.append(userId).append("|")
.append(deviceID).append("|")
.append(UUID.randomUUID().toString())
return buff.toString()
return buildString {
append(credentials.userId).append("|")
append(credentials.deviceId).append("|")
append(userId).append("|")
append(deviceID).append("|")
append(UUID.randomUUID().toString())
}
}

View File

@ -17,10 +17,12 @@ package im.vector.matrix.android.internal.database
import android.content.Context
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 timber.log.Timber
import java.security.SecureRandom
import javax.inject.Inject
/**
* 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
* key is encrypted with the public RSA key and stored with the encrypted key in the shared pref
*/
private object RealmKeysUtils {
private const val ENCRYPTED_KEY_PREFIX = "REALM_ENCRYPTED_KEY"
internal class RealmKeysUtils @Inject constructor(context: Context,
private val secretStoringUtils: SecretStoringUtils) {
private val rng = SecureRandom()
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE)
private fun generateKeyForRealm(): ByteArray {
val keyForRealm = ByteArray(RealmConfiguration.KEY_LENGTH)
rng.nextBytes(keyForRealm)
@ -49,8 +52,7 @@ private object RealmKeysUtils {
/**
* Check if there is already a key for this alias
*/
fun hasKeyForDatabase(alias: String, context: Context): Boolean {
val sharedPreferences = getSharedPreferences(context)
private fun hasKeyForDatabase(alias: String): Boolean {
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
* 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 encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
val toStore = SecretStoringUtils.securelyStoreString(encodedKey, alias, context)
val sharedPreferences = getSharedPreferences(context)
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
sharedPreferences
.edit()
.putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING))
@ -77,30 +78,43 @@ private object RealmKeysUtils {
* Retrieves the key for this database
* throws if something goes wrong
*/
fun extractKeyForDatabase(alias: String, context: Context): ByteArray {
val sharedPreferences = getSharedPreferences(context)
private fun extractKeyForDatabase(alias: String): ByteArray {
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
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)
}
private fun getSharedPreferences(context: Context) =
context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE)
}
fun RealmConfiguration.Builder.configureEncryption(alias: String, context: Context): RealmConfiguration.Builder {
if (RealmKeysUtils.hasKeyForDatabase(alias, context)) {
Timber.i("Found key for alias:$alias")
RealmKeysUtils.extractKeyForDatabase(alias, context).also {
this.encryptionKey(it)
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
val key = if (hasKeyForDatabase(alias)) {
Timber.i("Found key for alias:$alias")
extractKeyForDatabase(alias)
} else {
Timber.i("Create key for DB alias:$alias")
createAndSaveKeyForDatabase(alias)
}
} else {
Timber.i("Create key for DB alias:$alias")
RealmKeysUtils.createAndSaveKeyForDatabase(alias, context).also {
this.encryptionKey(it)
if (BuildConfig.LOG_PRIVATE_DATA) {
val log = key.joinToString("") { "%02x".format(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 {
fun map(roomSummaryEntity: GroupSummaryEntity): GroupSummary {
fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary {
return GroupSummary(
roomSummaryEntity.groupId,
roomSummaryEntity.displayName,
roomSummaryEntity.shortDescription,
roomSummaryEntity.avatarUrl,
roomSummaryEntity.roomIds.toList(),
roomSummaryEntity.userIds.toList()
groupSummaryEntity.groupId,
groupSummaryEntity.membership,
groupSummaryEntity.displayName,
groupSummaryEntity.shortDescription,
groupSummaryEntity.avatarUrl,
groupSummaryEntity.roomIds.toList(),
groupSummaryEntity.userIds.toList()
)
}
}

View File

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

View File

@ -26,8 +26,8 @@ import java.util.*
import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor(
val cryptoService: CryptoService,
val timelineEventMapper: TimelineEventMapper
private val cryptoService: CryptoService,
private val timelineEventMapper: TimelineEventMapper
) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
@ -35,7 +35,7 @@ internal class RoomSummaryMapper @Inject constructor(
RoomTag(it.tagName, it.tagOrder)
}
val latestEvent = roomSummaryEntity.latestEvent?.let {
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it)
}
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
@ -43,29 +43,32 @@ internal class RoomSummaryMapper @Inject constructor(
//for now decrypt sync
try {
val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString())
latestEvent.root.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
latestEvent.root.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
} catch (e: MXCryptoError) {
}
}
return RoomSummary(
roomId = roomSummaryEntity.roomId,
displayName = roomSummaryEntity.displayName ?: "",
topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect,
latestEvent = latestEvent,
latestPreviewableEvent = latestEvent,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount,
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
tags = tags,
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.annotations.PrimaryKey
internal open class GroupEntity(@PrimaryKey var groupId: String = ""
) : RealmObject() {
/**
* This class is used to store group info (groupId and membership) from the sync response.
* 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
var membership: Membership

View File

@ -16,18 +16,28 @@
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.RealmObject
import io.realm.annotations.PrimaryKey
internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "",
var displayName: String = "",
var shortDescription: String = "",
var avatarUrl: String = "",
var roomIds: RealmList<String> = RealmList(),
var userIds: RealmList<String> = RealmList()
var displayName: String = "",
var shortDescription: String = "",
var avatarUrl: String = "",
var roomIds: RealmList<String> = RealmList(),
var userIds: RealmList<String> = RealmList()
) : RealmObject() {
private var membershipStr: String = Membership.NONE.name
var membership: Membership
get() {
return Membership.valueOf(membershipStr)
}
set(value) {
membershipStr = value.name
}
companion object
}

View File

@ -15,17 +15,24 @@
*/
package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.pushrules.RuleKind
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Index
internal open class PushRulesEntity(
@Index var userId: String = "",
var scope: String = "",
// "content", etc.
var rulesetKey: String = "",
var pushRules: RealmList<PushRuleEntity> = RealmList()
) : RealmObject() {
private var kindStr: String = RuleKind.CONTENT.name
var kind: RuleKind
get() {
return RuleKind.valueOf(kindStr)
}
set(value) {
kindStr = value.name
}
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 io.realm.RealmObject
import io.realm.annotations.Index
//TODO
// 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 io.realm.Realm.executeTransaction(Realm.java:1493)
internal open class PusherEntity(
@Index var userId: String = "",
var pushKey: String = "",
var kind: String? = null,
var appId: String = "",

View File

@ -26,7 +26,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "",
var avatarUrl: String? = "",
var topic: String? = "",
var latestEvent: TimelineEventEntity? = null,
var latestPreviewableEvent: TimelineEventEntity? = null,
var heroes: RealmList<String> = RealmList(),
var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0,
@ -35,7 +35,9 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var otherMemberIds: RealmList<String> = RealmList(),
var notificationCount: 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() {
private var membershipStr: String = Membership.NONE.name

View File

@ -43,6 +43,8 @@ import io.realm.annotations.RealmModule
PushConditionEntity::class,
PusherEntity::class,
PusherDataEntity::class,
ReadReceiptsSummaryEntity::class
ReadReceiptsSummaryEntity::class,
UserDraftsEntity::class,
DraftEntity::class
])
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.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>()
.equalTo(GroupEntityFields.GROUP_ID, roomId)
.equalTo(GroupEntityFields.GROUP_ID, groupId)
}
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
}
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
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.PushRulesEntityFields
import im.vector.matrix.android.internal.database.model.PusherEntity
@ -24,10 +25,8 @@ import io.realm.RealmQuery
import io.realm.kotlin.where
internal fun PusherEntity.Companion.where(realm: Realm,
userId: String,
pushKey: String? = null): RealmQuery<PusherEntity> {
return realm.where<PusherEntity>()
.equalTo(PusherEntityFields.USER_ID, userId)
.apply {
if (pushKey != null) {
equalTo(PusherEntityFields.PUSH_KEY, pushKey)
@ -36,11 +35,9 @@ internal fun PusherEntity.Companion.where(realm: Realm,
}
internal fun PushRulesEntity.Companion.where(realm: Realm,
userId: String,
scope: String,
ruleSetKey: String): RealmQuery<PushRulesEntity> {
kind: RuleKind): RealmQuery<PushRulesEntity> {
return realm.where<PushRulesEntity>()
.equalTo(PushRulesEntityFields.USER_ID, userId)
.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
/**
* Use the annotation @MatrixScope to annotate classes we want the SDK to instantiate only once
*/
@Scope
@MustBeDocumented
@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.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataFallback
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataPushRules
object MoshiProvider {
@ -31,6 +32,7 @@ object MoshiProvider {
.add(UriMoshiAdapter())
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java)
.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)
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)

View File

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

View File

@ -18,10 +18,13 @@ package im.vector.matrix.android.internal.network
import com.squareup.moshi.JsonDataException
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.MatrixError
import im.vector.matrix.android.internal.di.MoshiProvider
import kotlinx.coroutines.CancellationException
import okhttp3.ResponseBody
import org.greenrobot.eventbus.EventBus
import retrofit2.Call
import timber.log.Timber
import java.io.IOException
@ -47,6 +50,7 @@ internal class Request<DATA> {
is IOException -> Failure.NetworkConnection(exception)
is Failure.ServerError,
is Failure.OtherServerError -> exception
is CancellationException -> Failure.Cancelled(exception)
else -> Failure.Unknown(exception)
}
}
@ -65,6 +69,11 @@ internal class Request<DATA> {
val matrixError = matrixErrorAdapter.fromJson(errorBodyStr)
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)
}
} catch (ex: JsonDataException) {

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@
*/
package im.vector.matrix.android.internal.session
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import im.vector.matrix.android.api.session.InitialSyncProgressService
@ -25,31 +26,33 @@ import javax.inject.Inject
@SessionScope
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
}
fun startTask(nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) {
fun startTask(@StringRes nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) {
// Create a rootTask, or add a child to the leaf
if (rootTask == null) {
rootTask = TaskInfo(nameRes, totalProgress)
} else {
val currentLeaf = rootTask!!.leaf()
val newTask = TaskInfo(nameRes, totalProgress)
newTask.parent = currentLeaf
newTask.offset = currentLeaf.currentProgress
val newTask = TaskInfo(nameRes,
totalProgress,
currentLeaf,
parentWeight)
currentLeaf.child = newTask
newTask.parentWeight = parentWeight
}
reportProgress(0)
}
fun reportProgress(progress: Int) {
rootTask?.leaf()?.incrementProgress(progress)
rootTask?.leaf()?.setProgress(progress)
}
fun endTask(nameRes: Int) {
@ -58,7 +61,7 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
//close it
val parent = endedTask.parent
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) {
status.postValue(null)
@ -71,14 +74,17 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
}
inner class TaskInfo(var nameRes: Int,
var totalProgress: Int) {
var parent: TaskInfo? = null
private inner class TaskInfo(@StringRes var nameRes: Int,
var totalProgress: Int,
var parent: TaskInfo? = null,
var parentWeight: Float = 1f,
var offset: Int = parent?.currentProgress ?: 0) {
var child: TaskInfo? = null
var parentWeight: Float = 1f
var currentProgress: Int = 0
var offset: Int = 0
/**
* Get the further child
*/
fun leaf(): TaskInfo {
var last = this
while (last.child != null) {
@ -87,26 +93,27 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
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
// val newProgress = Math.min(currentProgress + progress, totalProgress)
parent?.let {
val parentProgress = (currentProgress * parentWeight).toInt()
it.incrementProgress(offset + parentProgress)
}
if (parent == null) {
Timber.e("--- ${leaf().nameRes}: ${currentProgress}")
it.setProgress(offset + parentProgress)
} ?: run {
Timber.e("--- ${leaf().nameRes}: $currentProgress")
status.postValue(
InitialSyncProgressService.Status(leaf().nameRes, currentProgress)
)
}
}
}
}
inline fun <T> reportSubtask(reporter: DefaultInitialSyncProgressService?,
nameRes: Int,
@StringRes nameRes: Int,
totalProgress: Int,
parentWeight: Float = 1f,
block: () -> T): T {
@ -121,11 +128,11 @@ inline fun <K, V, R> Map<out K, V>.mapWithProgress(reporter: DefaultInitialSyncP
taskId: Int,
weight: Float,
transform: (Map.Entry<K, V>) -> R): List<R> {
val total = count()
val total = count().toFloat()
var current = 0
reporter?.startTask(taskId, 100, weight)
return this.map {
reporter?.reportProgress((current / total.toFloat() * 100).toInt())
return map {
reporter?.reportProgress((current / total * 100).toInt())
current++
transform.invoke(it)
}.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.room.RoomDirectoryService
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.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.util.MatrixCallbackDelegate
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider
@ -63,8 +62,9 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val signOutService: Lazy<SignOutService>,
private val pushRuleService: Lazy<PushRuleService>,
private val pushersService: Lazy<PushersService>,
private val cryptoService: Lazy<CryptoManager>,
private val cryptoService: Lazy<DefaultCryptoService>,
private val fileService: Lazy<FileService>,
private val secureStorageService: Lazy<SecureStorageService>,
private val syncThreadProvider: Provider<SyncThread>,
private val contentUrlResolver: ContentUrlResolver,
private val contentUploadProgressTracker: ContentUploadStateTracker,
@ -75,13 +75,13 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
GroupService by groupService.get(),
UserService by userService.get(),
CryptoService by cryptoService.get(),
CacheService by cacheService.get(),
SignOutService by signOutService.get(),
FilterService by filterService.get(),
PushRuleService by pushRuleService.get(),
PushersService by pushersService.get(),
FileService by fileService.get(),
InitialSyncProgressService by initialSyncProgressService.get() {
InitialSyncProgressService by initialSyncProgressService.get(),
SecureStorageService by secureStorageService.get() {
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>) {
stopSync()
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.session.InitialSyncProgressService
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.configureEncryption
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.di.Authenticated
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.di.*
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
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.room.DefaultRoomFactory
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.prune.EventsPruner
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 io.realm.RealmConfiguration
import okhttp3.OkHttpClient
@ -53,6 +52,7 @@ internal abstract class SessionModule {
@Module
companion object {
internal const val DB_ALIAS_PREFIX = "session_db_"
@JvmStatic
@Provides
@ -67,18 +67,40 @@ internal abstract class SessionModule {
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
@Provides
@SessionDatabase
@SessionScope
fun providesRealmConfiguration(sessionParams: SessionParams, context: Context): RealmConfiguration {
val childPath = sessionParams.credentials.userId.md5()
val directory = File(context.filesDir, childPath)
fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils,
@UserCacheDirectory directory: File,
@UserMd5 userMd5: String): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
.name("disk_store.realm")
.configureEncryption("session_db_$childPath", context)
.apply {
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
}
.modules(SessionRealmModule())
.deleteRealmIfMigrationNeeded()
.build()
@ -101,7 +123,18 @@ internal abstract class SessionModule {
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
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()
}
@ -142,4 +175,7 @@ internal abstract class SessionModule {
@Binds
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>>()
override fun track(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
val listeners = listeners[key] ?: ArrayList()
val listeners = listeners.getOrPut(key) { ArrayList() }
listeners.add(updateListener)
this.listeners[key] = listeners
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
mainHandler.post { updateListener.onUpdate(currentState) }
}

View File

@ -16,7 +16,7 @@
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.task.Task
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 filterRepository: FilterRepository
) : SaveFilterTask {
@ -41,7 +41,7 @@ internal class DefaultSaveFilterTask @Inject constructor(private val sessionPara
override suspend fun execute(params: SaveFilterTask.Params) {
val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry
apiCall = filterAPI.uploadFilter(sessionParams.credentials.userId, params.filter)
apiCall = filterAPI.uploadFilter(userId, params.filter)
}
filterRepository.storeFilterId(params.filter, filterResponse.filterId)
}

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.group
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.query.where
import im.vector.matrix.android.internal.network.executeRequest
@ -64,8 +65,7 @@ internal class DefaultGetGroupDataTask @Inject constructor(
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
val name = groupSummary.profile?.name
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription
?: ""
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: ""
val roomIds = groupRooms.rooms.map { it.roomId }
groupSummaryEntity.roomIds.clear()
@ -74,8 +74,12 @@ internal class DefaultGetGroupDataTask @Inject constructor(
val userIds = groupUsers.users.map { it.userId }
groupSummaryEntity.userIds.clear()
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.WorkManager
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.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.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.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
import javax.inject.Inject
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class GroupSummaryUpdater @Inject constructor(private val context: Context,
private val credentials: Credentials,
@SessionDatabase realmConfiguration: RealmConfiguration)
: RealmLiveEntityObserver<GroupEntity>(realmConfiguration) {
@UserId private val userId: String,
private val monarchy: Monarchy)
: 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) {
val newGroupIds = changeSet.insertions
// `insertions` for new groups and `changes` to handle left groups
val modifiedGroupEntity = (changeSet.insertions + changeSet.changes)
.asSequence()
.mapNotNull { results[it]?.groupId}
.toList()
.mapNotNull { results[it] }
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 sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
@ -61,4 +74,15 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
.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 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.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.session.events.model.Event
import im.vector.matrix.android.api.util.Cancelable
@ -35,53 +36,60 @@ import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class DefaultPushRuleService @Inject constructor(
private val sessionParams: SessionParams,
private val pushRulesTask: GetPushRulesTask,
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
private val taskExecutor: TaskExecutor,
private val monarchy: Monarchy
internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
private val taskExecutor: TaskExecutor,
private val monarchy: Monarchy
) : PushRuleService {
private var listeners = ArrayList<PushRuleService.PushRuleListener>()
override fun fetchPushRules(scope: String) {
pushRulesTask
getPushRulesTask
.configureWith(GetPushRulesTask.Params(scope))
.executeBy(taskExecutor)
}
override fun getPushRules(scope: String): List<PushRule> {
var contentRules: List<PushRule> = emptyList()
var overrideRules: List<PushRule> = emptyList()
var roomRules: List<PushRule> = emptyList()
var senderRules: List<PushRule> = emptyList()
var underrideRules: List<PushRule> = emptyList()
// TODO Create const for ruleSetKey
monarchy.doWithRealm { realm ->
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "content").findFirst()?.let { re ->
contentRules = re.pushRules.map { PushRulesMapper.mapContentRule(it) }
}
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "override").findFirst()?.let { re ->
overrideRules = re.pushRules.map { PushRulesMapper.map(it) }
}
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "room").findFirst()?.let { re ->
roomRules = re.pushRules.map { PushRulesMapper.mapRoomRule(it) }
}
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "sender").findFirst()?.let { re ->
senderRules = re.pushRules.map { PushRulesMapper.mapSenderRule(it) }
}
PushRulesEntity.where(realm, sessionParams.credentials.userId, scope, "underride").findFirst()?.let { re ->
underrideRules = re.pushRules.map { PushRulesMapper.map(it) }
}
PushRulesEntity.where(realm, scope, RuleSetKey.CONTENT)
.findFirst()
?.let { pushRulesEntity ->
contentRules = pushRulesEntity.pushRules.map { PushRulesMapper.mapContentRule(it) }
}
PushRulesEntity.where(realm, scope, RuleSetKey.OVERRIDE)
.findFirst()
?.let { pushRulesEntity ->
overrideRules = pushRulesEntity.pushRules.map { PushRulesMapper.map(it) }
}
PushRulesEntity.where(realm, scope, RuleSetKey.ROOM)
.findFirst()
?.let { pushRulesEntity ->
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
.configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) {
this.callback = callback
@ -114,8 +122,9 @@ internal class DefaultPushRuleService @Inject constructor(
fun dispatchBing(event: Event, rule: PushRule) {
try {
val actionsList = rule.getActions()
listeners.forEach {
it.onMatchRule(event, Action.mapFrom(rule) ?: emptyList())
it.onMatchRule(event, actionsList)
}
} catch (e: Throwable) {
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() {
try {
listeners.forEach {

View File

@ -16,11 +16,11 @@
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.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
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.sync.model.RoomsSyncResponse
import im.vector.matrix.android.internal.task.Task
@ -37,7 +37,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
internal class DefaultProcessEventForPushTask @Inject constructor(
private val defaultPushRuleService: DefaultPushRuleService,
private val roomService: RoomService,
private val sessionParams: SessionParams
@UserId private val userId: String
) : ProcessEventForPushTask {
override suspend fun execute(params: ProcessEventForPushTask.Params) {
@ -68,7 +68,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
else -> false
}
}.filter {
it.senderId != sessionParams.credentials.userId
it.senderId != userId
}
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
" to check for push rules with ${params.rules.size} rules")
@ -78,11 +78,31 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
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()
}
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 ->
val isFullfilled = rule.conditions?.map {
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,8 +28,7 @@ import io.realm.Realm
import javax.inject.Inject
internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> {
data class Params(val userId: String,
val pushKey: String,
data class Params(val pushKey: String,
val pushAppId: String)
}
@ -40,12 +39,12 @@ internal class DefaultRemovePusherTask @Inject constructor(
override suspend fun execute(params: RemovePusherTask.Params) {
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
}
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")
val deleteBody = JsonPusher(
@ -64,7 +63,7 @@ internal class DefaultRemovePusherTask @Inject constructor(
apiCall = pushersAPI.setPusher(deleteBody)
}
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