Compare commits

..

82 Commits

Author SHA1 Message Date
07fca0922b Merge branch 'release/0.5.0' 2019-09-17 14:50:55 +02:00
ba9d119892 Prepare release 0.5.0 2019-09-17 14:50:43 +02:00
4453f0ced9 Merge pull request #560 from vector-im/feature/no_network
Display a "No network" banner when the device has no network
2019-09-17 14:40:42 +02:00
77168bfd6a Merge pull request #558 from vector-im/feature/login_sso
Quick implementation of SSO login - Also handling of magic link
2019-09-17 14:28:04 +02:00
25e9a179d2 SyncThread: Fix issue when network is back and the app was in background: do not restart the thread 2019-09-17 14:26:30 +02:00
73ec0f5a83 NetworkConnectivityChecker: filter onConnected callbacks (several callback if Wifi and LTE is connected)
Also do not use merlinsBeard.isConnected, which return trus even if there is no internet access (ex: with Wifi hotspot)
2019-09-17 14:22:08 +02:00
993fa74252 Cleanup after BillCarsonFr's review 2019-09-17 11:24:37 +02:00
38fc4984fe Display a no network indicator when there is no network: Create a dedicated View 2019-09-17 11:13:00 +02:00
695d8cce00 Display a no network indicator when there is no network (#559) 2019-09-17 10:59:58 +02:00
07e99901e1 SecretStoringUtils -> move to internal package 2019-09-17 10:38:37 +02:00
20f53e9a58 Signout: propose the user to retry in case of error 2019-09-17 10:33:27 +02:00
ced72aff4f Revert change done to save alias for the client 2019-09-17 10:32:09 +02:00
fdaaca49c2 Code quality (bad import) 2019-09-16 19:27:13 +02:00
3485f023b0 All current notifications were dismissed by mistake when the app is launched from the launcher 2019-09-16 19:24:52 +02:00
384dd100e9 Daggerization and Kotlinification of SecretStoringUtils 2019-09-16 19:19:14 +02:00
1ba8a58219 Cleanup SecretStoringUtils, and delete keys when user signs out 2019-09-16 18:29:06 +02:00
c8010561fc Rework on sign out task 2019-09-16 17:45:26 +02:00
1f127335bc Daggerization of RealmKeysUtils 2019-09-16 15:50:56 +02:00
138a210a73 Dagger: Screen component now exposes ActiveSessionHolder instead of Session 2019-09-16 14:43:39 +02:00
ca6bcde82d Re add the remove CurlLoggingInterceptor 2019-09-16 14:43:08 +02:00
6bda437f5d Auto configure homeserver and identity server URLs of LoginActivity with a magic link 2019-09-16 10:58:51 +02:00
3e6b65e174 Handle M_CONSENT_NOT_GIVEN error (#64) 2019-09-13 18:21:56 +02:00
137dcab734 Curl login interceptor now log the AT (on debug mode) 2019-09-13 16:20:19 +02:00
b22b8fba02 Fix the mess up with OnBackPress support on Fragment 2019-09-13 15:55:33 +02:00
3ccdf4a244 Login: some cleanup 2019-09-13 15:35:44 +02:00
5fbd271b1c Login: add SSO support 2019-09-13 15:19:45 +02:00
db8ea0f5e8 Login: check login flow - step 1 2019-09-13 11:08:54 +02:00
a47a3ead1f Login: move login code to the ViewModel 2019-09-13 10:39:22 +02:00
05b2092ffc Login: move existing code to a Fragment, MvRx style 2019-09-13 10:07:55 +02:00
6249a59203 Merge pull request #554 from vector-im/feature/build_number
Fix issue with bad versionCode generated by Buildkite (#553)
2019-09-12 17:24:46 +02:00
618e9a4f52 Fix issue with bad versionCode generated by Buildkite (#553) 2019-09-12 16:17:44 +02:00
f2c8d4ad02 Merge pull request #549 from vector-im/feature/third_party_invite
Fix rendering issue of accepted third party invitation event
2019-09-06 16:36:30 +02:00
be524472ec Merge pull request #546 from vector-im/feature/cleanup
Cleanup
2019-09-06 16:25:08 +02:00
1b82a1a24d Cleanup 2019-09-06 15:52:29 +02:00
cf0b331c3b Handle invite to the current user rendering 2019-09-06 15:48:42 +02:00
2a92a3dc80 Fix rendering issue of accepted third party invitation event 2019-09-06 14:34:52 +02:00
012840abba Progress in initial sync dialog is decreasing for a step and should not (#532) 2019-09-05 18:14:05 +02:00
a5975a099e Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:23:09 +02:00
38da4b9ee5 Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:02:03 +02:00
242e60fcaa Rename CryptoManager to DefaultCryptoService 2019-09-05 16:14:34 +02:00
a23be05cbf Better type 2019-09-05 16:04:41 +02:00
ed39b02924 Avoid using keyword for variable names 2019-09-05 16:04:41 +02:00
fe931b5361 Merge pull request #418 from Dominaezzz/kotlinify-1
Some more kotlinification
2019-09-05 16:02:30 +02:00
90d9cd0587 Merge pull request #416 from Dominaezzz/kt-remove_java_util
Remove most usages of the java.util package
2019-09-05 15:33:03 +02:00
9cedb18921 Merge pull request #538 from vector-im/feature/log_mgmt
Reduce release build log level
2019-09-05 15:24:04 +02:00
e89ba7b87b Update wording 2019-09-05 15:23:38 +02:00
902657c22a Merge pull request #537 from vector-im/feature/fix_crash
Fix crash due to missing informationData (#535)
2019-09-02 15:31:28 +02:00
eec2abf164 Reduce release build log level 2019-09-02 14:33:53 +02:00
6879cc8ca8 Fix crash due to missing informationData (#535) 2019-09-02 14:24:36 +02:00
fd6bbbd3b5 Fix issue with version name (Fixes #533) 2019-08-30 15:57:39 +02:00
0ff0b014a9 Version++ (0.5.0) 2019-08-30 15:07:04 +02:00
a89f0ddd1d Merge branch 'release/0.4.0' 2019-08-30 15:04:43 +02:00
fdc9e84dd5 Merge branch 'release/0.4.0' into develop 2019-08-30 15:04:43 +02:00
58f878fca9 Prepare version 0.4.0 2019-08-30 15:04:28 +02:00
88095e4bd9 Add entry in change file 2019-08-30 14:54:15 +02:00
47d22a3d5e Import translation from Riot and MatrixSDK 2019-08-30 11:21:43 +02:00
28e82cb8ea Merge pull request #531 from vector-im/feature/fix_crash_530
Fix / EmojiCompat not initialized
2019-08-29 17:46:51 +02:00
35817245cb refactoring, code review 2019-08-29 17:27:49 +02:00
75266f42bb Fix / EmojiCompat not initialized 2019-08-29 16:49:22 +02:00
95c4c9ce56 Merge pull request #527 from vector-im/feature/privacy
Privacy: remove log of notifiable event (#519)
2019-08-29 12:16:34 +02:00
ce5570105d Privacy: remove log of notifiable event (#519) 2019-08-29 10:36:45 +02:00
188a9aebfa Merge pull request #525 from vector-im/feature/read_receipt_cleanup
Feature/read receipt cleanup
2019-08-29 10:19:06 +02:00
c95223f5d2 Add long click support on unsupported event 2019-08-28 18:17:37 +02:00
ef0362ba9c Display Read Receipt on unsupported events 2019-08-28 17:31:31 +02:00
ea242f6737 Hide ReadReceipt View when it is not relevant 2019-08-28 17:17:37 +02:00
cbc08d834b Merge pull request #522 from vector-im/feature/fix_e2e_reply
Fix / regression on e2e reply and edit of reply
2019-08-28 10:38:22 +02:00
0ab6b33fb6 Merge branch 'develop' into feature/fix_e2e_reply 2019-08-28 10:38:12 +02:00
1b394527b6 cleaning + code review 2019-08-28 10:22:51 +02:00
a8f1388721 Merge pull request #520 from vector-im/feature/read_receipts_511
Improve read receipt design
2019-08-28 10:17:56 +02:00
166be4e289 Improve read receipt design 2019-08-28 09:56:10 +02:00
b49ccefe63 Merge pull request #521 from vector-im/feature/fix_dome_video_wont_play
Some video won't play
2019-08-28 03:43:35 -04:00
825760d17e Fix / regression on e2e reply and edit of reply 2019-08-27 17:05:04 +02:00
b5af62c3ea Some video won't play
VideoView fails to play some remote uri video on some device. For now video is downloaded locally in internal cache then played. This offers basic support before full media preview implementation
2019-08-27 16:50:02 +02:00
a51d96bf00 Merge pull request #325 from vector-im/feature/non_unicode_reaction
Accept non unicode reactions
2019-08-27 08:10:51 -04:00
7e142d201d Use EmojiCompat to build EmojiSpans from text 2019-08-27 11:06:52 +02:00
2be6058971 accept non unicode reactions 2019-08-27 10:58:21 +02:00
49d73f360e Merge pull request #494 from vector-im/feature/fix_441
Fix text diff removed linebreak
2019-08-27 04:36:03 -04:00
9cd69d1e33 Merge branch 'release/0.3.0' 2019-08-08 16:45:03 +02:00
456908c851 Merge branch 'develop' into kt-remove_java_util 2019-08-06 18:27:39 +01:00
215324a03e Some kotlinification
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-08-06 11:36:39 +01:00
02e342849f Remove most usages of the java.util package
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-21 23:23:56 +01:00
df6080b1da Merge branch 'release/0.2.0' 2019-07-18 17:47:39 +02:00
186 changed files with 5557 additions and 1105 deletions

View File

@ -1,25 +1,41 @@
Changes in RiotX 0.4.0 (2019-XX-XX) Changes in RiotX 0.5.0 (2019-09-17)
===================================================
Features:
- Handle M_CONSENT_NOT_GIVEN error (#64)
- Auto configure homeserver and identity server URLs of LoginActivity with a magic link
Improvements:
- Reduce default release build log level, and lab option to enable more logs.
- Display a no network indicator when there is no network (#559)
Bugfix:
- Fix crash due to missing informationData (#535)
- Progress in initial sync dialog is decreasing for a step and should not (#532)
- Fix rendering issue of accepted third party invitation event
- All current notifications were dismissed by mistake when the app is launched from the launcher
Build:
- Fix issue with version name (#533)
- Fix issue with bad versionCode generated by Buildkite (#553)
Changes in RiotX 0.4.0 (2019-08-30)
=================================================== ===================================================
Features: Features:
- Display read receipts in timeline (#81) - Display read receipts in timeline (#81)
Improvements: Improvements:
- - Reactions: Reinstate the ability to react with non-unicode keys (#307)
Other changes:
-
Bugfix: Bugfix:
- Fix text diff linebreak display (#441) - Fix text diff linebreak display (#441)
- Date change message repeats for each redaction until a normal message (#358) - Date change message repeats for each redaction until a normal message (#358)
- Slide-in reply icon is distorted (#423) - Slide-in reply icon is distorted (#423)
- Regression / e2e replies not encrypted
Translations: - Some video won't play
- - Privacy: remove log of notifiable event (#519)
- Fix crash with EmojiCompat (#530)
Build:
-
Changes in RiotX 0.3.0 (2019-08-08) Changes in RiotX 0.3.0 (2019-08-08)
=================================================== ===================================================

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,22 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.failure
// This data class will be sent to the bus
data class ConsentNotGivenError(
val consentUri: String
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,6 @@ import im.vector.matrix.android.api.session.room.model.PowerLevels
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import java.util.*
/** /**
* Parameter to create a room, with facilities functions to configure it * Parameter to create a room, with facilities functions to configure it
@ -133,7 +132,7 @@ class CreateRoomParams {
) )
if (null == initialStates) { if (null == initialStates) {
initialStates = Arrays.asList<Event>(algoEvent) initialStates = mutableListOf(algoEvent)
} else { } else {
initialStates!!.add(algoEvent) initialStates!!.add(algoEvent)
} }
@ -166,7 +165,7 @@ class CreateRoomParams {
content = contentMap.toContent()) content = contentMap.toContent())
if (null == initialStates) { if (null == initialStates) {
initialStates = Arrays.asList<Event>(historyVisibilityEvent) initialStates = mutableListOf(historyVisibilityEvent)
} else { } else {
initialStates!!.add(historyVisibilityEvent) initialStates!!.add(historyVisibilityEvent)
} }

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.securestorage
import java.io.InputStream
import java.io.OutputStream
interface SecureStorageService {
fun securelyStoreObject(any: Any, keyAlias: String, outputStream: OutputStream)
fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T?
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class LoginFlowResponse( data class LoginFlowResponse(
@Json(name = "flows") @Json(name = "flows")
val flows: List<InteractiveAuthenticationFlow> val flows: List<InteractiveAuthenticationFlow>
) )

View File

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

View File

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

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal class ObjectSigner @Inject constructor(private val credentials: Credentials, internal class ObjectSigner @Inject constructor(private val credentials: Credentials,

View File

@ -31,7 +31,6 @@ import im.vector.matrix.android.internal.crypto.model.event.OlmPayloadContent
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.util.convertFromUTF8 import im.vector.matrix.android.internal.util.convertFromUTF8
import timber.log.Timber import timber.log.Timber
import java.util.*
internal class MXOlmDecryption( internal class MXOlmDecryption(
// The olm device interface // The olm device interface
@ -158,33 +157,14 @@ internal class MXOlmDecryption(
* @return payload, if decrypted successfully. * @return payload, if decrypted successfully.
*/ */
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? { private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
val sessionIdsSet = olmDevice.getSessionIds(theirDeviceIdentityKey) val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
val sessionIds: List<String> val messageBody = message["body"] as? String ?: return null
val messageType = when (val typeAsVoid = message["type"]) {
if (null == sessionIdsSet) { is Double -> typeAsVoid.toInt()
sessionIds = ArrayList() is Int -> typeAsVoid
} else { is Long -> typeAsVoid.toInt()
sessionIds = ArrayList(sessionIdsSet) else -> return null
}
val messageBody = message["body"] as? String
var messageType: Int? = null
val typeAsVoid = message["type"]
if (null != typeAsVoid) {
if (typeAsVoid is Double) {
messageType = typeAsVoid.toInt()
} else if (typeAsVoid is Int) {
messageType = typeAsVoid
} else if (typeAsVoid is Long) {
messageType = typeAsVoid.toInt()
}
}
if (null == messageBody || null == messageType) {
return null
} }
// Try each session in turn // Try each session in turn

View File

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

View File

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

View File

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

View File

@ -20,7 +20,6 @@ import android.os.Handler
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
import timber.log.Timber import timber.log.Timber
import java.util.*
internal class KeysBackupStateManager(private val uiHandler: Handler) { internal class KeysBackupStateManager(private val uiHandler: Handler) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import java.util.* import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor( internal class RoomSummaryMapper @Inject constructor(

View File

@ -0,0 +1,23 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.di
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class UserCacheDirectory

View File

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

View File

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

View File

@ -0,0 +1,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 UserMd5

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import java.util.* import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject

View File

@ -69,15 +69,11 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
.also { .also {
saveLocalEcho(it) saveLocalEcho(it)
} }
val sendRelationWork = createSendRelationWork(event) val sendRelationWork = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork) TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork)
return CancelableWork(context, sendRelationWork.id) return CancelableWork(context, sendRelationWork.id)
} }
private fun createSendRelationWork(event: Event): OneTimeWorkRequest {
return createSendEventWork(event)
}
override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ { override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ {
val params = FindReactionEventForUndoTask.Params( val params = FindReactionEventForUndoTask.Params(
@ -134,42 +130,42 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
.also { .also {
saveLocalEcho(it) saveLocalEcho(it)
} }
if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
return CancelableWork(context, encryptWork.id) CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
return CancelableWork(context, workRequest.id) CancelableWork(context, workRequest.id)
} }
} }
override fun editReply(replyToEdit: TimelineEvent, override fun editReply(replyToEdit: TimelineEvent,
originalEvent: TimelineEvent, originalTimelineEvent: TimelineEvent,
newBodyText: String, newBodyText: String,
compatibilityBodyText: String): Cancelable { compatibilityBodyText: String): Cancelable {
val event = eventFactory val event = eventFactory
.createReplaceTextOfReply(roomId, .createReplaceTextOfReply(roomId,
replyToEdit, replyToEdit,
originalEvent, originalTimelineEvent,
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText) newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
.also { .also {
saveLocalEcho(it) saveLocalEcho(it)
} }
if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
return CancelableWork(context, encryptWork.id) CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
return CancelableWork(context, workRequest.id) CancelableWork(context, workRequest.id)
} }
} }
@ -187,16 +183,16 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
saveLocalEcho(it) saveLocalEcho(it)
} ?: return null } ?: return null
if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
return CancelableWork(context, encryptWork.id) CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
return CancelableWork(context, workRequest.id) CancelableWork(context, workRequest.id)
} }
} }
@ -208,10 +204,10 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true) return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
} }
private fun createSendEventWork(event: Event): OneTimeWorkRequest { private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, true) return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
} }
override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> { override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> {
@ -220,7 +216,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
} }
return Transformations.map(liveEntity) { realmResults -> return Transformations.map(liveEntity) { realmResults ->
realmResults.firstOrNull()?.asDomain() realmResults.firstOrNull()?.asDomain()
?: EventAnnotationsSummary(eventId, emptyList(), null) ?: EventAnnotationsSummary(eventId, emptyList(), null)
} }
} }
@ -233,7 +229,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
private fun saveLocalEcho(event: Event) { private fun saveLocalEcho(event: Event) {
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
?: return@writeAsync ?: return@writeAsync
roomEntity.addSendingEvent(event) roomEntity.addSendingEvent(event)
} }
} }

View File

@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.util.StringProvider import im.vector.matrix.android.internal.util.StringProvider
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
import java.util.* import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -304,17 +304,22 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
} }
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String { private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
val lines = body.text.split("\n") return buildString {
val replyFallback = StringBuffer("> <$originalSenderId>") append("> <")
lines.forEachIndexed { index, s -> append(originalSenderId)
if (index == 0) { append(">")
replyFallback.append(" $s")
} else { val lines = body.text.split("\n")
replyFallback.append("\n> $s") lines.forEachIndexed { index, s ->
if (index == 0) {
append(" $s")
} else {
append("\n> $s")
}
} }
append("\n\n")
append(newBodyText)
} }
replyFallback.append("\n\n").append(newBodyText)
return replyFallback.toString()
} }
/** /**

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.session.securestorage
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject
internal class DefaultSecureStorageService @Inject constructor(private val secretStoringUtils: SecretStoringUtils) : SecureStorageService {
override fun securelyStoreObject(any: Any, keyAlias: String, outputStream: OutputStream) {
secretStoringUtils.securelyStoreObject(any, keyAlias, outputStream)
}
override fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T? {
return secretStoringUtils.loadSecureSecret(inputStream, keyAlias)
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.util package im.vector.matrix.android.internal.session.securestorage
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
@ -22,10 +22,12 @@ import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import timber.log.Timber
import java.io.* import java.io.*
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.KeyStore import java.security.KeyStore
import java.security.KeyStoreException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
import javax.crypto.* import javax.crypto.*
@ -33,6 +35,7 @@ import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import javax.inject.Inject
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
@ -65,22 +68,24 @@ import javax.security.auth.x500.X500Principal
* val kDecripted = SecretStoringUtils.loadSecureSecret(KEncrypted!!, "myAlias", context) * val kDecripted = SecretStoringUtils.loadSecureSecret(KEncrypted!!, "myAlias", context)
* </code> * </code>
* *
* You can also just use this utility to store a secret key, and use any encryption algorthim that you want. * You can also just use this utility to store a secret key, and use any encryption algorithm that you want.
* *
* Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you * Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you
* add a pin or change the schema); So you might and with a useless pile of bytes. * add a pin or change the schema); So you might and with a useless pile of bytes.
*/ */
object SecretStoringUtils { internal class SecretStoringUtils @Inject constructor(private val context: Context) {
private const val ANDROID_KEY_STORE = "AndroidKeyStore" companion object {
private const val AES_MODE = "AES/GCM/NoPadding"; private const val ANDROID_KEY_STORE = "AndroidKeyStore"
private const val RSA_MODE = "RSA/ECB/PKCS1Padding" private const val AES_MODE = "AES/GCM/NoPadding"
private const val RSA_MODE = "RSA/ECB/PKCS1Padding"
const val FORMAT_API_M: Byte = 0 private const val FORMAT_API_M: Byte = 0
const val FORMAT_1: Byte = 1 private const val FORMAT_1: Byte = 1
const val FORMAT_2: Byte = 2 private const val FORMAT_2: Byte = 2
}
val keyStore: KeyStore by lazy { private val keyStore: KeyStore by lazy {
KeyStore.getInstance(ANDROID_KEY_STORE).apply { KeyStore.getInstance(ANDROID_KEY_STORE).apply {
load(null) load(null)
} }
@ -88,24 +93,30 @@ object SecretStoringUtils {
private val secureRandom = SecureRandom() private val secureRandom = SecureRandom()
fun safeDeleteKey(keyAlias: String) {
try {
keyStore.deleteEntry(keyAlias)
} catch (e: KeyStoreException) {
Timber.e(e)
}
}
/** /**
* Encrypt the given secret using the android Keystore. * Encrypt the given secret using the android Keystore.
* On android >= M, will directly use the keystore to generate a symetric key * On android >= M, will directly use the keystore to generate a symmetric key
* On KitKat >= KitKat and <M, as symetric key gen is not available, will use an asymetric key generated * On android >= KitKat and <M, as symmetric key gen is not available, will use an symmetric key generated
* in the keystore to encrypted a random symetric key. The encrypted symetric key is returned * in the keystore to encrypted a random symmetric key. The encrypted symmetric key is returned
* in the bytearray (in can be stored anywhere, it is encrypted) * in the bytearray (in can be stored anywhere, it is encrypted)
* On older version a key in generated from alias with random salt. * On older version a key in generated from alias with random salt.
* *
* The secret is encrypted using the following method: AES/GCM/NoPadding * The secret is encrypted using the following method: AES/GCM/NoPadding
*/ */
@Throws(Exception::class) @Throws(Exception::class)
fun securelyStoreString(secret: String, keyAlias: String, context: Context): ByteArray? { fun securelyStoreString(secret: String, keyAlias: String): ByteArray? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return when {
return encryptStringM(secret, keyAlias) Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> encryptStringK(secret, keyAlias)
return encryptStringJ(secret, keyAlias, context) else -> encryptForOldDevicesNotGood(secret, keyAlias)
} else {
return encryptForOldDevicesNotGood(secret, keyAlias)
} }
} }
@ -113,39 +124,33 @@ object SecretStoringUtils {
* Decrypt a secret that was encrypted by #securelyStoreString() * Decrypt a secret that was encrypted by #securelyStoreString()
*/ */
@Throws(Exception::class) @Throws(Exception::class)
fun loadSecureSecret(encrypted: ByteArray, keyAlias: String, context: Context): String? { fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return when {
return decryptStringM(encrypted, keyAlias) Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> decryptStringM(encrypted, keyAlias)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> decryptStringK(encrypted, keyAlias)
return decryptStringJ(encrypted, keyAlias, context) else -> decryptForOldDevicesNotGood(encrypted, keyAlias)
} else {
return decryptForOldDevicesNotGood(encrypted, keyAlias)
} }
} }
fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream, context: Context) { fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { when {
saveSecureObjectM(keyAlias, output, any) Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> saveSecureObjectK(keyAlias, output, any)
return saveSecureObjectK(keyAlias, output, any, context) else -> saveSecureObjectOldNotGood(keyAlias, output, any)
} else {
return saveSecureObjectOldNotGood(keyAlias, output, any)
} }
} }
fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String, context: Context): T? { fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return when {
return loadSecureObjectM(keyAlias, inputStream) Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> loadSecureObjectM(keyAlias, inputStream)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> loadSecureObjectK(keyAlias, inputStream)
return loadSecureObjectK(keyAlias, inputStream, context) else -> loadSecureObjectOldNotGood(keyAlias, inputStream)
} else {
return loadSecureObjectOldNotGood(keyAlias, inputStream)
} }
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
fun getOrGenerateSymmetricKeyForAlias(alias: String): SecretKey { private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey {
val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry)
?.secretKey ?.secretKey
if (secretKeyEntry == null) { if (secretKeyEntry == null) {
@ -163,7 +168,6 @@ object SecretStoringUtils {
return secretKeyEntry return secretKeyEntry
} }
/* /*
Symetric Key Generation is only available in M, so before M the idea is to: Symetric Key Generation is only available in M, so before M the idea is to:
- Generate a pair of RSA keys; - Generate a pair of RSA keys;
@ -172,8 +176,8 @@ object SecretStoringUtils {
- Store the encrypted AES - Store the encrypted AES
Generate a key pair for encryption Generate a key pair for encryption
*/ */
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(Build.VERSION_CODES.KITKAT)
fun getOrGenerateKeyPairForAlias(alias: String, context: Context): KeyStore.PrivateKeyEntry { fun getOrGenerateKeyPairForAlias(alias: String): KeyStore.PrivateKeyEntry {
val privateKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.PrivateKeyEntry) val privateKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.PrivateKeyEntry)
if (privateKeyEntry != null) return privateKeyEntry if (privateKeyEntry != null) return privateKeyEntry
@ -201,7 +205,7 @@ object SecretStoringUtils {
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
fun encryptStringM(text: String, keyAlias: String): ByteArray? { fun encryptStringM(text: String, keyAlias: String): ByteArray? {
val secretKey = getOrGenerateSymmetricKeyForAlias(keyAlias) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, secretKey) cipher.init(Cipher.ENCRYPT_MODE, secretKey)
@ -212,10 +216,10 @@ object SecretStoringUtils {
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
fun decryptStringM(encryptedChunk: ByteArray, keyAlias: String): String { private fun decryptStringM(encryptedChunk: ByteArray, keyAlias: String): String {
val (iv, encryptedText) = formatMExtract(ByteArrayInputStream(encryptedChunk)) val (iv, encryptedText) = formatMExtract(ByteArrayInputStream(encryptedChunk))
val secretKey = getOrGenerateSymmetricKeyForAlias(keyAlias) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
val spec = GCMParameterSpec(128, iv) val spec = GCMParameterSpec(128, iv)
@ -224,15 +228,15 @@ object SecretStoringUtils {
return String(cipher.doFinal(encryptedText), Charsets.UTF_8) return String(cipher.doFinal(encryptedText), Charsets.UTF_8)
} }
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(Build.VERSION_CODES.KITKAT)
fun encryptStringJ(text: String, keyAlias: String, context: Context): ByteArray? { private fun encryptStringK(text: String, keyAlias: String): ByteArray? {
//we generate a random symetric key //we generate a random symetric key
val key = ByteArray(16) val key = ByteArray(16)
secureRandom.nextBytes(key) secureRandom.nextBytes(key)
val sKey = SecretKeySpec(key, "AES") val sKey = SecretKeySpec(key, "AES")
//we encrypt this key thanks to the key store //we encrypt this key thanks to the key store
val encryptedKey = rsaEncrypt(keyAlias, key, context) val encryptedKey = rsaEncrypt(keyAlias, key)
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, sKey) cipher.init(Cipher.ENCRYPT_MODE, sKey)
@ -242,7 +246,7 @@ object SecretStoringUtils {
return format1Make(encryptedKey, iv, encryptedBytes) return format1Make(encryptedKey, iv, encryptedBytes)
} }
fun encryptForOldDevicesNotGood(text: String, keyAlias: String): ByteArray { private fun encryptForOldDevicesNotGood(text: String, keyAlias: String): ByteArray {
val salt = ByteArray(8) val salt = ByteArray(8)
secureRandom.nextBytes(salt) secureRandom.nextBytes(salt)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
@ -258,11 +262,11 @@ object SecretStoringUtils {
return format2Make(salt, iv, encryptedBytes) return format2Make(salt, iv, encryptedBytes)
} }
fun decryptForOldDevicesNotGood(data: ByteArray, keyAlias: String): String? { private fun decryptForOldDevicesNotGood(data: ByteArray, keyAlias: String): String? {
val (salt, iv, encrypted) = format2Extract(ByteArrayInputStream(data)) val (salt, iv, encrypted) = format2Extract(ByteArrayInputStream(data))
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val spec = PBEKeySpec(keyAlias.toCharArray(), salt, 10000, 128) val spec = PBEKeySpec(keyAlias.toCharArray(), salt, 10_000, 128)
val tmp = factory.generateSecret(spec) val tmp = factory.generateSecret(spec)
val sKey = SecretKeySpec(tmp.encoded, "AES") val sKey = SecretKeySpec(tmp.encoded, "AES")
@ -277,25 +281,23 @@ object SecretStoringUtils {
} }
@RequiresApi(Build.VERSION_CODES.KITKAT) @RequiresApi(Build.VERSION_CODES.KITKAT)
fun decryptStringJ(data: ByteArray, keyAlias: String, context: Context): String? { private fun decryptStringK(data: ByteArray, keyAlias: String): String? {
val (encryptedKey, iv, encrypted) = format1Extract(ByteArrayInputStream(data)) val (encryptedKey, iv, encrypted) = format1Extract(ByteArrayInputStream(data))
//we need to decrypt the key //we need to decrypt the key
val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey), context) val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey))
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
val spec = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) IvParameterSpec(iv) else GCMParameterSpec(128, iv) val spec = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) IvParameterSpec(iv) else GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec)
return String(cipher.doFinal(encrypted), Charsets.UTF_8) return String(cipher.doFinal(encrypted), Charsets.UTF_8)
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
@Throws(IOException::class) @Throws(IOException::class)
fun saveSecureObjectM(keyAlias: String, output: OutputStream, writeObject: Any) { private fun saveSecureObjectM(keyAlias: String, output: OutputStream, writeObject: Any) {
val secretKey = getOrGenerateSymmetricKeyForAlias(keyAlias) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, secretKey/*, spec*/) cipher.init(Cipher.ENCRYPT_MODE, secretKey/*, spec*/)
@ -314,14 +316,14 @@ object SecretStoringUtils {
} }
@RequiresApi(Build.VERSION_CODES.KITKAT) @RequiresApi(Build.VERSION_CODES.KITKAT)
fun saveSecureObjectK(keyAlias: String, output: OutputStream, writeObject: Any, context: Context) { private fun saveSecureObjectK(keyAlias: String, output: OutputStream, writeObject: Any) {
//we generate a random symetric key //we generate a random symetric key
val key = ByteArray(16) val key = ByteArray(16)
secureRandom.nextBytes(key) secureRandom.nextBytes(key)
val sKey = SecretKeySpec(key, "AES") val sKey = SecretKeySpec(key, "AES")
//we encrypt this key thanks to the key store //we encrypt this key thanks to the key store
val encryptedKey = rsaEncrypt(keyAlias, key, context) val encryptedKey = rsaEncrypt(keyAlias, key)
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, sKey) cipher.init(Cipher.ENCRYPT_MODE, sKey)
@ -342,7 +344,7 @@ object SecretStoringUtils {
output.write(bos1.toByteArray()) output.write(bos1.toByteArray())
} }
fun saveSecureObjectOldNotGood(keyAlias: String, output: OutputStream, writeObject: Any) { private fun saveSecureObjectOldNotGood(keyAlias: String, output: OutputStream, writeObject: Any) {
val salt = ByteArray(8) val salt = ByteArray(8)
secureRandom.nextBytes(salt) secureRandom.nextBytes(salt)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
@ -387,8 +389,8 @@ object SecretStoringUtils {
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
@Throws(IOException::class) @Throws(IOException::class)
fun <T> loadSecureObjectM(keyAlias: String, inputStream: InputStream): T? { private fun <T> loadSecureObjectM(keyAlias: String, inputStream: InputStream): T? {
val secretKey = getOrGenerateSymmetricKeyForAlias(keyAlias) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
val format = inputStream.read() val format = inputStream.read()
assert(format.toByte() == FORMAT_API_M) assert(format.toByte() == FORMAT_API_M)
@ -411,12 +413,12 @@ object SecretStoringUtils {
@RequiresApi(Build.VERSION_CODES.KITKAT) @RequiresApi(Build.VERSION_CODES.KITKAT)
@Throws(IOException::class) @Throws(IOException::class)
fun <T> loadSecureObjectK(keyAlias: String, inputStream: InputStream, context: Context): T? { private fun <T> loadSecureObjectK(keyAlias: String, inputStream: InputStream): T? {
val (encryptedKey, iv, encrypted) = format1Extract(inputStream) val (encryptedKey, iv, encrypted) = format1Extract(inputStream)
//we need to decrypt the key //we need to decrypt the key
val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey), context) val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey))
val cipher = Cipher.getInstance(AES_MODE) val cipher = Cipher.getInstance(AES_MODE)
val spec = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) IvParameterSpec(iv) else GCMParameterSpec(128, iv) val spec = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) IvParameterSpec(iv) else GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec)
@ -432,8 +434,7 @@ object SecretStoringUtils {
} }
@Throws(Exception::class) @Throws(Exception::class)
fun <T> loadSecureObjectOldNotGood(keyAlias: String, inputStream: InputStream): T? { private fun <T> loadSecureObjectOldNotGood(keyAlias: String, inputStream: InputStream): T? {
val (salt, iv, encrypted) = format2Extract(inputStream) val (salt, iv, encrypted) = format2Extract(inputStream)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
@ -456,10 +457,10 @@ object SecretStoringUtils {
} }
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(Build.VERSION_CODES.KITKAT)
@Throws(Exception::class) @Throws(Exception::class)
private fun rsaEncrypt(alias: String, secret: ByteArray, context: Context): ByteArray { private fun rsaEncrypt(alias: String, secret: ByteArray): ByteArray {
val privateKeyEntry = getOrGenerateKeyPairForAlias(alias, context) val privateKeyEntry = getOrGenerateKeyPairForAlias(alias)
// Encrypt the text // Encrypt the text
val inputCipher = Cipher.getInstance(RSA_MODE) val inputCipher = Cipher.getInstance(RSA_MODE)
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.certificate.publicKey) inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.certificate.publicKey)
@ -472,19 +473,14 @@ object SecretStoringUtils {
return outputStream.toByteArray() return outputStream.toByteArray()
} }
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(Build.VERSION_CODES.KITKAT)
@Throws(Exception::class) @Throws(Exception::class)
private fun rsaDecrypt(alias: String, encrypted: InputStream, context: Context): ByteArray { private fun rsaDecrypt(alias: String, encrypted: InputStream): ByteArray {
val privateKeyEntry = getOrGenerateKeyPairForAlias(alias, context) val privateKeyEntry = getOrGenerateKeyPairForAlias(alias)
val output = Cipher.getInstance(RSA_MODE) val output = Cipher.getInstance(RSA_MODE)
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.privateKey) output.init(Cipher.DECRYPT_MODE, privateKeyEntry.privateKey)
val bos = ByteArrayOutputStream() return CipherInputStream(encrypted, output).use { it.readBytes() }
CipherInputStream(encrypted, output).use {
it.copyTo(bos)
}
return bos.toByteArray()
} }
private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> { private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
@ -495,14 +491,7 @@ object SecretStoringUtils {
val iv = ByteArray(ivSize) val iv = ByteArray(ivSize)
bis.read(iv, 0, ivSize) bis.read(iv, 0, ivSize)
val encrypted = bis.readBytes()
val bos = ByteArrayOutputStream()
var next = bis.read()
while (next != -1) {
bos.write(next)
next = bis.read()
}
val encrypted = bos.toByteArray()
return Pair(iv, encrypted) return Pair(iv, encrypted)
} }
@ -516,7 +505,6 @@ object SecretStoringUtils {
} }
private fun format1Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> { private fun format1Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> {
val format = bis.read() val format = bis.read()
assert(format.toByte() == FORMAT_1) assert(format.toByte() == FORMAT_1)
@ -530,14 +518,7 @@ object SecretStoringUtils {
val iv = ByteArray(ivSize) val iv = ByteArray(ivSize)
bis.read(iv) bis.read(iv)
val bos = ByteArrayOutputStream() val encrypted = bis.readBytes()
var next = bis.read()
while (next != -1) {
bos.write(next)
next = bis.read()
}
val encrypted = bos.toByteArray()
return Triple(encryptedKey, iv, encrypted) return Triple(encryptedKey, iv, encrypted)
} }
@ -567,7 +548,6 @@ object SecretStoringUtils {
} }
private fun format2Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> { private fun format2Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> {
val format = bis.read() val format = bis.read()
assert(format.toByte() == FORMAT_2) assert(format.toByte() == FORMAT_2)
@ -579,14 +559,7 @@ object SecretStoringUtils {
val iv = ByteArray(ivSize) val iv = ByteArray(ivSize)
bis.read(iv) bis.read(iv)
val bos = ByteArrayOutputStream() val encrypted = bis.readBytes()
var next = bis.read()
while (next != -1) {
bos.write(next)
next = bis.read()
}
val encrypted = bos.toByteArray()
return Triple(salt, iv, encrypted) return Triple(salt, iv, encrypted)
} }
} }

View File

@ -16,25 +16,64 @@
package im.vector.matrix.android.internal.session.signout package im.vector.matrix.android.internal.session.signout
import android.content.Context
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.crypto.CryptoModule
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.UserCacheDirectory
import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionModule
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import timber.log.Timber
import java.io.File
import javax.inject.Inject import javax.inject.Inject
internal interface SignOutTask : Task<Unit, Unit> internal interface SignOutTask : Task<Unit, Unit>
internal class DefaultSignOutTask @Inject constructor(private val credentials: Credentials, internal class DefaultSignOutTask @Inject constructor(private val context: Context,
private val credentials: Credentials,
private val signOutAPI: SignOutAPI, private val signOutAPI: SignOutAPI,
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
private val sessionParamsStore: SessionParamsStore) : SignOutTask { private val sessionParamsStore: SessionParamsStore,
@SessionDatabase private val clearSessionDataTask: ClearCacheTask,
@CryptoDatabase private val clearCryptoDataTask: ClearCacheTask,
@UserCacheDirectory private val userFile: File,
private val realmKeysUtils: RealmKeysUtils,
@UserMd5 private val userMd5: String) : SignOutTask {
override suspend fun execute(params: Unit) { override suspend fun execute(params: Unit) {
Timber.d("SignOut: send request...")
executeRequest<Unit> { executeRequest<Unit> {
apiCall = signOutAPI.signOut() apiCall = signOutAPI.signOut()
} }
sessionParamsStore.delete(credentials.userId)
Timber.d("SignOut: release session...")
sessionManager.releaseSession(credentials.userId) sessionManager.releaseSession(credentials.userId)
Timber.d("SignOut: cancel pending works...")
WorkManagerUtil.cancelAllWorks(context)
Timber.d("SignOut: delete session params...")
sessionParamsStore.delete(credentials.userId)
Timber.d("SignOut: clear session data...")
clearSessionDataTask.execute(Unit)
Timber.d("SignOut: clear crypto data...")
clearCryptoDataTask.execute(Unit)
Timber.d("SignOut: clear file system")
userFile.deleteRecursively()
Timber.d("SignOut: clear the database keys")
realmKeysUtils.clear(SessionModule.DB_ALIAS_PREFIX + userMd5)
realmKeysUtils.clear(CryptoModule.DB_ALIAS_PREFIX + userMd5)
} }
} }

View File

@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
@ -33,7 +33,7 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: CryptoManager, internal class CryptoSyncHandler @Inject constructor(private val cryptoService: DefaultCryptoService,
private val sasVerificationService: DefaultSasVerificationService) { private val sasVerificationService: DefaultSasVerificationService) {
fun handleToDevice(toDevice: ToDeviceSyncResponse, initialSyncProgressService: DefaultInitialSyncProgressService? = null) { fun handleToDevice(toDevice: ToDeviceSyncResponse, initialSyncProgressService: DefaultInitialSyncProgressService? = null) {
@ -47,13 +47,13 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content) Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
} else { } else {
sasVerificationService.onToDeviceEvent(event) sasVerificationService.onToDeviceEvent(event)
cryptoManager.onToDeviceEvent(event) cryptoService.onToDeviceEvent(event)
} }
} }
} }
fun onSyncCompleted(syncResponse: SyncResponse) { fun onSyncCompleted(syncResponse: SyncResponse) {
cryptoManager.onSyncCompleted(syncResponse) cryptoService.onSyncCompleted(syncResponse)
} }
@ -68,7 +68,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
if (event.getClearType() == EventType.ENCRYPTED) { if (event.getClearType() == EventType.ENCRYPTED) {
var result: MXEventDecryptionResult? = null var result: MXEventDecryptionResult? = null
try { try {
result = cryptoManager.decryptEvent(event, timelineId ?: "") result = cryptoService.decryptEvent(event, timelineId ?: "")
} catch (exception: MXCryptoError) { } catch (exception: MXCryptoError) {
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType //setCryptoError(exception.cryptoError) event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType //setCryptoError(exception.cryptoError)
} }

View File

@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.EventEntityFields
@ -50,7 +50,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private val readReceiptHandler: ReadReceiptHandler, private val readReceiptHandler: ReadReceiptHandler,
private val roomSummaryUpdater: RoomSummaryUpdater, private val roomSummaryUpdater: RoomSummaryUpdater,
private val roomTagHandler: RoomTagHandler, private val roomTagHandler: RoomTagHandler,
private val cryptoManager: CryptoManager, private val cryptoService: DefaultCryptoService,
private val tokenStore: SyncTokenStore, private val tokenStore: SyncTokenStore,
private val pushRuleService: DefaultPushRuleService, private val pushRuleService: DefaultPushRuleService,
private val processForPushTask: ProcessEventForPushTask, private val processForPushTask: ProcessEventForPushTask,
@ -97,12 +97,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
handleJoinedRoom(realm, it.key, it.value, isInitialSync) handleJoinedRoom(realm, it.key, it.value, isInitialSync)
} }
is HandlingStrategy.INVITED -> is HandlingStrategy.INVITED ->
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.4f) { handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.1f) {
handleInvitedRoom(realm, it.key, it.value) handleInvitedRoom(realm, it.key, it.value)
} }
is HandlingStrategy.LEFT -> { is HandlingStrategy.LEFT -> {
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_left_rooms, 0.2f) { handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_left_rooms, 0.3f) {
handleLeftRoom(realm, it.key, it.value) handleLeftRoom(realm, it.key, it.value)
} }
} }
@ -125,8 +125,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
} }
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
?: realm.createObject(roomId)
if (roomEntity.membership == Membership.INVITE) { if (roomEntity.membership == Membership.INVITE) {
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
@ -135,13 +134,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
// State event // State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() ?: Int.MIN_VALUE
?: Int.MIN_VALUE
val untimelinedStateIndex = minStateIndex + 1 val untimelinedStateIndex = minStateIndex + 1
roomSync.state.events.forEach { event -> roomSync.state.events.forEach { event ->
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex) roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
// Give info to crypto module // Give info to crypto module
cryptoManager.onStateEvent(roomId, event) cryptoService.onStateEvent(roomId, event)
UserEntityFactory.createOrNull(event)?.also { UserEntityFactory.createOrNull(event)?.also {
realm.insertOrUpdate(it) realm.insertOrUpdate(it)
} }
@ -167,8 +165,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomSync: roomSync:
InvitedRoomSync): RoomEntity { InvitedRoomSync): RoomEntity {
Timber.v("Handle invited sync for room $roomId") Timber.v("Handle invited sync for room $roomId")
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events) val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
@ -181,8 +178,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private fun handleLeftRoom(realm: Realm, private fun handleLeftRoom(realm: Realm,
roomId: String, roomId: String,
roomSync: RoomSync): RoomEntity { roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
?: realm.createObject(roomId)
roomEntity.membership = Membership.LEAVE roomEntity.membership = Membership.LEAVE
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
@ -214,7 +210,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
event.eventId?.also { eventIds.add(it) } event.eventId?.also { eventIds.add(it) }
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset) chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)
// Give info to crypto module // Give info to crypto module
cryptoManager.onLiveEvent(roomEntity.roomId, event) cryptoService.onLiveEvent(roomEntity.roomId, event)
// Try to remove local echo // Try to remove local echo
event.unsignedData?.transactionId?.also { event.unsignedData?.transactionId?.also {
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it) val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)

View File

@ -21,7 +21,6 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomTagEntity import im.vector.matrix.android.internal.database.model.RoomTagEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm import io.realm.Realm
import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal class RoomTagHandler @Inject constructor() { internal class RoomTagHandler @Inject constructor() {
@ -30,16 +29,8 @@ internal class RoomTagHandler @Inject constructor() {
if (content == null) { if (content == null) {
return return
} }
val tags = ArrayList<RoomTagEntity>() val tags = content.tags.entries.map { (tagName, params) ->
for (tagName in content.tags.keys) { RoomTagEntity(tagName, params["order"] as? Double)
val params = content.tags[tagName]
val order = params?.get("order")
val tag = if (order is Double) {
RoomTagEntity(tagName, order)
} else {
RoomTagEntity(tagName, null)
}
tags.add(tag)
} }
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: RoomSummaryEntity(roomId) ?: RoomSummaryEntity(roomId)

View File

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.sync
import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
import im.vector.matrix.android.internal.session.reportSubtask import im.vector.matrix.android.internal.session.reportSubtask
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
@ -30,7 +30,7 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val groupSyncHandler: GroupSyncHandler, private val groupSyncHandler: GroupSyncHandler,
private val cryptoSyncHandler: CryptoSyncHandler, private val cryptoSyncHandler: CryptoSyncHandler,
private val cryptoManager: CryptoManager, private val cryptoService: DefaultCryptoService,
private val initialSyncProgressService: DefaultInitialSyncProgressService) { private val initialSyncProgressService: DefaultInitialSyncProgressService) {
fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> { fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> {
@ -40,12 +40,12 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
val reporter = initialSyncProgressService.takeIf { isInitialSync } val reporter = initialSyncProgressService.takeIf { isInitialSync }
measureTimeMillis { measureTimeMillis {
if (!cryptoManager.isStarted()) { if (!cryptoService.isStarted()) {
Timber.v("Should start cryptoManager") Timber.v("Should start cryptoService")
cryptoManager.start(isInitialSync) cryptoService.start(isInitialSync)
} }
}.also { }.also {
Timber.v("Finish handling start cryptoManager in $it ms") Timber.v("Finish handling start cryptoService in $it ms")
} }
val measure = measureTimeMillis { val measure = measureTimeMillis {
// Handle the to device events before the room ones // Handle the to device events before the room ones

View File

@ -93,8 +93,8 @@ open class SyncService : Service() {
} }
fun doSync(once: Boolean = false) { fun doSync(once: Boolean = false) {
if (!networkConnectivityChecker.isConnected()) { if (!networkConnectivityChecker.hasInternetAccess) {
Timber.v("Sync is Paused. Waiting...") Timber.v("No internet access. Waiting...")
//TODO Retry in ? //TODO Retry in ?
timer.schedule(object : TimerTask() { timer.schedule(object : TimerTask() {
override fun run() { override fun run() {

View File

@ -50,6 +50,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private val lock = Object() private val lock = Object()
private var cancelableTask: Cancelable? = null private var cancelableTask: Cancelable? = null
private var isStarted = false
init { init {
updateStateTo(SyncState.IDLE) updateStateTo(SyncState.IDLE)
} }
@ -60,19 +62,18 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
fun restart() = synchronized(lock) { fun restart() = synchronized(lock) {
if (state is SyncState.PAUSED) { if (!isStarted) {
Timber.v("Resume sync...") Timber.v("Resume sync...")
updateStateTo(SyncState.RUNNING(afterPause = true)) isStarted = true
lock.notify() lock.notify()
} }
} }
fun pause() = synchronized(lock) { fun pause() = synchronized(lock) {
if (state is SyncState.RUNNING) { if (isStarted) {
Timber.v("Pause sync...") Timber.v("Pause sync...")
updateStateTo(SyncState.PAUSED) isStarted = false
cancelableTask?.cancel() cancelableTask?.cancel()
lock.notify()
} }
} }
@ -87,19 +88,31 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
return liveState return liveState
} }
override fun onConnect() {
Timber.v("Network is back")
synchronized(lock) {
lock.notify()
}
}
override fun run() { override fun run() {
Timber.v("Start syncing...") Timber.v("Start syncing...")
isStarted = true
networkConnectivityChecker.register(this) networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this) backgroundDetectionObserver.register(this)
while (state != SyncState.KILLING) { while (state != SyncState.KILLING) {
Timber.v("Entering loop, state: $state") Timber.v("Entering loop, state: $state")
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) { if (!networkConnectivityChecker.hasInternetAccess) {
Timber.v("No network or sync is Paused. Waiting...") Timber.v("No network. Waiting...")
synchronized(lock) { updateStateTo(SyncState.NO_NETWORK)
lock.wait() synchronized(lock) { lock.wait() }
} Timber.v("...unlocked")
} else if (!isStarted) {
Timber.v("Sync is Paused. Waiting...")
updateStateTo(SyncState.PAUSED)
synchronized(lock) { lock.wait() }
Timber.v("...unlocked") Timber.v("...unlocked")
} else { } else {
if (state !is SyncState.RUNNING) { if (state !is SyncState.RUNNING) {
@ -167,16 +180,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
private fun updateStateTo(newState: SyncState) { private fun updateStateTo(newState: SyncState) {
Timber.v("Update state to $newState") Timber.v("Update state from $state to $newState")
state = newState state = newState
liveState.postValue(newState) liveState.postValue(newState)
} }
override fun onConnect() {
synchronized(lock) {
lock.notify()
}
}
override fun onMoveToForeground() { override fun onMoveToForeground() {
restart() restart()

View File

@ -33,7 +33,7 @@ internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObse
private set private set
private private
val listeners = ArrayList<Listener>() val listeners = LinkedHashSet<Listener>()
fun register(listener: Listener) { fun register(listener: Listener) {
listeners.add(listener) listeners.add(listener)

View File

@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.util
import android.content.Context import android.content.Context
import androidx.work.WorkManager import androidx.work.WorkManager
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import java.util.* import java.util.UUID
internal class CancelableWork(private val context: Context, internal class CancelableWork(private val context: Context,
private val workId: UUID) : Cancelable { private val workId: UUID) : Cancelable {

View File

@ -34,7 +34,7 @@ import java.security.*
import java.security.cert.CertificateException import java.security.cert.CertificateException
import java.security.spec.AlgorithmParameterSpec import java.security.spec.AlgorithmParameterSpec
import java.security.spec.RSAKeyGenParameterSpec import java.security.spec.RSAKeyGenParameterSpec
import java.util.* import java.util.Calendar
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.* import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.GCMParameterSpec

View File

@ -22,7 +22,7 @@ import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.TreeSet
/** /**
* Build canonical Json * Build canonical Json
@ -60,43 +60,33 @@ object JsonCanonicalizer {
when (any) { when (any) {
is JSONArray -> { is JSONArray -> {
// Canonicalize each element of the array // Canonicalize each element of the array
val result = StringBuilder("[") return (0 until any.length()).joinToString(separator = ",", prefix = "[", postfix = "]") {
canonicalizeRecursive(any.get(it))
for (i in 0 until any.length()) {
result.append(canonicalizeRecursive(any.get(i)))
if (i < any.length() - 1) {
result.append(",")
}
} }
result.append("]")
return result.toString()
} }
is JSONObject -> { is JSONObject -> {
// Sort the attributes by name, and the canonicalize each element of the JSONObject // Sort the attributes by name, and the canonicalize each element of the JSONObject
val result = StringBuilder("{")
val attributes = TreeSet<String>() val attributes = TreeSet<String>()
for (entry in any.keys()) { for (entry in any.keys()) {
attributes.add(entry) attributes.add(entry)
} }
for (attribute in attributes.withIndex()) { return buildString {
result.append("\"") append("{")
.append(attribute.value) for ((index, value) in attributes.withIndex()) {
.append("\"") append("\"")
.append(":") append(value)
.append(canonicalizeRecursive(any[attribute.value])) append("\"")
append(":")
append(canonicalizeRecursive(any[value]))
if (attribute.index < attributes.size - 1) { if (index < attributes.size - 1) {
result.append(",") append(",")
}
} }
append("}")
} }
result.append("}")
return result.toString()
} }
is String -> return JSONObject.quote(any) is String -> return JSONObject.quote(any)
else -> return any.toString() else -> return any.toString()

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.worker
import android.content.Context import android.content.Context
import androidx.work.* import androidx.work.*
// TODO Multiaccount
internal object WorkManagerUtil { internal object WorkManagerUtil {
private const val MATRIX_SDK_TAG = "MatrixSDK" private const val MATRIX_SDK_TAG = "MatrixSDK"

View File

@ -167,4 +167,9 @@
<string name="initial_sync_start_importing_account_data">Начална синхронизация: <string name="initial_sync_start_importing_account_data">Начална синхронизация:
\nИмпортиране на данни за профила</string> \nИмпортиране на данни за профила</string>
<string name="notice_room_update">%s обнови тази стая.</string>
<string name="event_status_sending_message">Изпращане на съобщение…</string>
<string name="clear_timeline_send_queue">Изчисти опашката за изпращане</string>
</resources> </resources>

View File

@ -105,4 +105,65 @@
<string name="verification_emoji_pig">Schwein</string> <string name="verification_emoji_pig">Schwein</string>
<string name="verification_emoji_elephant">Elefant</string> <string name="verification_emoji_elephant">Elefant</string>
<string name="verification_emoji_rabbit">Hase</string> <string name="verification_emoji_rabbit">Hase</string>
<string name="notice_room_update">%s hat diesen Raum aufgewertet.</string>
<string name="verification_emoji_panda">Panda</string>
<string name="verification_emoji_rooster">Hahn</string>
<string name="verification_emoji_penguin">Pinguin</string>
<string name="verification_emoji_turtle">Schildkröte</string>
<string name="verification_emoji_fish">Fisch</string>
<string name="verification_emoji_octopus">Tintenfisch</string>
<string name="verification_emoji_butterfly">Schmetterling</string>
<string name="verification_emoji_flower">Blume</string>
<string name="verification_emoji_tree">Baum</string>
<string name="verification_emoji_cactus">Kaktus</string>
<string name="verification_emoji_mushroom">Pilz</string>
<string name="verification_emoji_globe">Globus</string>
<string name="verification_emoji_moon">Mond</string>
<string name="verification_emoji_cloud">Wolke</string>
<string name="verification_emoji_fire">Feuer</string>
<string name="verification_emoji_banana">Banane</string>
<string name="verification_emoji_apple">Apfel</string>
<string name="verification_emoji_strawberry">Erdbeere</string>
<string name="verification_emoji_corn">Mais</string>
<string name="verification_emoji_cake">Kuchen</string>
<string name="verification_emoji_heart">Herz</string>
<string name="verification_emoji_smiley">Lächeln</string>
<string name="verification_emoji_robot">Roboter</string>
<string name="verification_emoji_hat">Hut</string>
<string name="verification_emoji_glasses">Brille</string>
<string name="verification_emoji_wrench">Schraubenschlüssel</string>
<string name="verification_emoji_santa">Nikolaus</string>
<string name="verification_emoji_thumbsup">Daumen hoch</string>
<string name="verification_emoji_umbrella">Regenschirm</string>
<string name="verification_emoji_hourglass">Sanduhr</string>
<string name="verification_emoji_clock">Uhr</string>
<string name="verification_emoji_gift">Geschenk</string>
<string name="verification_emoji_lightbulb">Glühbirne</string>
<string name="verification_emoji_book">Buch</string>
<string name="verification_emoji_pencil">Stift</string>
<string name="verification_emoji_paperclip">Büroklammer</string>
<string name="verification_emoji_scissors">Scheren</string>
<string name="verification_emoji_lock">sperren</string>
<string name="verification_emoji_key">Schlüssel</string>
<string name="verification_emoji_hammer">Hammer</string>
<string name="verification_emoji_telephone">Telefon</string>
<string name="verification_emoji_flag">Flagge</string>
<string name="verification_emoji_train">Zug</string>
<string name="verification_emoji_bicycle">Fahrrad</string>
<string name="verification_emoji_airplane">Flugzeug</string>
<string name="verification_emoji_rocket">Rakete</string>
<string name="verification_emoji_trophy">Pokal</string>
<string name="verification_emoji_ball">Ball</string>
<string name="verification_emoji_guitar">Gitarre</string>
<string name="verification_emoji_trumpet">Trompete</string>
<string name="verification_emoji_bell">Glocke</string>
<string name="verification_emoji_anchor">Anker</string>
<string name="verification_emoji_headphone">Kopfhörer</string>
<string name="verification_emoji_folder">Ordner</string>
<string name="verification_emoji_pin">Stecknadel</string>
<string name="event_status_sending_message">Sende eine Nachricht…</string>
<string name="clear_timeline_send_queue">Sendewarteschlange leeren</string>
</resources> </resources>

View File

@ -167,4 +167,9 @@
<string name="initial_sync_start_importing_account_data">Hasierako sinkronizazioa: <string name="initial_sync_start_importing_account_data">Hasierako sinkronizazioa:
\nKontuaren datuak inportatzen</string> \nKontuaren datuak inportatzen</string>
<string name="notice_room_update">%s erabiltzaileak gela hau eguneratu du.</string>
<string name="event_status_sending_message">Mezua bidaltzen…</string>
<string name="clear_timeline_send_queue">Garbitu bidalketa-ilara</string>
</resources> </resources>

View File

@ -168,4 +168,9 @@
<string name="initial_sync_start_importing_account_data">Alkusynkronointi: <string name="initial_sync_start_importing_account_data">Alkusynkronointi:
\nTuodaan tilin tietoja</string> \nTuodaan tilin tietoja</string>
<string name="notice_room_update">%s päivitti tämän huoneen.</string>
<string name="event_status_sending_message">Lähetetään viestiä…</string>
<string name="clear_timeline_send_queue">Tyhjennä lähetysjono</string>
</resources> </resources>

View File

@ -167,4 +167,9 @@
<string name="initial_sync_start_importing_account_data">Synchronisation initiale : <string name="initial_sync_start_importing_account_data">Synchronisation initiale :
\nImportation des données du compte</string> \nImportation des données du compte</string>
<string name="notice_room_update">%s a mis à niveau ce salon.</string>
<string name="event_status_sending_message">Envoi du message…</string>
<string name="clear_timeline_send_queue">Vider la file denvoi</string>
</resources> </resources>

View File

@ -166,4 +166,9 @@
<string name="initial_sync_start_importing_account_data">Induló szinkronizáció: <string name="initial_sync_start_importing_account_data">Induló szinkronizáció:
\nFiók adatok betöltése</string> \nFiók adatok betöltése</string>
<string name="notice_room_update">%s frissítette ezt a szobát.</string>
<string name="event_status_sending_message">Üzenet küldése…</string>
<string name="clear_timeline_send_queue">Küldő sor ürítése</string>
</resources> </resources>

View File

@ -167,4 +167,9 @@
<string name="initial_sync_start_importing_account_data">Sync iniziale: <string name="initial_sync_start_importing_account_data">Sync iniziale:
\nImportazione dati account</string> \nImportazione dati account</string>
<string name="notice_room_update">%s ha aggiornato questa stanza.</string>
<string name="event_status_sending_message">Invio messaggio in corso …</string>
<string name="clear_timeline_send_queue">Cancella la coda di invio</string>
</resources> </resources>

View File

@ -1,6 +1,173 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources> <resources>
<string name="summary_message">%1$s: %2$s</string> <string name="summary_message">%1$s: %2$s</string>
<string name="notice_room_invite_no_invitee">%s\'의 초대</string> <string name="notice_room_invite_no_invitee">%s의 초대</string>
<string name="verification_emoji_headphone">헤드폰</string> <string name="verification_emoji_headphone">헤드폰</string>
<string name="summary_user_sent_image">%1$s님이 사진을 보냈습니다.</string>
<string name="summary_user_sent_sticker">%1$s님이 스티커를 보냈습니다.</string>
<string name="notice_room_invite">%1$s님이 %2$s님을 초대했습니다</string>
<string name="notice_room_invite_you">%1$s님이 당신을 초대했습니다</string>
<string name="notice_room_join">%1$s님이 참가했습니다</string>
<string name="notice_room_leave">%1$s님이 떠났습니다</string>
<string name="notice_room_reject">%1$s님이 초대를 거부했습니다</string>
<string name="notice_room_kick">%1$s님이 %2$s님을 추방했습니다</string>
<string name="notice_room_unban">%1$s님이 %2$s님의 차단을 풀었습니다</string>
<string name="notice_room_ban">%1$s님이 %2$s님을 차단했습니다</string>
<string name="notice_room_withdraw">%1$s님이 %2$s님의 초대를 취소했습니다</string>
<string name="notice_avatar_url_changed">%1$s님이 아바타를 변경했습니다</string>
<string name="notice_display_name_set">%1$s님이 표시 이름을 %2$s(으)로 설정했습니다</string>
<string name="notice_display_name_changed_from">%1$s님이 표시 이름을 %2$s에서 %3$s(으)로 변경했습니다</string>
<string name="notice_display_name_removed">%1$s님이 표시 이름을 삭제했습니다 (%2$s)</string>
<string name="notice_room_topic_changed">%1$s님이 주제를 다음으로 변경했습니다: %2$s</string>
<string name="notice_room_name_changed">%1$s님이 방 이름을 다음으로 변경했습니다: %2$s</string>
<string name="notice_placed_video_call">%s님이 영상 통화를 걸었습니다.</string>
<string name="notice_placed_voice_call">%s님이 음성 통화를 걸었습니다.</string>
<string name="notice_answered_call">%s님이 전화를 받았습니다.</string>
<string name="notice_ended_call">%s님이 전화를 끊었습니다.</string>
<string name="notice_made_future_room_visibility">%1$s님이 이후 %2$s에게 방 기록을 공개했습니다</string>
<string name="notice_room_visibility_invited">초대된 시점부터 모든 방 구성원.</string>
<string name="notice_room_visibility_joined">들어온 시점부터 모든 방 구성원.</string>
<string name="notice_room_visibility_shared">모든 방 구성원.</string>
<string name="notice_room_visibility_world_readable">누구나.</string>
<string name="notice_room_visibility_unknown">알 수 없음 (%s).</string>
<string name="notice_end_to_end">%1$s님이 종단 간 암호화를 켰습니다 (%2$s)</string>
<string name="notice_room_update">%s님이 방을 업그레이드했습니다.</string>
<string name="notice_requested_voip_conference">%1$s님이 VoIP 회의를 요청했습니다</string>
<string name="notice_voip_started">VoIP 회의가 시작했습니다</string>
<string name="notice_voip_finished">VoIP 회의가 끝났습니다</string>
<string name="notice_avatar_changed_too">(아바타도 변경됨)</string>
<string name="notice_room_name_removed">%1$s님이 방 이름을 삭제했습니다</string>
<string name="notice_room_topic_removed">%1$s님이 방 주제를 삭제했습니다</string>
<string name="notice_event_redacted">메시지가 삭제되었습니다</string>
<string name="notice_event_redacted_by">메시지가 %1$s님에 의해 삭제되었습니다</string>
<string name="notice_event_redacted_with_reason">메시지가 삭제되었습니다 [이유: %1$s]</string>
<string name="notice_event_redacted_by_with_reason">메시지가 %1$s님에 의해 삭제되었습니다 [이유: %2$s]</string>
<string name="notice_profile_change_redacted">%1$s님이 프로필 %2$s을(를) 업데이트했습니다</string>
<string name="notice_room_third_party_invite">%1$s님이 %2$s님에게 방 초대를 보냈습니다</string>
<string name="notice_room_third_party_registered_invite">%1$s님이 %2$s의 초대를 수락했습니다</string>
<string name="notice_crypto_unable_to_decrypt">** 암호를 해독할 수 없음: %s **</string>
<string name="notice_crypto_error_unkwown_inbound_session_id">발신인의 기기에서 이 메시지의 키를 보내지 않았습니다.</string>
<string name="message_reply_to_prefix">이 답장의 질문</string>
<string name="could_not_redact">검열할 수 없습니다</string>
<string name="unable_to_send_message">메시지를 보낼 수 없습니다</string>
<string name="message_failed_to_upload">사진 업로드에 실패했습니다</string>
<string name="network_error">네트워크 오류</string>
<string name="matrix_error">Matrix 오류</string>
<string name="room_error_join_failed_empty_room">현재 빈 방에 다시 들어갈 수 없습니다.</string>
<string name="encrypted_message">암호화된 메시지</string>
<string name="medium_email">이메일 주소</string>
<string name="medium_phone_number">전화번호</string>
<string name="reply_to_an_image">사진을 보냈습니다.</string>
<string name="reply_to_a_video">동영상을 보냈습니다.</string>
<string name="reply_to_an_audio_file">오디오 파일을 보냈습니다.</string>
<string name="reply_to_a_file">파일을 보냈습니다.</string>
<string name="room_displayname_invite_from">%s에서 초대함</string>
<string name="room_displayname_room_invite">방 초대</string>
<string name="room_displayname_two_members">%1$s님과 %2$s님</string>
<plurals name="room_displayname_three_and_more_members">
<item quantity="other">%1$s님 외 %2$d명</item>
</plurals>
<string name="room_displayname_empty_room">빈 방</string>
<string name="verification_emoji_dog">개</string>
<string name="verification_emoji_cat">고양이</string>
<string name="verification_emoji_lion">사자</string>
<string name="verification_emoji_horse">말</string>
<string name="verification_emoji_unicorn">유니콘</string>
<string name="verification_emoji_pig">돼지</string>
<string name="verification_emoji_elephant">코끼리</string>
<string name="verification_emoji_rabbit">토끼</string>
<string name="verification_emoji_panda">판다</string>
<string name="verification_emoji_rooster">수탉</string>
<string name="verification_emoji_penguin">펭귄</string>
<string name="verification_emoji_turtle">거북</string>
<string name="verification_emoji_fish">물고기</string>
<string name="verification_emoji_octopus">문어</string>
<string name="verification_emoji_butterfly">나비</string>
<string name="verification_emoji_flower">꽃</string>
<string name="verification_emoji_tree">나무</string>
<string name="verification_emoji_cactus">선인장</string>
<string name="verification_emoji_mushroom">버섯</string>
<string name="verification_emoji_globe">지구본</string>
<string name="verification_emoji_moon">달</string>
<string name="verification_emoji_cloud">구름</string>
<string name="verification_emoji_fire">불</string>
<string name="verification_emoji_banana">바나나</string>
<string name="verification_emoji_apple">사과</string>
<string name="verification_emoji_strawberry">딸기</string>
<string name="verification_emoji_corn">옥수수</string>
<string name="verification_emoji_pizza">피자</string>
<string name="verification_emoji_cake">케이크</string>
<string name="verification_emoji_heart">하트</string>
<string name="verification_emoji_smiley">웃음</string>
<string name="verification_emoji_robot">로봇</string>
<string name="verification_emoji_hat">모자</string>
<string name="verification_emoji_glasses">안경</string>
<string name="verification_emoji_wrench">스패너</string>
<string name="verification_emoji_santa">산타클로스</string>
<string name="verification_emoji_thumbsup">좋아요</string>
<string name="verification_emoji_umbrella">우산</string>
<string name="verification_emoji_hourglass">모래시계</string>
<string name="verification_emoji_clock">시계</string>
<string name="verification_emoji_gift">선물</string>
<string name="verification_emoji_lightbulb">전구</string>
<string name="verification_emoji_book">책</string>
<string name="verification_emoji_pencil">연필</string>
<string name="verification_emoji_paperclip">클립</string>
<string name="verification_emoji_scissors">가위</string>
<string name="verification_emoji_lock">자물쇠</string>
<string name="verification_emoji_key">열쇠</string>
<string name="verification_emoji_hammer">망치</string>
<string name="verification_emoji_telephone">전화기</string>
<string name="verification_emoji_flag">깃발</string>
<string name="verification_emoji_train">기차</string>
<string name="verification_emoji_bicycle">자전거</string>
<string name="verification_emoji_airplane">비행기</string>
<string name="verification_emoji_rocket">로켓</string>
<string name="verification_emoji_trophy">트로피</string>
<string name="verification_emoji_ball">공</string>
<string name="verification_emoji_guitar">기타</string>
<string name="verification_emoji_trumpet">트럼펫</string>
<string name="verification_emoji_bell">종</string>
<string name="verification_emoji_anchor">닻</string>
<string name="verification_emoji_folder">폴더</string>
<string name="verification_emoji_pin">핀</string>
<string name="initial_sync_start_importing_account">초기 동기화:
\n계정 가져오는 중…</string>
<string name="initial_sync_start_importing_account_crypto">초기 동기화:
\n암호 가져오는 중</string>
<string name="initial_sync_start_importing_account_rooms">초기 동기화:
\n방 가져오는 중</string>
<string name="initial_sync_start_importing_account_joined_rooms">초기 동기화:
\n들어간 방 가져오는 중</string>
<string name="initial_sync_start_importing_account_invited_rooms">초기 동기화:
\n초대받은 방 가져오는 중</string>
<string name="initial_sync_start_importing_account_left_rooms">초기 동기화:
\n떠난 방 가져오는 중</string>
<string name="initial_sync_start_importing_account_groups">초기 동기화:
\n커뮤니티 가져오는 중</string>
<string name="initial_sync_start_importing_account_data">초기 동기화:
\n계정 데이터 가져오는 중</string>
<string name="event_status_sending_message">메시지 보내는 중…</string>
<string name="clear_timeline_send_queue">전송 대기 열 지우기</string>
</resources> </resources>

View File

@ -176,4 +176,9 @@
<string name="initial_sync_start_importing_account_data">Initiële synchronisatie: <string name="initial_sync_start_importing_account_data">Initiële synchronisatie:
\nAccountgegevens worden geïmporteerd</string> \nAccountgegevens worden geïmporteerd</string>
<string name="notice_room_update">%s heeft dit gesprek opgewaardeerd.</string>
<string name="event_status_sending_message">Bericht wordt verstuurd…</string>
<string name="clear_timeline_send_queue">Uitgaande wachtrij legen</string>
</resources> </resources>

View File

@ -7,7 +7,7 @@
<string name="notice_room_invite">%1$s zaprosił(a) %2$s</string> <string name="notice_room_invite">%1$s zaprosił(a) %2$s</string>
<string name="notice_room_invite_you">%1$s zaprosił(a) Cię</string> <string name="notice_room_invite_you">%1$s zaprosił(a) Cię</string>
<string name="notice_room_join">%1$s dołączył(a)</string> <string name="notice_room_join">%1$s dołączył(a)</string>
<string name="notice_room_leave">%1$s wyszedł(-ła)</string> <string name="notice_room_leave">%1$s opuścił(a)</string>
<string name="notice_room_reject">%1$s odrzucił(a) zaproszenie</string> <string name="notice_room_reject">%1$s odrzucił(a) zaproszenie</string>
<string name="notice_room_kick">%1$s wyrzucił(a) %2$s</string> <string name="notice_room_kick">%1$s wyrzucił(a) %2$s</string>
<string name="notice_room_unban">%1$s odblokował(a) %2$s</string> <string name="notice_room_unban">%1$s odblokował(a) %2$s</string>
@ -17,11 +17,11 @@
<string name="notice_display_name_changed_from">%1$s zmienił(a) wyświetlaną nazwę z %2$s na %3$s</string> <string name="notice_display_name_changed_from">%1$s zmienił(a) wyświetlaną nazwę z %2$s na %3$s</string>
<string name="notice_display_name_removed">%1$s usunął(-ęła) swoją wyświetlaną nazwę (%2$s)</string> <string name="notice_display_name_removed">%1$s usunął(-ęła) swoją wyświetlaną nazwę (%2$s)</string>
<string name="notice_room_topic_changed">%1$s zmienił(a) temat na: %2$s</string> <string name="notice_room_topic_changed">%1$s zmienił(a) temat na: %2$s</string>
<string name="unable_to_send_message">Nie udało się wysłać wiadomości</string> <string name="unable_to_send_message">Nie można wysłać wiadomości</string>
<string name="message_failed_to_upload">Nie udało się wysłać zdjęcia</string> <string name="message_failed_to_upload">Przesyłanie zdjęcia nie powiodło się</string>
<string name="network_error">ogólne błędy</string> <string name="network_error">Błąd sieci</string>
<string name="matrix_error">Błąd Matrixa</string> <string name="matrix_error">Błąd Matrixa</string>
<string name="encrypted_message">Wiadomość zaszyfrowana</string> <string name="encrypted_message">Wiadomość zaszyfrowana</string>
@ -31,7 +31,7 @@
<string name="notice_room_visibility_shared">wszyscy członkowie pokoju.</string> <string name="notice_room_visibility_shared">wszyscy członkowie pokoju.</string>
<string name="notice_room_visibility_world_readable">wszyscy.</string> <string name="notice_room_visibility_world_readable">wszyscy.</string>
<string name="notice_room_name_changed">%1$s zmienił(a) znawę pokoju na: %2$s</string> <string name="notice_room_name_changed">%1$s zmienił(a) nazwę pokoju na: %2$s</string>
<string name="notice_ended_call">%s zakończył(a) rozmowę.</string> <string name="notice_ended_call">%s zakończył(a) rozmowę.</string>
<string name="notice_room_name_removed">%1$s usunął(-ęła) nazwę pokoju</string> <string name="notice_room_name_removed">%1$s usunął(-ęła) nazwę pokoju</string>
<string name="notice_room_topic_removed">%1$s usunął(-ęła) temat pokoju</string> <string name="notice_room_topic_removed">%1$s usunął(-ęła) temat pokoju</string>
@ -57,9 +57,9 @@
</plurals> </plurals>
<string name="notice_crypto_unable_to_decrypt">** Nie można odszyfrować: %s **</string> <string name="notice_crypto_unable_to_decrypt">** Nie można odszyfrować: %s **</string>
<string name="notice_placed_video_call">%s umieścił wideo rozmowe.</string> <string name="notice_placed_video_call">%s wykonał(a) rozmowę wideo.</string>
<string name="notice_placed_voice_call">%s umieścił połączenie głosowe.</string> <string name="notice_placed_voice_call">%s wykonał(a) połączenie głosowe.</string>
<string name="notice_made_future_room_visibility">%1$s uczynił historię pokoju widoczną do %2$s</string> <string name="notice_made_future_room_visibility">%1$s uczynił(a) przyszłą historię pokoju widoczną dla %2$s</string>
<string name="notice_room_visibility_invited">wszyscy członkowie pokoju, od momentu w którym zostali zaproszeni.</string> <string name="notice_room_visibility_invited">wszyscy członkowie pokoju, od momentu w którym zostali zaproszeni.</string>
<string name="notice_room_visibility_joined">wszyscy członkowie pokoju, od momentu w którym dołączyli.</string> <string name="notice_room_visibility_joined">wszyscy członkowie pokoju, od momentu w którym dołączyli.</string>
<string name="notice_room_visibility_unknown">nieznane (%s).</string> <string name="notice_room_visibility_unknown">nieznane (%s).</string>
@ -147,4 +147,29 @@
<string name="verification_emoji_santa">Mikołaj</string> <string name="verification_emoji_santa">Mikołaj</string>
<string name="verification_emoji_gift">Prezent</string> <string name="verification_emoji_gift">Prezent</string>
<string name="verification_emoji_hammer">Młotek</string> <string name="verification_emoji_hammer">Młotek</string>
<string name="notice_room_update">%s zakutalizował(a) ten pokój.</string>
<string name="verification_emoji_thumbsup">Kciuk w górę</string>
<string name="verification_emoji_lock">Zamek</string>
<string name="verification_emoji_ball">Piłka</string>
<string name="initial_sync_start_importing_account">Synchronizacja początkowa:
\nImportowanie konta…</string>
<string name="initial_sync_start_importing_account_crypto">Synchronizacja początkowa:
\nImportowanie kryptografii</string>
<string name="initial_sync_start_importing_account_rooms">Synchronizacja początkowa:
\nImportowanie Pokoi</string>
<string name="initial_sync_start_importing_account_joined_rooms">Synchronizacja początkowa:
\nImportowanie dołączonych Pokoi</string>
<string name="initial_sync_start_importing_account_invited_rooms">Synchronizacja początkowa:
\nImportowanie zaproszonych Pokoi</string>
<string name="initial_sync_start_importing_account_left_rooms">Synchronizacja początkowa:
\nImportowanie opuszczonych Pokoi</string>
<string name="initial_sync_start_importing_account_groups">Synchronizacja początkowa:
\nImportowanie Społeczności</string>
<string name="initial_sync_start_importing_account_data">Synchronizacja początkowa:
\nImportowanie danych Konta</string>
<string name="event_status_sending_message">Wysyłanie wiadomości…</string>
<string name="clear_timeline_send_queue">Wyczyść kolejkę wysyłania</string>
</resources> </resources>

View File

@ -78,4 +78,10 @@
<string name="room_displayname_empty_room">Sala vazia</string> <string name="room_displayname_empty_room">Sala vazia</string>
<string name="summary_user_sent_sticker">%1$s enviou um sticker.</string>
<string name="notice_room_update">%s fez o upgrade da sala.</string>
<string name="notice_event_redacted">Mensagem removida</string>
<string name="notice_event_redacted_by">Mensagem removida por %1$s</string>
</resources> </resources>

View File

@ -169,9 +169,9 @@
\nИмпорт криптографии</string> \nИмпорт криптографии</string>
<string name="initial_sync_start_importing_account_rooms">Начальная синхронизация: <string name="initial_sync_start_importing_account_rooms">Начальная синхронизация:
\nИмпорт комнат</string> \nИмпорт комнат</string>
<string name="initial_sync_start_importing_account_joined_rooms">Начальная синхронизация: <string name="initial_sync_start_importing_account_joined_rooms">Синхронизация начата:
\nИмпорт присоединенных комнат</string> \nИмпорт присоединенных комнат</string>
<string name="initial_sync_start_importing_account_invited_rooms">Начальная синхронизация: <string name="initial_sync_start_importing_account_invited_rooms">Синхронизация начата:
\nИмпорт приглашенных комнат</string> \nИмпорт приглашенных комнат</string>
<string name="initial_sync_start_importing_account_left_rooms">Начальная синхронизация: <string name="initial_sync_start_importing_account_left_rooms">Начальная синхронизация:
\nИмпорт покинутых комнат</string> \nИмпорт покинутых комнат</string>
@ -180,4 +180,9 @@
<string name="initial_sync_start_importing_account_data">Начальная синхронизация: <string name="initial_sync_start_importing_account_data">Начальная синхронизация:
\nИмпорт данных учетной записи</string> \nИмпорт данных учетной записи</string>
<string name="notice_room_update">%s обновил эту комнату.</string>
<string name="event_status_sending_message">Отправка сообщения…</string>
<string name="clear_timeline_send_queue">Очистить очередь отправки</string>
</resources> </resources>

View File

@ -82,4 +82,95 @@
</plurals> </plurals>
<string name="notice_room_update">%s aktualizoval túto miestnosť.</string>
<string name="notice_event_redacted">Správa odstránená</string>
<string name="notice_event_redacted_by">Správa odstránená používateľom %1$s</string>
<string name="notice_event_redacted_with_reason">Správa odstránená [dôvod: %1$s]</string>
<string name="notice_event_redacted_by_with_reason">Správa odstránená používateľom %1$s [dôvod: %2$s]</string>
<string name="verification_emoji_dog">Pes</string>
<string name="verification_emoji_cat">Mačka</string>
<string name="verification_emoji_lion">Lev</string>
<string name="verification_emoji_horse">Kôň</string>
<string name="verification_emoji_unicorn">Jednorožec</string>
<string name="verification_emoji_pig">Prasa</string>
<string name="verification_emoji_elephant">Slon</string>
<string name="verification_emoji_rabbit">Zajac</string>
<string name="verification_emoji_panda">Panda</string>
<string name="verification_emoji_rooster">Kohút</string>
<string name="verification_emoji_penguin">Tučniak</string>
<string name="verification_emoji_turtle">Korytnačka</string>
<string name="verification_emoji_fish">Ryba</string>
<string name="verification_emoji_octopus">Chobotnica</string>
<string name="verification_emoji_butterfly">Motýľ</string>
<string name="verification_emoji_flower">Kvetina</string>
<string name="verification_emoji_tree">Strom</string>
<string name="verification_emoji_cactus">Kaktus</string>
<string name="verification_emoji_mushroom">Hríb</string>
<string name="verification_emoji_globe">Zemeguľa</string>
<string name="verification_emoji_moon">Mesiac</string>
<string name="verification_emoji_cloud">Oblak</string>
<string name="verification_emoji_fire">Oheň</string>
<string name="verification_emoji_banana">Banán</string>
<string name="verification_emoji_apple">Jablko</string>
<string name="verification_emoji_strawberry">Jahoda</string>
<string name="verification_emoji_corn">Kukurica</string>
<string name="verification_emoji_pizza">Pizza</string>
<string name="verification_emoji_cake">Koláč</string>
<string name="verification_emoji_heart">Srdce</string>
<string name="verification_emoji_smiley">Úsmev</string>
<string name="verification_emoji_robot">Robot</string>
<string name="verification_emoji_hat">Klobúk</string>
<string name="verification_emoji_glasses">Okuliare</string>
<string name="verification_emoji_wrench">Skrutkovač</string>
<string name="verification_emoji_santa">Mikuláš</string>
<string name="verification_emoji_thumbsup">Palec nahor</string>
<string name="verification_emoji_umbrella">Dáždnik</string>
<string name="verification_emoji_hourglass">Presýpacie hodiny</string>
<string name="verification_emoji_clock">Hodiny</string>
<string name="verification_emoji_gift">Darček</string>
<string name="verification_emoji_lightbulb">Žiarovka</string>
<string name="verification_emoji_book">Kniha</string>
<string name="verification_emoji_pencil">Ceruzka</string>
<string name="verification_emoji_paperclip">Kancelárska sponka</string>
<string name="verification_emoji_scissors">Nožnice</string>
<string name="verification_emoji_lock">Zámok</string>
<string name="verification_emoji_key">Kľúč</string>
<string name="verification_emoji_hammer">Kladivo</string>
<string name="verification_emoji_telephone">Telefón</string>
<string name="verification_emoji_flag">Vlajka</string>
<string name="verification_emoji_train">Vlak</string>
<string name="verification_emoji_bicycle">Bicykel</string>
<string name="verification_emoji_airplane">Lietadlo</string>
<string name="verification_emoji_rocket">Raketa</string>
<string name="verification_emoji_trophy">Trofej</string>
<string name="verification_emoji_ball">Lopta</string>
<string name="verification_emoji_guitar">Gitara</string>
<string name="verification_emoji_trumpet">Trúbka</string>
<string name="verification_emoji_bell">Zvonček</string>
<string name="verification_emoji_anchor">Kotva</string>
<string name="verification_emoji_headphone">Schlúchadlá</string>
<string name="verification_emoji_folder">Priečinok</string>
<string name="verification_emoji_pin">Pin</string>
<string name="initial_sync_start_importing_account">Úvodná synchronizácia:
\nPrebieha import účtu…</string>
<string name="initial_sync_start_importing_account_crypto">Úvodná synchronizácia:
\nPrebieha import šifrovacích kľúčov</string>
<string name="initial_sync_start_importing_account_rooms">Úvodná synchronizácia:
\nPrebieha import miestností</string>
<string name="initial_sync_start_importing_account_joined_rooms">Úvodná synchronizácia:
\nPrebieha import miestností, do ktorých ste vstúpili</string>
<string name="initial_sync_start_importing_account_invited_rooms">Úvodná synchronizácia:
\nPrebieha import pozvánok</string>
<string name="initial_sync_start_importing_account_left_rooms">Úvodná synchronizácia:
\nPrebieha import opustených miestností</string>
<string name="initial_sync_start_importing_account_groups">Úvodná synchronizácia:
\nPrebieha import komunít</string>
<string name="initial_sync_start_importing_account_data">Úvodná synchronizácia:
\nPrebieha import údajov účtu</string>
<string name="event_status_sending_message">Odosielanie správy…</string>
<string name="clear_timeline_send_queue">Vymazať správy na odoslanie</string>
</resources> </resources>

View File

@ -146,4 +146,26 @@
<string name="verification_emoji_anchor">Spirancë</string> <string name="verification_emoji_anchor">Spirancë</string>
<string name="verification_emoji_headphone">Kufje</string> <string name="verification_emoji_headphone">Kufje</string>
<string name="verification_emoji_folder">Dosje</string> <string name="verification_emoji_folder">Dosje</string>
<string name="notice_room_update">%s e përmirësoi këtë dhomë.</string>
<string name="initial_sync_start_importing_account">Njëkohësimi Fillestar:
\nPo importohet llogaria…</string>
<string name="initial_sync_start_importing_account_crypto">Njëkohësimi Fillestar:
\nPo importohet kriptografi</string>
<string name="initial_sync_start_importing_account_rooms">Njëkohësimi Fillestar:
\nPo importohen Dhoma</string>
<string name="initial_sync_start_importing_account_joined_rooms">Njëkohësimi Fillestar:
\nPo importohen Dhoma Ku Është Bërë Hyrje</string>
<string name="initial_sync_start_importing_account_invited_rooms">Njëkohësimi Fillestar:
\nPo importohen Dhoma Me Ftesë</string>
<string name="initial_sync_start_importing_account_left_rooms">Njëkohësimi Fillestar:
\nPo importohen Dhoma të Braktisura</string>
<string name="initial_sync_start_importing_account_groups">Njëkohësimi Fillestar:
\nPo importohen Bashkësi</string>
<string name="initial_sync_start_importing_account_data">Njëkohësimi Fillestar:
\nPo importohet të Dhëna Llogarie</string>
<string name="event_status_sending_message">Po dërgohet mesazh…</string>
<string name="clear_timeline_send_queue">Spastro radhë pritjeje</string>
</resources> </resources>

View File

@ -48,7 +48,7 @@
<string name="notice_room_third_party_registered_invite">%1$s èt duutnodigienge vo %2$s anveird</string> <string name="notice_room_third_party_registered_invite">%1$s èt duutnodigienge vo %2$s anveird</string>
<string name="notice_crypto_unable_to_decrypt">** Kun nie ountsleuteln: %s **</string> <string name="notice_crypto_unable_to_decrypt">** Kun nie ountsleuteln: %s **</string>
<string name="notice_crypto_error_unkwown_inbound_session_id">t Toestel van den afzender èt geen sleutels vo dit bericht gesteurd.</string> <string name="notice_crypto_error_unkwown_inbound_session_id">t Toestel van den afzender èt geen sleutels vo da bericht hier gesteurd.</string>
<string name="message_reply_to_prefix">Als antwoord ip</string> <string name="message_reply_to_prefix">Als antwoord ip</string>
@ -150,21 +150,26 @@
<string name="verification_emoji_folder">Mappe</string> <string name="verification_emoji_folder">Mappe</string>
<string name="verification_emoji_pin">Pinne</string> <string name="verification_emoji_pin">Pinne</string>
<string name="initial_sync_start_importing_account">Initiële synchronisoatie: <string name="initial_sync_start_importing_account">Initiële synchronisoasje:
\nAccount wor geïmporteerd…</string> \nAccount wor geïmporteerd…</string>
<string name="initial_sync_start_importing_account_crypto">Initiële synchronisoatie: <string name="initial_sync_start_importing_account_crypto">Initiële synchronisoasje:
\nCrypto wor geïmporteerd</string> \nCrypto wor geïmporteerd</string>
<string name="initial_sync_start_importing_account_rooms">Initiële synchronisoatie: <string name="initial_sync_start_importing_account_rooms">Initiële synchronisoasje:
\nGesprekkn wordn geïmporteerd</string> \nGesprekkn wordn geïmporteerd</string>
<string name="initial_sync_start_importing_account_joined_rooms">Initiële synchronisoatie: <string name="initial_sync_start_importing_account_joined_rooms">Initiële synchronisoasje:
\nDeelgenoomn gesprekken wordn geïmporteerd</string> \nDeelgenoomn gesprekken wordn geïmporteerd</string>
<string name="initial_sync_start_importing_account_invited_rooms">Initiële synchronisoatie: <string name="initial_sync_start_importing_account_invited_rooms">Initiële synchronisoasje:
\nUutgenodigde gesprekkn wordn geïmporteerd</string> \nUutgenodigde gesprekkn wordn geïmporteerd</string>
<string name="initial_sync_start_importing_account_left_rooms">Initiële synchronisoatie: <string name="initial_sync_start_importing_account_left_rooms">Initiële synchronisoasje:
\nVerloatn gesprekkn wordn geïmporteerd</string> \nVerloatn gesprekkn wordn geïmporteerd</string>
<string name="initial_sync_start_importing_account_groups">Initiële synchronisoatie: <string name="initial_sync_start_importing_account_groups">Initiële synchronisoasje:
\nGemeenschappn wordn geïmporteerd</string> \nGemeenschappn wordn geïmporteerd</string>
<string name="initial_sync_start_importing_account_data">Initiële synchronisoatie: <string name="initial_sync_start_importing_account_data">Initiële synchronisoasje:
\nAccountgegeevns wordn geïmporteerd</string> \nAccountgegeevns wordn geïmporteerd</string>
<string name="notice_room_update">%s èt da gesprek hier ipgewoardeerd.</string>
<string name="event_status_sending_message">Bericht wor verstuurd…</string>
<string name="clear_timeline_send_queue">Uutgoande wachtreeke leegn</string>
</resources> </resources>

View File

@ -162,4 +162,9 @@
<string name="initial_sync_start_importing_account_data">初始化同步: <string name="initial_sync_start_importing_account_data">初始化同步:
\n正在导入账号数据</string> \n正在导入账号数据</string>
<string name="notice_room_update">%s 升级了聊天室。</string>
<string name="event_status_sending_message">正在发送消息…</string>
<string name="clear_timeline_send_queue">清除正在发送队列</string>
</resources> </resources>

View File

@ -165,4 +165,9 @@
<string name="initial_sync_start_importing_account_data">初始化同步: <string name="initial_sync_start_importing_account_data">初始化同步:
\n正在匯入帳號資料</string> \n正在匯入帳號資料</string>
<string name="notice_room_update">%s 已升級此聊天室。</string>
<string name="event_status_sending_message">正在傳送訊息……</string>
<string name="clear_timeline_send_queue">清除傳送佇列</string>
</resources> </resources>

View File

@ -3,4 +3,7 @@
<string name="no_network_indicator">There is no network connection right now</string>
</resources> </resources>

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
adb shell am start -a android.intent.action.VIEW -d "https://riot.im/config/config?hs_url=https%3A%2F%2Fmozilla-test.modular.im"

View File

@ -15,7 +15,7 @@ androidExtensions {
} }
ext.versionMajor = 0 ext.versionMajor = 0
ext.versionMinor = 3 ext.versionMinor = 5
ext.versionPatch = 0 ext.versionPatch = 0
static def getGitTimestamp() { static def getGitTimestamp() {
@ -51,8 +51,15 @@ static def gitRevisionDate() {
} }
static def gitBranchName() { static def gitBranchName() {
def cmd = "git name-rev --name-only HEAD" def fromEnv = System.env.BUILDKITE_BRANCH as String ?: ""
return cmd.execute().text.trim()
if (!fromEnv.isEmpty()) {
return fromEnv
} else {
// Note: this command return "HEAD" on Buildkite, so use the system env 'BUILDKITE_BRANCH' content first
def cmd = "git rev-parse --abbrev-ref HEAD"
return cmd.execute().text.trim()
}
} }
static def getVersionSuffix() { static def getVersionSuffix() {
@ -75,7 +82,7 @@ project.android.buildTypes.all { buildType ->
// 64 bits have greater value than 32 bits // 64 bits have greater value than 32 bits
ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].withDefault { 0 } ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].withDefault { 0 }
def buildNumber = System.getenv("BUILDKITE_BUILD_NUMBER") as Integer ?: 0 def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
android { android {
compileSdkVersion 28 compileSdkVersion 28
@ -273,6 +280,9 @@ dependencies {
implementation "ru.noties.markwon:html:$markwon_version" implementation "ru.noties.markwon:html:$markwon_version"
implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'me.saket:better-link-movement-method:2.2.0'
// Bus
implementation 'org.greenrobot:eventbus:3.1.1'
// Passphrase strength helper // Passphrase strength helper
implementation 'com.nulab-inc:zxcvbn:1.2.5' implementation 'com.nulab-inc:zxcvbn:1.2.5'
@ -318,6 +328,8 @@ dependencies {
implementation 'diff_match_patch:diff_match_patch:current' implementation 'diff_match_patch:diff_match_patch:current'
implementation "androidx.emoji:emoji-appcompat:1.0.0"
// TESTS // TESTS
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@ -65,6 +65,19 @@
<activity android:name=".features.home.room.detail.RoomDetailActivity" /> <activity android:name=".features.home.room.detail.RoomDetailActivity" />
<activity android:name=".features.debug.DebugMenuActivity" /> <activity android:name=".features.debug.DebugMenuActivity" />
<activity android:name=".features.home.createdirect.CreateDirectRoomActivity" /> <activity android:name=".features.home.createdirect.CreateDirectRoomActivity" />
<activity android:name=".features.webview.VectorWebViewActivity" />
<activity android:name=".features.link.LinkHandlerActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="riot.im" />
<data android:pathPrefix="/config/" />
</intent-filter>
</activity>
<!-- Services --> <!-- Services -->

View File

@ -349,6 +349,11 @@ SOFTWARE.
<br/> <br/>
Copyright 2018 The diff-match-patch Authors. https://github.com/google/diff-match-patch Copyright 2018 The diff-match-patch Authors. https://github.com/google/diff-match-patch
</li> </li>
<li>
<b>EventBus</b>
<br/>
Copyright (C) 2012-2017 Markus Junginger, greenrobot (http://greenrobot.org)
</li>
</ul> </ul>

View File

@ -0,0 +1,67 @@
/*
* 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.riotx
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import androidx.core.provider.FontRequest
import androidx.emoji.text.EmojiCompat
import androidx.emoji.text.FontRequestEmojiCompatConfig
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class EmojiCompatWrapper @Inject constructor(private val context: Context) {
private var initialized = false
fun init(fontRequest: FontRequest) {
//Use emoji compat for the benefit of emoji spans
val config = FontRequestEmojiCompatConfig(context, fontRequest)
// we want to replace all emojis with selected font
.setReplaceAll(true)
//Debug options
// .setEmojiSpanIndicatorEnabled(true)
// .setEmojiSpanIndicatorColor(Color.GREEN)
EmojiCompat.init(config)
.registerInitCallback(object : EmojiCompat.InitCallback() {
override fun onInitialized() {
Timber.v("Emoji compat onInitialized success ")
initialized = true
}
override fun onFailed(throwable: Throwable?) {
Timber.e(throwable, "Failed to init EmojiCompat")
}
})
}
fun safeEmojiSpanify(sequence: CharSequence): CharSequence {
if (initialized) {
try {
return EmojiCompat.get().process(sequence)
} catch (throwable: Throwable) {
//Defensive coding against error (should not happend as it is initialized)
Timber.e(throwable, "Failed to init EmojiCompat")
return sequence
}
} else {
return sequence
}
}
}

View File

@ -66,6 +66,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
@Inject lateinit var authenticator: Authenticator @Inject lateinit var authenticator: Authenticator
@Inject lateinit var vectorConfiguration: VectorConfiguration @Inject lateinit var vectorConfiguration: VectorConfiguration
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider @Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
@Inject lateinit var emojiCompatWrapper: EmojiCompatWrapper
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@ -85,9 +86,12 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
vectorComponent = DaggerVectorComponent.factory().create(this) vectorComponent = DaggerVectorComponent.factory().create(this)
vectorComponent.inject(this) vectorComponent.inject(this)
vectorUncaughtExceptionHandler.activate(this) vectorUncaughtExceptionHandler.activate(this)
// Log
VectorFileLogger.init(this) if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree(), VectorFileLogger) Timber.plant(Timber.DebugTree())
}
Timber.plant(vectorComponent.vectorFileLogger())
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this) Stetho.initializeWithDefaults(this)
} }
@ -105,6 +109,9 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
) )
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration() vectorConfiguration.initConfiguration()
emojiCompatWrapper.init(fontRequest)
NotificationUtils.createNotificationChannels(applicationContext) NotificationUtils.createNotificationChannels(applicationContext)
if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!! val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!!

View File

@ -21,7 +21,6 @@ import androidx.lifecycle.ViewModelProvider
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Component import dagger.Component
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.core.preference.UserAvatarPreference import im.vector.riotx.core.preference.UserAvatarPreference
import im.vector.riotx.features.MainActivity import im.vector.riotx.features.MainActivity
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyFragment import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyFragment
@ -42,15 +41,14 @@ import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFrag
import im.vector.riotx.features.home.group.GroupListFragment import im.vector.riotx.features.home.group.GroupListFragment
import im.vector.riotx.features.home.room.detail.RoomDetailFragment import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.riotx.features.home.room.detail.timeline.action.*
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuFragment
import im.vector.riotx.features.home.room.detail.timeline.action.QuickReactionFragment
import im.vector.riotx.features.home.room.detail.timeline.action.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.invite.VectorInviteView
import im.vector.riotx.features.link.LinkHandlerActivity
import im.vector.riotx.features.login.LoginActivity import im.vector.riotx.features.login.LoginActivity
import im.vector.riotx.features.login.LoginFragment
import im.vector.riotx.features.login.LoginSsoFallbackFragment
import im.vector.riotx.features.media.ImageMediaViewerActivity import im.vector.riotx.features.media.ImageMediaViewerActivity
import im.vector.riotx.features.media.VideoMediaViewerActivity import im.vector.riotx.features.media.VideoMediaViewerActivity
import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.navigation.Navigator
@ -58,26 +56,21 @@ import im.vector.riotx.features.rageshake.BugReportActivity
import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.BugReporter
import im.vector.riotx.features.rageshake.RageShake import im.vector.riotx.features.rageshake.RageShake
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
import im.vector.riotx.features.reactions.widget.ReactionButton
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.settings.*
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
import im.vector.riotx.features.settings.push.PushGatewaysFragment import im.vector.riotx.features.settings.push.PushGatewaysFragment
@Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class]) @Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class])
@ScreenScope @ScreenScope
interface ScreenComponent { interface ScreenComponent {
fun session(): Session fun activeSessionHolder(): ActiveSessionHolder
fun viewModelFactory(): ViewModelProvider.Factory fun viewModelFactory(): ViewModelProvider.Factory
@ -133,6 +126,10 @@ interface ScreenComponent {
fun inject(publicRoomsFragment: PublicRoomsFragment) fun inject(publicRoomsFragment: PublicRoomsFragment)
fun inject(loginFragment: LoginFragment)
fun inject(loginSsoFallbackFragment: LoginSsoFallbackFragment)
fun inject(sasVerificationIncomingFragment: SASVerificationIncomingFragment) fun inject(sasVerificationIncomingFragment: SASVerificationIncomingFragment)
fun inject(quickReactionFragment: QuickReactionFragment) fun inject(quickReactionFragment: QuickReactionFragment)
@ -141,6 +138,8 @@ interface ScreenComponent {
fun inject(loginActivity: LoginActivity) fun inject(loginActivity: LoginActivity)
fun inject(linkHandlerActivity: LinkHandlerActivity)
fun inject(mainActivity: MainActivity) fun inject(mainActivity: MainActivity)
fun inject(roomDirectoryActivity: RoomDirectoryActivity) fun inject(roomDirectoryActivity: RoomDirectoryActivity)
@ -181,6 +180,8 @@ interface ScreenComponent {
fun inject(displayReadReceiptsBottomSheet: DisplayReadReceiptsBottomSheet) fun inject(displayReadReceiptsBottomSheet: DisplayReadReceiptsBottomSheet)
fun inject(reactionButton: ReactionButton)
@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(vectorComponent: VectorComponent, fun create(vectorComponent: VectorComponent,

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.EmojiCompatWrapper
import im.vector.riotx.VectorApplication import im.vector.riotx.VectorApplication
import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.core.pushers.PushersManager
import im.vector.riotx.features.configuration.VectorConfiguration import im.vector.riotx.features.configuration.VectorConfiguration
@ -40,6 +41,7 @@ import im.vector.riotx.features.notifications.NotificationBroadcastReceiver
import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.BugReporter
import im.vector.riotx.features.rageshake.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import javax.inject.Singleton import javax.inject.Singleton
@ -70,6 +72,8 @@ interface VectorComponent {
fun emojiCompatFontProvider(): EmojiCompatFontProvider fun emojiCompatFontProvider(): EmojiCompatFontProvider
fun emojiCompatWrapper() : EmojiCompatWrapper
fun eventHtmlRenderer(): EventHtmlRenderer fun eventHtmlRenderer(): EventHtmlRenderer
fun navigator(): Navigator fun navigator(): Navigator
@ -98,6 +102,8 @@ interface VectorComponent {
fun vectorPreferences(): VectorPreferences fun vectorPreferences(): VectorPreferences
fun vectorFileLogger(): VectorFileLogger
@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(@BindsInstance context: Context): VectorComponent fun create(@BindsInstance context: Context): VectorComponent

View File

@ -26,9 +26,9 @@ private const val KEY_DIALOG_IS_DISPLAYED = "DialogLocker.KEY_DIALOG_IS_DISPLAYE
/** /**
* Class to avoid displaying twice the same dialog * Class to avoid displaying twice the same dialog
*/ */
class DialogLocker() : Restorable { class DialogLocker(savedInstanceState: Bundle?) : Restorable {
private var isDialogDisplayed: Boolean = false private var isDialogDisplayed = savedInstanceState?.getBoolean(KEY_DIALOG_IS_DISPLAYED, false) == true
private fun unlock() { private fun unlock() {
isDialogDisplayed = false isDialogDisplayed = false

View File

@ -17,6 +17,7 @@
package im.vector.riotx.core.error package im.vector.riotx.core.error
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import javax.inject.Inject import javax.inject.Inject
@ -34,8 +35,13 @@ class ErrorFormatter @Inject constructor(val stringProvider: StringProvider) {
null -> null null -> null
is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network) is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network)
is Failure.ServerError -> { is Failure.ServerError -> {
throwable.error.message.takeIf { it.isNotEmpty() } if (throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN) {
?: throwable.error.code.takeIf { it.isNotEmpty() } // Special case for terms and conditions
stringProvider.getString(R.string.error_terms_not_accepted)
} else {
throwable.error.message.takeIf { it.isNotEmpty() }
?: throwable.error.code.takeIf { it.isNotEmpty() }
}
} }
else -> throwable.localizedMessage else -> throwable.localizedMessage
} }

View File

@ -49,7 +49,7 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
@CallSuper @CallSuper
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
session = injector.session() session = injector.activeSessionHolder().getActiveSession()
} }
override fun initUiAndData() { override fun initUiAndData() {

View File

@ -36,11 +36,14 @@ import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxActivity import com.airbnb.mvrx.BaseMvRxActivity
import com.bumptech.glide.util.Util import com.bumptech.glide.util.Util
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import im.vector.matrix.android.api.failure.ConsentNotGivenError
import im.vector.riotx.BuildConfig import im.vector.riotx.BuildConfig
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.* import im.vector.riotx.core.di.*
import im.vector.riotx.core.dialogs.DialogLocker
import im.vector.riotx.core.utils.toast import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.configuration.VectorConfiguration import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.consent.ConsentNotGivenHelper
import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.navigation.Navigator
import im.vector.riotx.features.rageshake.BugReportActivity import im.vector.riotx.features.rageshake.BugReportActivity
import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.BugReporter
@ -50,6 +53,9 @@ import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.receivers.DebugReceiver import im.vector.riotx.receivers.DebugReceiver
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import timber.log.Timber import timber.log.Timber
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -73,6 +79,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
protected lateinit var bugReporter: BugReporter protected lateinit var bugReporter: BugReporter
private lateinit var rageShake: RageShake private lateinit var rageShake: RageShake
protected lateinit var navigator: Navigator protected lateinit var navigator: Navigator
private lateinit var activeSessionHolder: ActiveSessionHolder
private var unBinder: Unbinder? = null private var unBinder: Unbinder? = null
@ -127,6 +134,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
bugReporter = screenComponent.bugReporter() bugReporter = screenComponent.bugReporter()
rageShake = screenComponent.rageShake() rageShake = screenComponent.rageShake()
navigator = screenComponent.navigator() navigator = screenComponent.navigator()
activeSessionHolder = screenComponent.activeSessionHolder()
configurationViewModel.activityRestarter.observe(this, Observer { configurationViewModel.activityRestarter.observe(this, Observer {
if (!it.hasBeenHandled) { if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed // Recreate the Activity because configuration has changed
@ -175,7 +183,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
configurationViewModel.onActivityResumed() configurationViewModel.onActivityResumed()
if (this !is BugReportActivity) { if (this !is BugReportActivity) {
rageShake?.start() rageShake.start()
} }
DebugReceiver DebugReceiver
@ -190,7 +198,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
rageShake?.stop() rageShake.stop()
debugReceiver?.let { debugReceiver?.let {
unregisterReceiver(debugReceiver) unregisterReceiver(debugReceiver)
@ -265,18 +273,21 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
protected fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean { override fun onBackPressed() {
// if (fm.backStackEntryCount == 0) val handled = recursivelyDispatchOnBackPressed(supportFragmentManager)
// return false if (!handled) {
super.onBackPressed()
}
}
val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed() private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean {
val reverseOrder = fm.fragments.filter { it is VectorBaseFragment }.reversed()
for (f in reverseOrder) { for (f in reverseOrder) {
val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager) val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager)
if (handledByChildFragments) { if (handledByChildFragments) {
return true return true
} }
val backPressable = f as OnBackPressed if (f is OnBackPressed && f.onBackPressed()) {
if (backPressable.onBackPressed()) {
return true return true
} }
} }
@ -388,6 +399,31 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
} }
} }
/* ==========================================================================================
* User Consent
* ========================================================================================== */
private val consentNotGivenHelper by lazy {
ConsentNotGivenHelper(this, DialogLocker(savedInstanceState))
.apply { restorables.add(this) }
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onConsentNotGivenError(consentNotGivenError: ConsentNotGivenError) {
consentNotGivenHelper.displayDialog(consentNotGivenError.consentUri,
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "")
}
/* ========================================================================================== /* ==========================================================================================
* Temporary method * Temporary method
* ========================================================================================== */ * ========================================================================================== */
@ -399,5 +435,4 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
toast(getString(R.string.not_implemented)) toast(getString(R.string.not_implemented))
} }
} }
} }

View File

@ -19,11 +19,7 @@ package im.vector.riotx.core.platform
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.*
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.MainThread import androidx.annotation.MainThread
@ -42,7 +38,7 @@ import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import timber.log.Timber import timber.log.Timber
abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasScreenInjector { abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
// Butterknife unbinder // Butterknife unbinder
private var mUnBinder: Unbinder? = null private var mUnBinder: Unbinder? = null
@ -132,10 +128,6 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasScreen
super.onViewStateRestored(savedInstanceState) super.onViewStateRestored(savedInstanceState)
} }
override fun onBackPressed(): Boolean {
return false
}
override fun invalidate() { override fun invalidate() {
//no-ops by default //no-ops by default
Timber.w("invalidate() method has not been implemented") Timber.w("invalidate() method has not been implemented")

View File

@ -17,11 +17,8 @@
package im.vector.riotx.core.platform package im.vector.riotx.core.platform
import com.airbnb.mvrx.* import com.airbnb.mvrx.*
import im.vector.matrix.android.api.util.CancelableBag
import im.vector.riotx.BuildConfig
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.disposables.Disposable
abstract class VectorViewModel<S : MvRxState>(initialState: S) abstract class VectorViewModel<S : MvRxState>(initialState: S)
: BaseMvRxViewModel<S>(initialState, false) { : BaseMvRxViewModel<S>(initialState, false) {

View File

@ -21,11 +21,8 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import butterknife.ButterKnife
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import kotlinx.android.synthetic.main.view_read_receipts.view.* import kotlinx.android.synthetic.main.view_read_receipts.view.*
@ -48,7 +45,6 @@ class ReadReceiptsView @JvmOverloads constructor(
private fun setupView() { private fun setupView() {
inflate(context, R.layout.view_read_receipts, this) inflate(context, R.layout.view_read_receipts, this)
ButterKnife.bind(this)
} }
fun render(readReceipts: List<ReadReceiptData>, avatarRenderer: AvatarRenderer, clickListener: OnClickListener) { fun render(readReceipts: List<ReadReceiptData>, avatarRenderer: AvatarRenderer, clickListener: OnClickListener) {

View File

@ -0,0 +1,32 @@
/*
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.utils
import java.lang.ref.WeakReference
import kotlin.reflect.KProperty
fun <T> weak(value: T) = WeakReferenceDelegate(value)
class WeakReferenceDelegate<T>(value: T) {
private var weakReference: WeakReference<T> = WeakReference(value)
operator fun getValue(thisRef: Any, property: KProperty<*>): T? = weakReference.get()
operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
weakReference = WeakReference(value)
}
}

View File

@ -19,12 +19,15 @@ package im.vector.riotx.features
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.utils.deleteAllFiles import im.vector.riotx.core.utils.deleteAllFiles
import im.vector.riotx.features.home.HomeActivity import im.vector.riotx.features.home.HomeActivity
@ -57,6 +60,7 @@ class MainActivity : VectorBaseActivity() {
@Inject lateinit var matrix: Matrix @Inject lateinit var matrix: Matrix
@Inject lateinit var authenticator: Authenticator @Inject lateinit var authenticator: Authenticator
@Inject lateinit var sessionHolder: ActiveSessionHolder @Inject lateinit var sessionHolder: ActiveSessionHolder
@Inject lateinit var errorFormatter: ErrorFormatter
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
@ -69,42 +73,68 @@ class MainActivity : VectorBaseActivity() {
// Handle some wanted cleanup // Handle some wanted cleanup
if (clearCache || clearCredentials) { if (clearCache || clearCredentials) {
GlobalScope.launch(Dispatchers.Main) { doCleanUp(clearCache, clearCredentials)
// On UI Thread } else {
Glide.get(this@MainActivity).clearMemory() start()
withContext(Dispatchers.IO) {
// On BG thread
Glide.get(this@MainActivity).clearDiskCache()
// Also clear cache (Logs, etc...)
deleteAllFiles(this@MainActivity.cacheDir)
}
}
} }
}
private fun doCleanUp(clearCache: Boolean, clearCredentials: Boolean) {
when { when {
clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback<Unit> { clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: success, start app") Timber.w("SIGN_OUT: success, start app")
sessionHolder.clearActiveSession() sessionHolder.clearActiveSession()
start() doLocalCleanupAndStart()
}
override fun onFailure(failure: Throwable) {
displayError(failure, clearCache, clearCredentials)
} }
}) })
clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback<Unit> { clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
start() doLocalCleanupAndStart()
}
override fun onFailure(failure: Throwable) {
displayError(failure, clearCache, clearCredentials)
} }
}) })
else -> start()
} }
} }
private fun doLocalCleanupAndStart() {
GlobalScope.launch(Dispatchers.Main) {
// On UI Thread
Glide.get(this@MainActivity).clearMemory()
withContext(Dispatchers.IO) {
// On BG thread
Glide.get(this@MainActivity).clearDiskCache()
// Also clear cache (Logs, etc...)
deleteAllFiles(this@MainActivity.cacheDir)
}
}
start()
}
private fun displayError(failure: Throwable, clearCache: Boolean, clearCredentials: Boolean) {
AlertDialog.Builder(this)
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(failure))
.setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp(clearCache, clearCredentials) }
.setNegativeButton(R.string.cancel) { _, _ -> start() }
.setCancelable(false)
.show()
}
private fun start() { private fun start() {
val intent = if (sessionHolder.hasActiveSession()) { val intent = if (sessionHolder.hasActiveSession()) {
HomeActivity.newIntent(this) HomeActivity.newIntent(this)
} else { } else {
LoginActivity.newIntent(this) LoginActivity.newIntent(this, null)
} }
startActivity(intent) startActivity(intent)
finish() finish()

View File

@ -0,0 +1,57 @@
/*
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.consent
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import im.vector.riotx.R
import im.vector.riotx.core.dialogs.DialogLocker
import im.vector.riotx.core.platform.Restorable
import im.vector.riotx.features.webview.VectorWebViewActivity
import im.vector.riotx.features.webview.WebViewMode
class ConsentNotGivenHelper(private val activity: Activity,
private val dialogLocker: DialogLocker) :
Restorable by dialogLocker {
/* ==========================================================================================
* Public methods
* ========================================================================================== */
/**
* Display the consent dialog, if not already displayed
*/
fun displayDialog(consentUri: String, homeServerHost: String) {
dialogLocker.displayDialog {
AlertDialog.Builder(activity)
.setTitle(R.string.settings_app_term_conditions)
.setMessage(activity.getString(R.string.dialog_user_consent_content, homeServerHost))
.setPositiveButton(R.string.dialog_user_consent_submit) { _, _ ->
openWebViewActivity(consentUri)
}
}
}
/* ==========================================================================================
* Private
* ========================================================================================== */
private fun openWebViewActivity(consentUri: String) {
val intent = VectorWebViewActivity.getIntent(activity, consentUri, activity.getString(R.string.settings_app_term_conditions), WebViewMode.CONSENT)
activity.startActivity(intent)
}
}

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