diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 681f41ae2aee4749eb4ddda94f8c6a76c825c825..0d156937bbbd8ca9d099f9cda685e7b13c6a64c2 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,23 @@ <component name="ProjectCodeStyleConfiguration"> <code_scheme name="Project" version="173"> + <JetCodeStyleSettings> + <option name="PACKAGES_TO_USE_STAR_IMPORTS"> + <value> + <package name="java.util" alias="false" withSubpackages="false" /> + <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" /> + <package name="io.ktor" alias="false" withSubpackages="true" /> + </value> + </option> + <option name="PACKAGES_IMPORT_LAYOUT"> + <value> + <package name="" alias="false" withSubpackages="true" /> + <package name="java" alias="false" withSubpackages="true" /> + <package name="javax" alias="false" withSubpackages="true" /> + <package name="kotlin" alias="false" withSubpackages="true" /> + <package name="" alias="true" withSubpackages="true" /> + </value> + </option> + </JetCodeStyleSettings> <codeStyleSettings language="XML"> <indentOptions> <option name="CONTINUATION_INDENT_SIZE" value="4" /> diff --git a/build.gradle b/build.gradle index 44384b9f7d376074b501b21e61d85e783ffdec45..2991170b821817855c4d8fa474a60b8fe079fece 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,10 @@ allprojects { includeGroupByRegex "com\\.github\\.Zhuinden" } } + // Jitsi repo + maven { + url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-2.9.3" + } google() jcenter() } diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index a96e0690dc1868e744841715d8d26661a27761ee..29a8b67550418884d30dcee7d5c303f24afc54a5 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -26,7 +26,7 @@ android { minSdkVersion 21 targetSdkVersion 29 versionCode 1 - versionName "0.0.1" + versionName "1.0.5" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -132,8 +132,13 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" implementation "com.squareup.retrofit2:converter-scalars:$retrofit_version" - implementation 'com.squareup.okhttp3:okhttp:4.2.2' - implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' + + + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1")) + implementation 'com.squareup.okhttp3:okhttp' + implementation 'com.squareup.okhttp3:logging-interceptor' + implementation 'com.squareup.okhttp3:okhttp-urlconnection' + implementation "com.squareup.moshi:moshi-adapters:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" @@ -174,8 +179,10 @@ dependencies { implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23' // Web RTC - // TODO meant for development purposes only. See http://webrtc.github.io/webrtc-org/native-code/android/ - implementation 'org.webrtc:google-webrtc:1.0.+' + // org.webrtc:google-webrtc is for development purposes only. See http://webrtc.github.io/webrtc-org/native-code/android/ + // implementation 'org.webrtc:google-webrtc:1.0.+' + // Use the same WebRTC library than the one used by Jitsi library + implementation('com.facebook.react:react-native-webrtc:1.84.0-jitsi-5112273@aar') debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0' releaseImplementation 'com.airbnb.okreplay:noop:1.5.0' diff --git a/matrix-sdk-android/lint.xml b/matrix-sdk-android/lint.xml index 3e4078d7d979c7d2db009e6a81a6744afc67ba7f..134aba822b4d2ea00779c1a5161d57b5a1719217 100644 --- a/matrix-sdk-android/lint.xml +++ b/matrix-sdk-android/lint.xml @@ -29,5 +29,6 @@ <issue id="SetTextI18n" severity="error" /> <issue id="ViewConstructor" severity="error" /> <issue id="UseValueOf" severity="error" /> + <issue id="ObsoleteSdkInt" severity="error" /> </lint> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt index a4bd8badd754de6c22024f183072ad1a6a6f559e..b10cae6171a08d7228f2760bd18113af59980d1c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt @@ -42,6 +42,9 @@ import org.matrix.android.sdk.api.util.JsonDict * } * ] * } + * "im.vector.riot.jitsi": { + * "preferredDomain": "https://jitsi.riot.im/" + * } * } * </pre> */ @@ -57,7 +60,10 @@ data class WellKnown( val integrations: JsonDict? = null, @Json(name = "im.vector.riot.e2ee") - val e2eAdminSetting: E2EWellKnownConfig? = null + val e2eAdminSetting: E2EWellKnownConfig? = null, + + @Json(name = "im.vector.riot.jitsi") + val jitsiServer: WellKnownPreferredConfig? = null ) @@ -66,3 +72,9 @@ data class E2EWellKnownConfig( @Json(name = "default") val e2eDefault: Boolean = true ) + +@JsonClass(generateAdapter = true) +data class WellKnownPreferredConfig( + @Json(name = "preferredDomain") + val preferredDomain: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 95370c01880228d1a37dfcc7d7ae2dfa2529d649..cfddf73363cda80472e784ed55632794553819ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session import androidx.annotation.MainThread import androidx.lifecycle.LiveData +import okhttp3.OkHttpClient import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.pushrules.PushRuleService @@ -35,6 +36,7 @@ import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -48,7 +50,6 @@ import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.widgets.WidgetService -import okhttp3.OkHttpClient /** * This interface defines interactions with a session. @@ -195,6 +196,11 @@ interface Session : */ fun fileService(): FileService + /** + * Returns the permalink service associated with the session + */ + fun permalinkService(): PermalinkService + /** * Add a listener to the session. * @param listener the listener to add. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index c463fe9e72d77907a4ea317c6321989e2cc60c4b..de7ac45bf364943e338b043567719ceb21761e11 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -38,7 +38,11 @@ data class HomeServerCapabilities( * Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms * (as it was before) for various environments where this is desired. */ - val adminE2EByDefault: Boolean = true + val adminE2EByDefault: Boolean = true, + /** + * Preferred Jitsi domain, provided in Wellknown + */ + val preferredJitsiDomain: String? = null ) { companion object { const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/MatrixLinkify.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt index b7e719d6db6dfc5ef2bb1e2bf5962f3fdd8e26ee..8927f06c779e9d7f28d8bbe6361efeb90b47b813 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.permalinks +package org.matrix.android.sdk.api.session.permalinks import android.text.Spannable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/MatrixPermalinkSpan.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt similarity index 89% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/MatrixPermalinkSpan.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt index 15957d359a0da5da05c1516f81dc8353b3d286da..a66093064677e2e167e0ee40e04748fb27120161 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/MatrixPermalinkSpan.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt @@ -15,11 +15,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.permalinks +package org.matrix.android.sdk.api.session.permalinks import android.text.style.ClickableSpan import android.view.View -import org.matrix.android.sdk.api.permalinks.MatrixPermalinkSpan.Callback +import org.matrix.android.sdk.api.session.permalinks.MatrixPermalinkSpan.Callback /** * This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkData.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt index 3955c850c5937deb6d27da9131a804fcbae91e58..85632d6e83d39d3cd25eb026ceb71f9364467410 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.permalinks +package org.matrix.android.sdk.api.session.permalinks import android.net.Uri diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkParser.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt index 4cf9331f0e56c64f61960a48f6f1c361a2427564..dd6847f1e368d04f33cae94068957fd04646f4bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.permalinks +package org.matrix.android.sdk.api.session.permalinks import android.net.Uri import org.matrix.android.sdk.api.MatrixPatterns @@ -37,7 +37,7 @@ object PermalinkParser { * Turns an uri to a [PermalinkData] */ fun parse(uri: Uri): PermalinkData { - if (!uri.toString().startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) { + if (!uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)) { return PermalinkData.FallbackLink(uri) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt similarity index 57% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkFactory.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt index 87b42f5ae8bd815aa97c755bf3c007ceac083566..804cde5b309204faffa73132114dfa44454d3805 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt @@ -15,55 +15,58 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.permalinks +package org.matrix.android.sdk.api.session.permalinks import org.matrix.android.sdk.api.session.events.model.Event /** * Useful methods to create Matrix permalink (matrix.to links). */ -object PermalinkFactory { +interface PermalinkService { - const val MATRIX_TO_URL_BASE = "https://matrix.to/#/" + companion object { + const val MATRIX_TO_URL_BASE = "https://matrix.to/#/" + } /** * Creates a permalink for an event. * Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org" * * @param event the event + * * @return the permalink, or null in case of error */ - fun createPermalink(event: Event): String? { - if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) { - return null - } - return createPermalink(event.roomId, event.eventId) - } + fun createPermalink(event: Event): String? /** - * Creates a permalink for an id (can be a user Id, Room Id, etc.). + * Creates a permalink for an id (can be a user Id, etc.). + * For a roomId, consider using [createRoomPermalink] * Ex: "https://matrix.to/#/@benoit:matrix.org" * * @param id the id * @return the permalink, or null in case of error */ - fun createPermalink(id: String): String? { - return if (id.isEmpty()) { - null - } else MATRIX_TO_URL_BASE + escape(id) - } + fun createPermalink(id: String): String? /** - * Creates a permalink for an event. If you have an event you can use [.createPermalink] - * Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org" + * Creates a permalink for a roomId, including the via parameters + * + * @param roomId the room id + * + * @return the permalink, or null in case of error + */ + fun createRoomPermalink(roomId: String): String? + + /** + * Creates a permalink for an event. If you have an event you can use [createPermalink] + * Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org?via=matrix.org" * * @param roomId the id of the room * @param eventId the id of the event + * * @return the permalink */ - fun createPermalink(roomId: String, eventId: String): String { - return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) - } + fun createPermalink(roomId: String, eventId: String): String /** * Extract the linked id from the universal link @@ -71,31 +74,5 @@ object PermalinkFactory { * @param url the universal link, Ex: "https://matrix.to/#/@benoit:matrix.org" * @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink */ - fun getLinkedId(url: String): String? { - val isSupported = url.startsWith(MATRIX_TO_URL_BASE) - - return if (isSupported) { - url.substring(MATRIX_TO_URL_BASE.length) - } else null - } - - /** - * Escape '/' in id, because it is used as a separator - * - * @param id the id to escape - * @return the escaped id - */ - internal fun escape(id: String): String { - return id.replace("/", "%2F") - } - - /** - * Unescape '/' in id - * - * @param id the id to escape - * @return the escaped id - */ - internal fun unescape(id: String): String { - return id.replace("%2F", "/") - } + fun getLinkedId(url: String): String? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt index d70dae3454936b6ce59b9bc76a46fef814336cb8..508cf5ea1d4e50b813c2f018d3679be7b4f060b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt @@ -18,8 +18,9 @@ package org.matrix.android.sdk.api.session.room.failure import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixError sealed class CreateRoomFailure : Failure.FeatureFailure() { - - object CreatedWithTimeout: CreateRoomFailure() + object CreatedWithTimeout : CreateRoomFailure() + data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt index 00eb7e859969026dfe8606a7cec8b9bd5a56e553..d0c02b6027457747a2e69b0d06d25375f0382c66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt @@ -42,6 +42,11 @@ import retrofit2.http.Url * The login REST API. */ internal interface AuthAPI { + /** + * Get a Riot config file, using the name including the domain + */ + @GET("config.{domain}.json") + fun getRiotConfigDomain(@Path("domain") domain: String): Call<RiotConfig> /** * Get a Riot config file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 1294855b6edef86a7302fa3438a0758b49e90c2f..02c48dab07ba5b7c37edb02dbfc6fe026bb9fc2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -19,6 +19,9 @@ package org.matrix.android.sdk.internal.auth import android.net.Uri import dagger.Lazy +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.Credentials @@ -50,12 +53,8 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.util.exhaustive import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.wellknown.GetWellknownTask -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient import javax.inject.Inject import javax.net.ssl.HttpsURLConnection @@ -157,7 +156,7 @@ internal class DefaultAuthenticationService @Inject constructor( if (it is Failure.OtherServerError && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { // It's maybe a Riot url? - getRiotLoginFlowInternal(homeServerConnectionConfig) + getRiotDomainLoginFlowInternal(homeServerConnectionConfig) } else { throw it } @@ -166,6 +165,37 @@ internal class DefaultAuthenticationService @Inject constructor( } } + private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { + val authAPI = buildAuthAPI(homeServerConnectionConfig) + + val domain = homeServerConnectionConfig.homeServerUri.host + ?: return getRiotLoginFlowInternal(homeServerConnectionConfig) + + // Ok, try to get the config.domain.json file of a RiotWeb client + return runCatching { + executeRequest<RiotConfig>(null) { + apiCall = authAPI.getRiotConfigDomain(domain) + } + } + .map { riotConfig -> + onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) + } + .fold( + { + it + }, + { + if (it is Failure.OtherServerError + && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // Try with config.json + getRiotLoginFlowInternal(homeServerConnectionConfig) + } else { + throw it + } + } + ) + } + private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) @@ -176,23 +206,7 @@ internal class DefaultAuthenticationService @Inject constructor( } } .map { riotConfig -> - if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) { - // Ok, good sign, we got a default hs url - val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl) - ) - - val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - - val versions = executeRequest<Versions>(null) { - apiCall = newAuthAPI.versions() - } - - getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl) - } else { - // Config exists, but there is no default homeserver url (ex: https://riot.im/app) - throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) - } + onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) } .fold( { @@ -210,6 +224,27 @@ internal class DefaultAuthenticationService @Inject constructor( ) } + private suspend fun onRiotConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, riotConfig: RiotConfig): LoginFlowResult { + val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl() + if (defaultHomeServerUrl?.isNotEmpty() == true) { + // Ok, good sign, we got a default hs url + val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( + homeServerUri = Uri.parse(defaultHomeServerUrl) + ) + + val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) + + val versions = executeRequest<Versions>(null) { + apiCall = newAuthAPI.versions() + } + + return getLoginFlowResult(newAuthAPI, versions, defaultHomeServerUrl) + } else { + // Config exists, but there is no default homeserver url (ex: https://riot.im/app) + throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) + } + } + private suspend fun getWellknownLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val domain = homeServerConnectionConfig.homeServerUri.host ?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) @@ -234,7 +269,7 @@ internal class DefaultAuthenticationService @Inject constructor( getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl) } else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) - }.exhaustive + } } private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt index 42db3152620f85f7d140c6b457390b96a0dc84d1..9fb89638128bae9e6d6b977f64438d67f5afff99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt @@ -21,9 +21,31 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class RiotConfig( - // There are plenty of other elements in the file config.json of a RiotWeb client, but for the moment only one is interesting - // Ex: "brand", "branding", etc. +internal data class RiotConfig( + /** + * This is now deprecated, but still used first, rather than value from "default_server_config" + */ @Json(name = "default_hs_url") - val defaultHomeServerUrl: String? + val defaultHomeServerUrl: String?, + + @Json(name = "default_server_config") + val defaultServerConfig: RiotConfigDefaultServerConfig? +) { + fun getPreferredHomeServerUrl(): String? { + return defaultHomeServerUrl + ?.takeIf { it.isNotEmpty() } + ?: defaultServerConfig?.homeServer?.baseURL + } +} + +@JsonClass(generateAdapter = true) +internal data class RiotConfigDefaultServerConfig( + @Json(name = "m.homeserver") + val homeServer: RiotConfigBaseConfig? = null +) + +@JsonClass(generateAdapter = true) +internal data class RiotConfigBaseConfig( + @Json(name = "base_url") + val baseURL: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt index 98098686cc8aa4507c735eee86b8896a8040719e..978c82303ecc8623f7c708669874a7a0e149bd7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.store.db import android.util.Base64 -import org.matrix.android.sdk.internal.util.CompatUtil import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObject @@ -26,6 +25,7 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream /** * Get realm, invoke the action, close realm, and return the result of the action @@ -78,7 +78,7 @@ fun serializeForRealm(o: Any?): String? { } val baos = ByteArrayOutputStream() - val gzis = CompatUtil.createGzipOutputStream(baos) + val gzis = GZIPOutputStream(baos) val out = ObjectOutputStream(gzis) out.use { it.writeObject(o) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 195116cfe6cc032633365ccf203a4ab8d2ae91e2..7d2a4ea581cf928e653825b2f8198bdd491919fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.database -import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import io.realm.DynamicRealm import io.realm.RealmMigration +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import timber.log.Timber import javax.inject.Inject @@ -31,6 +31,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 0) migrateTo1(realm) if (oldVersion <= 1) migrateTo2(realm) + if (oldVersion <= 2) migrateTo3(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -52,4 +53,14 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true) } } + + private fun migrateTo3(realm: DynamicRealm) { + Timber.d("Step 2 -> 3") + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.PREFERRED_JITSI_DOMAIN, String::class.java) + ?.transform { obj -> + // Schedule a refresh of the capabilities + obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index e53240c5b86b3cdd94e77f898e2a90ee7c2857f1..456eecc54a9c7c27fd33fc32bc42cff6f1b4046d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -47,7 +47,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( context: Context) { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 2L + const val SESSION_STORE_SCHEMA_VERSION = 3L } // Keep legacy preferences name for compatibility reason diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 8d38c3fbe54f98e45f52ba0dd7046070af6db25f..e5de271d9328b7b9c87c39fdf5ad986445a451fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -31,7 +31,8 @@ internal object HomeServerCapabilitiesMapper { maxUploadFileSize = entity.maxUploadFileSize, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, defaultIdentityServerUrl = entity.defaultIdentityServerUrl, - adminE2EByDefault = entity.adminE2EByDefault + adminE2EByDefault = entity.adminE2EByDefault, + preferredJitsiDomain = entity.preferredJitsiDomain ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 3b74f053b6b3931e1fed7737fb4f8db6231225c2..7e3af69436e2355d79f200dd5bf4d16c76c9954b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -26,7 +26,8 @@ internal open class HomeServerCapabilitiesEntity( var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, var adminE2EByDefault: Boolean = true, - var lastUpdatedTimestamp: Long = 0L + var lastUpdatedTimestamp: Long = 0L, + var preferredJitsiDomain: String? = null ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index 71961d02d3191d8c6c1f4cd0da7e9da9e1a2bf5d..5fff658a5682c223f6842d63a9ab2d90b55df46f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -77,7 +77,11 @@ internal object NetworkModule { .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) - .addNetworkInterceptor(stethoInterceptor) + .apply { + if (BuildConfig.DEBUG) { + addNetworkInterceptor(stethoInterceptor) + } + } .addInterceptor(timeoutInterceptor) .addInterceptor(userAgentInterceptor) .addInterceptor(httpLoggingInterceptor) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt index 1f3e77d80007e65b213e14b0314538c897692988..ae55324d53371987f5a31ddb3fbb107cd32dc26c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt @@ -75,9 +75,7 @@ internal class DefaultNetworkConnectivityChecker @Inject constructor(private val override fun register(listener: NetworkConnectivityChecker.Listener) { if (listeners.isEmpty()) { - if (backgroundDetectionObserver.isInBackground) { - unbind() - } else { + if (!backgroundDetectionObserver.isInBackground) { bind() } backgroundDetectionObserver.register(backgroundDetectionObserverListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index eb4b475b4d90056041f2bef0118cf0c217e76e3f..f8ba6259478c0a708adea3ad23dc107cd8d3a2fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -19,6 +19,13 @@ package org.matrix.android.sdk.internal.session import androidx.annotation.MainThread import dagger.Lazy +import io.realm.RealmConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError @@ -37,6 +44,7 @@ import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -63,13 +71,6 @@ import org.matrix.android.sdk.internal.session.sync.job.SyncWorker import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.createUIHandler -import io.realm.RealmConfiguration -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import okhttp3.OkHttpClient -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode import timber.log.Timber import javax.inject.Inject import javax.inject.Provider @@ -96,6 +97,7 @@ internal class DefaultSession @Inject constructor( private val termsService: Lazy<TermsService>, private val cryptoService: Lazy<DefaultCryptoService>, private val defaultFileService: Lazy<FileService>, + private val permalinkService: Lazy<PermalinkService>, private val secureStorageService: Lazy<SecureStorageService>, private val profileService: Lazy<ProfileService>, private val widgetService: Lazy<WidgetService>, @@ -254,6 +256,8 @@ internal class DefaultSession @Inject constructor( override fun fileService(): FileService = defaultFileService.get() + override fun permalinkService(): PermalinkService = permalinkService.get() + override fun widgetService(): WidgetService = widgetService.get() override fun integrationManagerService() = integrationManagerService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 1c1804ba5f276f839d87f2f8c5b2c4bd9be99e2c..d404cecc51f214089d1434c98739266e5421a513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -25,6 +25,9 @@ import dagger.Lazy import dagger.Module import dagger.Provides import dagger.multibindings.IntoSet +import io.realm.RealmConfiguration +import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -35,6 +38,7 @@ import org.matrix.android.sdk.api.session.InitialSyncProgressService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker @@ -72,6 +76,7 @@ import org.matrix.android.sdk.internal.session.download.DownloadProgressIntercep import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager +import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor @@ -81,9 +86,6 @@ import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import org.matrix.android.sdk.internal.session.user.accountdata.DefaultAccountDataService import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter import org.matrix.android.sdk.internal.util.md5 -import io.realm.RealmConfiguration -import okhttp3.OkHttpClient -import org.greenrobot.eventbus.EventBus import retrofit2.Retrofit import java.io.File import javax.inject.Provider @@ -356,6 +358,9 @@ internal abstract class SessionModule { @Binds abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService + @Binds + abstract fun bindPermalinkService(service: DefaultPermalinkService): PermalinkService + @Binds abstract fun bindTypingUsersTracker(tracker: DefaultTypingUsersTracker): TypingUsersTracker } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt index 0c1a129733d319e434007cdc358e1550b7ba94e5..6a17f0e92565134ca67de52d55b6b173357aa637 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt @@ -58,18 +58,32 @@ internal class DefaultCallSignalingService @Inject constructor( private val activeCalls = mutableListOf<MxCall>() - private var cachedTurnServerResponse: TurnServerResponse? = null + private val cachedTurnServerResponse = object { + + private val MIN_TTL = 60 + + private val now = { System.currentTimeMillis() / 1000 } + + private var expiresAt: Long = 0 + + var data: TurnServerResponse? = null + get() = if (expiresAt > now()) field else null + set(value) { + expiresAt = now() + (value?.ttl ?: 0) - MIN_TTL + field = value + } + } override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable { - if (cachedTurnServerResponse != null) { - cachedTurnServerResponse?.let { callback.onSuccess(it) } + if (cachedTurnServerResponse.data != null) { + cachedTurnServerResponse.data?.let { callback.onSuccess(it) } return NoOpCancellable } return turnServerTask .configureWith(GetTurnServerTask.Params) { this.callback = object : MatrixCallback<TurnServerResponse> { override fun onSuccess(data: TurnServerResponse) { - cachedTurnServerResponse = data + cachedTurnServerResponse.data = data callback.onSuccess(data) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt index 798d7ceee032a538dd9be880d1f68186422d83a4..5e5380fce1360e67ea52ec901efc28afe1df9cba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt @@ -20,11 +20,6 @@ package org.matrix.android.sdk.internal.session.content import android.content.Context import android.net.Uri import com.squareup.moshi.Moshi -import org.matrix.android.sdk.api.session.content.ContentUrlResolver -import org.matrix.android.sdk.internal.di.Authenticated -import org.matrix.android.sdk.internal.network.ProgressRequestBody -import org.matrix.android.sdk.internal.network.awaitResponse -import org.matrix.android.sdk.internal.network.toFailure import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -35,6 +30,11 @@ import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.session.content.ContentUrlResolver +import org.matrix.android.sdk.internal.di.Authenticated +import org.matrix.android.sdk.internal.network.ProgressRequestBody +import org.matrix.android.sdk.internal.network.awaitResponse +import org.matrix.android.sdk.internal.network.toFailure import java.io.File import java.io.FileNotFoundException import java.io.IOException @@ -70,15 +70,14 @@ internal class FileUploader @Inject constructor(@Authenticated filename: String?, mimeType: String?, progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { - val inputStream = withContext(Dispatchers.IO) { - context.contentResolver.openInputStream(uri) - } ?: throw FileNotFoundException() + return withContext(Dispatchers.IO) { + val inputStream = context.contentResolver.openInputStream(uri) ?: throw FileNotFoundException() - inputStream.use { - return uploadByteArray(it.readBytes(), filename, mimeType, progressListener) + inputStream.use { + uploadByteArray(it.readBytes(), filename, mimeType, progressListener) + } } } - private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse { val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index 0f9d9548d226514591e795924a350d7dc91309d4..13ce84cf902577e3fabd08fb40268ab6cf82eddc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -110,6 +110,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true + homeServerCapabilitiesEntity.preferredJitsiDomain = getWellknownResult.wellKnown.jitsiServer?.preferredDomain // We are also checking for integration manager configurations val config = configExtractor.extract(getWellknownResult.wellKnown) if (config != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt new file mode 100644 index 0000000000000000000000000000000000000000..cef1fce05ce8aa57b3478fa234dbaa14c7a25781 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.permalinks + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE +import javax.inject.Inject + +internal class DefaultPermalinkService @Inject constructor( + private val permalinkFactory: PermalinkFactory +) : PermalinkService { + + override fun createPermalink(event: Event): String? { + return permalinkFactory.createPermalink(event) + } + + override fun createPermalink(id: String): String? { + return permalinkFactory.createPermalink(id) + } + + override fun createRoomPermalink(roomId: String): String? { + return permalinkFactory.createRoomPermalink(roomId) + } + + override fun createPermalink(roomId: String, eventId: String): String { + return permalinkFactory.createPermalink(roomId, eventId) + } + + override fun getLinkedId(url: String): String? { + return url + .takeIf { it.startsWith(MATRIX_TO_URL_BASE) } + ?.substring(MATRIX_TO_URL_BASE.length) + ?.substringBeforeLast("?") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..3ec8fe8397fa720a1305c5ac411f7e563a46164d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.permalinks + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE +import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.RoomGetter +import java.net.URLEncoder +import javax.inject.Inject +import javax.inject.Provider + +internal class PermalinkFactory @Inject constructor( + @UserId + private val userId: String, + // Use a provider to fix circular Dagger dependency + private val roomGetterProvider: Provider<RoomGetter> +) { + + fun createPermalink(event: Event): String? { + if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) { + return null + } + return createPermalink(event.roomId, event.eventId) + } + + fun createPermalink(id: String): String? { + return if (id.isEmpty()) { + null + } else MATRIX_TO_URL_BASE + escape(id) + } + + fun createRoomPermalink(roomId: String): String? { + return if (roomId.isEmpty()) { + null + } else { + MATRIX_TO_URL_BASE + escape(roomId) + computeViaParams(userId, roomId) + } + } + + fun createPermalink(roomId: String, eventId: String): String { + return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + computeViaParams(userId, roomId) + } + + fun getLinkedId(url: String): String? { + val isSupported = url.startsWith(MATRIX_TO_URL_BASE) + + return if (isSupported) { + url.substring(MATRIX_TO_URL_BASE.length) + } else null + } + + /** + * Compute the via parameters. + * Take up to 3 homeserver domains, taking the most representative one regarding room members and including the + * current user one. + */ + private fun computeViaParams(userId: String, roomId: String): String { + val userHomeserver = userId.substringAfter(":") + return getUserIdsOfJoinedMembers(roomId) + .map { it.substringAfter(":") } + .groupBy { it } + .mapValues { it.value.size } + .toMutableMap() + // Ensure the user homeserver will be included + .apply { this[userHomeserver] = Int.MAX_VALUE } + .let { map -> map.keys.sortedByDescending { map[it] } } + .take(3) + .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } + } + + /** + * Escape '/' in id, because it is used as a separator + * + * @param id the id to escape + * @return the escaped id + */ + private fun escape(id: String): String { + return id.replace("/", "%2F") + } + + /** + * Unescape '/' in id + * + * @param id the id to escape + * @return the escaped id + */ + private fun unescape(id: String): String { + return id.replace("%2F", "/") + } + + /** + * Get a set of userIds of joined members of a room + */ + private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> { + return roomGetterProvider.get().getRoom(roomId) + ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) + ?.map { it.userId } + .orEmpty() + .toSet() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 06dcaeccefca61e7503d442a90545acf9fbc8d16..633b0479948699af48760f535c12b20e284e1504 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -21,6 +21,7 @@ package org.matrix.android.sdk.internal.session.profile import android.net.Uri import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy +import io.realm.kotlin.where import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService @@ -34,7 +35,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import io.realm.kotlin.where import javax.inject.Inject internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor, @@ -75,11 +75,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) { val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg") - setAvatarUrlTask - .configureWith(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) { - callback = matrixCallback - } - .executeBy(taskExecutor) + setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index e13d5305b539e2bea83d7d7793df61aa5780dfd2..6738c7d881323b42a52cf2dc82e8f4008f8246f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -18,6 +18,11 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy +import io.realm.RealmConfiguration +import kotlinx.coroutines.TimeoutCancellationException +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset @@ -34,9 +39,6 @@ import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelpe import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction -import io.realm.RealmConfiguration -import kotlinx.coroutines.TimeoutCancellationException -import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -55,10 +57,26 @@ internal class DefaultCreateRoomTask @Inject constructor( ) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { + val otherUserId = if (params.isDirect()) { + params.getFirstInvitedUserId() + ?: throw IllegalStateException("You can't create a direct room without an invitedUser") + } else null + val createRoomBody = createRoomBodyBuilder.build(params) - val createRoomResponse = executeRequest<CreateRoomResponse>(eventBus) { - apiCall = roomAPI.createRoom(createRoomBody) + val createRoomResponse = try { + executeRequest<CreateRoomResponse>(eventBus) { + apiCall = roomAPI.createRoom(createRoomBody) + } + } catch (throwable: Throwable) { + if (throwable is Failure.ServerError + && throwable.httpCode == 403 + && throwable.error.code == MatrixError.M_FORBIDDEN + && throwable.error.message.startsWith("Federation denied with")) { + throw CreateRoomFailure.CreatedWithFederationFailure(throwable.error) + } else { + throw throwable + } } val roomId = createRoomResponse.roomId // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) @@ -70,17 +88,14 @@ internal class DefaultCreateRoomTask @Inject constructor( } catch (exception: TimeoutCancellationException) { throw CreateRoomFailure.CreatedWithTimeout } - if (params.isDirect()) { - handleDirectChatCreation(params, roomId) + if (otherUserId != null) { + handleDirectChatCreation(roomId, otherUserId) } setReadMarkers(roomId) return roomId } - private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String) { - val otherUserId = params.getFirstInvitedUserId() - ?: throw IllegalStateException("You can't create a direct room without an invitedUser") - + private suspend fun handleDirectChatCreation(roomId: String, otherUserId: String) { monarchy.awaitTransaction { realm -> RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { this.directUserId = otherUserId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 458d98bd52238802767430ce13249cb4b01e714f..2199193de0ac1f7ce28882c1256680791793f07a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -156,11 +156,15 @@ internal class DefaultRelationService @AssistedInject constructor( originalTimelineEvent: TimelineEvent, newBodyText: String, compatibilityBodyText: String): Cancelable { - val event = eventFactory - .createReplaceTextOfReply(roomId, - replyToEdit, - originalTimelineEvent, - newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText) + val event = eventFactory.createReplaceTextOfReply( + roomId, + replyToEdit, + originalTimelineEvent, + newBodyText, + true, + MessageType.MSGTYPE_TEXT, + compatibilityBodyText + ) .also { saveLocalEcho(it) } return if (cryptoService.isRoomEncrypted(roomId)) { val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index fe6daad3b077d066352cc9df906280a41a24aa4e..ac92e526a85eaaa9842d14198f193965b65ef3d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -22,7 +22,6 @@ import android.graphics.Bitmap import android.media.MediaMetadataRetriever import androidx.exifinterface.media.ExifInterface import org.matrix.android.sdk.R -import org.matrix.android.sdk.api.permalinks.PermalinkFactory import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -59,8 +58,8 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.isReply import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor +import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils -import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.StringProvider import javax.inject.Inject @@ -79,8 +78,8 @@ internal class LocalEchoEventFactory @Inject constructor( private val stringProvider: StringProvider, private val markdownParser: MarkdownParser, private val textPillsUtils: TextPillsUtils, - private val taskExecutor: TaskExecutor, - private val localEchoRepository: LocalEchoRepository + private val localEchoRepository: LocalEchoRepository, + private val permalinkFactory: PermalinkFactory ) { fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event { if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) { @@ -169,9 +168,8 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String): Event { - val permalink = PermalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "") - val userLink = originalEvent.root.senderId?.let { PermalinkFactory.createPermalink(it) } - ?: "" + val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "") + val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it) } ?: "" val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) val replyFormatted = REPLY_PATTERN.format( @@ -207,7 +205,7 @@ internal class LocalEchoEventFactory @Inject constructor( ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment) ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment) ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment) - ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment) + ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment) } } @@ -361,12 +359,15 @@ internal class LocalEchoEventFactory @Inject constructor( return System.currentTimeMillis() } - fun createReplyTextEvent(roomId: String, eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Event? { + fun createReplyTextEvent(roomId: String, + eventReplied: TimelineEvent, + replyText: CharSequence, + autoMarkdown: Boolean): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null - val permalink = PermalinkFactory.createPermalink(eventReplied.root) ?: return null + val permalink = permalinkFactory.createPermalink(eventReplied.root) ?: return null val userId = eventReplied.root.senderId ?: return null - val userLink = PermalinkFactory.createPermalink(userId) ?: return null + val userLink = permalinkFactory.createPermalink(userId) ?: return null val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) val replyFormatted = REPLY_PATTERN.format( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt index 0d898e46ce38d70e04c3f8cca8d28b7cda6e6ab6..2ae115f325f4d0f9aa39df11e460c82283bf4760 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt @@ -44,10 +44,7 @@ import javax.crypto.CipherInputStream import javax.crypto.CipherOutputStream import javax.crypto.KeyGenerator import javax.crypto.SecretKey -import javax.crypto.SecretKeyFactory import javax.crypto.spec.GCMParameterSpec -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.SecretKeySpec import javax.inject.Inject import javax.security.auth.x500.X500Principal @@ -127,9 +124,8 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte @Throws(Exception::class) fun securelyStoreString(secret: String, keyAlias: String): ByteArray? { return when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> encryptStringK(secret, keyAlias) - else -> encryptForOldDevicesNotGood(secret, keyAlias) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias) + else -> encryptString(secret, keyAlias) } } @@ -139,25 +135,22 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte @Throws(Exception::class) fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String? { return when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> decryptStringM(encrypted, keyAlias) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> decryptStringK(encrypted, keyAlias) - else -> decryptForOldDevicesNotGood(encrypted, keyAlias) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> decryptStringM(encrypted, keyAlias) + else -> decryptString(encrypted, keyAlias) } } fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) { when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> saveSecureObjectK(keyAlias, output, any) - else -> saveSecureObjectOldNotGood(keyAlias, output, any) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any) + else -> saveSecureObject(keyAlias, output, any) } } fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T? { return when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> loadSecureObjectM(keyAlias, inputStream) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> loadSecureObjectK(keyAlias, inputStream) - else -> loadSecureObjectOldNotGood(keyAlias, inputStream) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> loadSecureObjectM(keyAlias, inputStream) + else -> loadSecureObject(keyAlias, inputStream) } } @@ -188,7 +181,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte - Store the encrypted AES Generate a key pair for encryption */ - @RequiresApi(Build.VERSION_CODES.KITKAT) fun getOrGenerateKeyPairForAlias(alias: String): KeyStore.PrivateKeyEntry { val privateKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.PrivateKeyEntry) @@ -238,8 +230,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte return String(cipher.doFinal(encryptedText), Charsets.UTF_8) } - @RequiresApi(Build.VERSION_CODES.KITKAT) - private fun encryptStringK(text: String, keyAlias: String): ByteArray? { + private fun encryptString(text: String, keyAlias: String): ByteArray? { // we generate a random symmetric key val key = ByteArray(16) secureRandom.nextBytes(key) @@ -256,47 +247,13 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte return format1Make(encryptedKey, iv, encryptedBytes) } - private fun encryptForOldDevicesNotGood(text: String, keyAlias: String): ByteArray { - val salt = ByteArray(8) - secureRandom.nextBytes(salt) - val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") - val spec = PBEKeySpec(keyAlias.toCharArray(), salt, 10000, 128) - val tmp = factory.generateSecret(spec) - val sKey = SecretKeySpec(tmp.encoded, "AES") - - val cipher = Cipher.getInstance(AES_MODE) - cipher.init(Cipher.ENCRYPT_MODE, sKey) - val iv = cipher.iv - val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) - - return format2Make(salt, iv, encryptedBytes) - } - - private fun decryptForOldDevicesNotGood(data: ByteArray, keyAlias: String): String? { - val (salt, iv, encrypted) = format2Extract(ByteArrayInputStream(data)) - val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") - val spec = PBEKeySpec(keyAlias.toCharArray(), salt, 10_000, 128) - val tmp = factory.generateSecret(spec) - val sKey = SecretKeySpec(tmp.encoded, "AES") - - val cipher = Cipher.getInstance(AES_MODE) -// cipher.init(Cipher.ENCRYPT_MODE, sKey) -// val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) - - val specIV = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) IvParameterSpec(iv) else GCMParameterSpec(128, iv) - cipher.init(Cipher.DECRYPT_MODE, sKey, specIV) - - return String(cipher.doFinal(encrypted), Charsets.UTF_8) - } - - @RequiresApi(Build.VERSION_CODES.KITKAT) - private fun decryptStringK(data: ByteArray, keyAlias: String): String? { + private fun decryptString(data: ByteArray, keyAlias: String): String? { val (encryptedKey, iv, encrypted) = format1Extract(ByteArrayInputStream(data)) // we need to decrypt the key val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey)) 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 = GCMParameterSpec(128, iv) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec) return String(cipher.doFinal(encrypted), Charsets.UTF_8) @@ -323,8 +280,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte output.write(doFinal) } - @RequiresApi(Build.VERSION_CODES.KITKAT) - private fun saveSecureObjectK(keyAlias: String, output: OutputStream, writeObject: Any) { + private fun saveSecureObject(keyAlias: String, output: OutputStream, writeObject: Any) { // we generate a random symmetric key val key = ByteArray(16) secureRandom.nextBytes(key) @@ -352,32 +308,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte output.write(bos1.toByteArray()) } - private fun saveSecureObjectOldNotGood(keyAlias: String, output: OutputStream, writeObject: Any) { - val salt = ByteArray(8) - secureRandom.nextBytes(salt) - val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") - val tmp = factory.generateSecret(PBEKeySpec(keyAlias.toCharArray(), salt, 10000, 128)) - val secretKey = SecretKeySpec(tmp.encoded, "AES") - - val cipher = Cipher.getInstance(AES_MODE) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) - val iv = cipher.iv - - val bos1 = ByteArrayOutputStream() - ObjectOutputStream(bos1).use { - it.writeObject(writeObject) - } - // Have to do it like that if i encapsulate the output stream, the cipher could fail saying reuse IV - val doFinal = cipher.doFinal(bos1.toByteArray()) - - output.write(FORMAT_2.toInt()) - output.write(salt.size) - output.write(salt) - output.write(iv.size) - output.write(iv) - output.write(doFinal) - } - // @RequiresApi(Build.VERSION_CODES.M) // @Throws(IOException::class) // fun saveSecureObjectM(keyAlias: String, file: File, writeObject: Any) { @@ -418,15 +348,14 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte } } - @RequiresApi(Build.VERSION_CODES.KITKAT) @Throws(IOException::class) - private fun <T> loadSecureObjectK(keyAlias: String, inputStream: InputStream): T? { + private fun <T> loadSecureObject(keyAlias: String, inputStream: InputStream): T? { val (encryptedKey, iv, encrypted) = format1Extract(inputStream) // we need to decrypt the key val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey)) 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 = GCMParameterSpec(128, iv) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec) val encIS = ByteArrayInputStream(encrypted) @@ -440,31 +369,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte } } - @Throws(Exception::class) - private fun <T> loadSecureObjectOldNotGood(keyAlias: String, inputStream: InputStream): T? { - val (salt, iv, encrypted) = format2Extract(inputStream) - - val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") - val tmp = factory.generateSecret(PBEKeySpec(keyAlias.toCharArray(), salt, 10000, 128)) - val sKey = SecretKeySpec(tmp.encoded, "AES") - // we need to decrypt the key - - val cipher = Cipher.getInstance(AES_MODE) - val spec = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) IvParameterSpec(iv) else GCMParameterSpec(128, iv) - cipher.init(Cipher.DECRYPT_MODE, sKey, spec) - - val encIS = ByteArrayInputStream(encrypted) - - CipherInputStream(encIS, cipher).use { - ObjectInputStream(it).use { ois -> - val readObject = ois.readObject() - @Suppress("UNCHECKED_CAST") - return readObject as? T - } - } - } - - @RequiresApi(Build.VERSION_CODES.KITKAT) @Throws(Exception::class) private fun rsaEncrypt(alias: String, secret: ByteArray): ByteArray { val privateKeyEntry = getOrGenerateKeyPairForAlias(alias) @@ -480,7 +384,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte return outputStream.toByteArray() } - @RequiresApi(Build.VERSION_CODES.KITKAT) @Throws(Exception::class) private fun rsaDecrypt(alias: String, encrypted: InputStream): ByteArray { val privateKeyEntry = getOrGenerateKeyPairForAlias(alias) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt index 0f8f2c1f94a0b4a34f5a274294c030edfd75b6ff..395484433b9254c5406c4eee843839e8d39b5719 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.widgets -import android.os.Build import android.webkit.JavascriptInterface import android.webkit.WebView import com.squareup.moshi.Moshi @@ -172,11 +171,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh val functionLine = "sendResponseFromRiotAndroid('" + eventData["_id"] + "' , " + jsString + ");" Timber.v("BRIDGE sendResponse: $functionLine") // call the javascript method - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - webView?.loadUrl("javascript:$functionLine") - } else { - webView?.evaluateJavascript(functionLine, null) - } + webView?.evaluateJavascript(functionLine, null) } catch (e: Exception) { Timber.e(e, "## sendResponse() failed ") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CompatUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CompatUtil.kt index 0836d96af9aca30e263c68d81fdae652884d9fe8..6583dc89ea88b4169bbb75f8c98e52eacf9b309a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CompatUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CompatUtil.kt @@ -27,7 +27,6 @@ import android.security.KeyPairGeneratorSpec import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 -import androidx.annotation.RequiresApi import androidx.core.content.edit import timber.log.Timber import java.io.IOException @@ -48,7 +47,6 @@ import java.security.cert.CertificateException import java.security.spec.AlgorithmParameterSpec import java.security.spec.RSAKeyGenParameterSpec import java.util.Calendar -import java.util.zip.GZIPOutputStream import javax.crypto.Cipher import javax.crypto.CipherInputStream import javax.crypto.CipherOutputStream @@ -82,22 +80,6 @@ object CompatUtil { */ private val prng: SecureRandom by lazy(LazyThreadSafetyMode.NONE) { SecureRandom() } - /** - * Create a GZIPOutputStream instance - * Special treatment on KitKat device, force the syncFlush param to false - * Before Kitkat, this param does not exist and after Kitkat it is set to false by default - * - * @param outputStream the output stream - */ - @Throws(IOException::class) - fun createGzipOutputStream(outputStream: OutputStream): GZIPOutputStream { - return if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { - GZIPOutputStream(outputStream, false) - } else { - GZIPOutputStream(outputStream) - } - } - /** * Returns the AES key used for local storage encryption/decryption with AES/GCM. * The key is created if it does not exist already in the keystore. @@ -107,7 +89,6 @@ object CompatUtil { * * @param context the context holding the application shared preferences */ - @RequiresApi(Build.VERSION_CODES.KITKAT) @Synchronized @Throws(KeyStoreException::class, CertificateException::class, @@ -249,10 +230,6 @@ object CompatUtil { KeyStoreException::class, IllegalBlockSizeException::class) fun createCipherOutputStream(out: OutputStream, context: Context): OutputStream? { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - return out - } - val keyAndVersion = getAesGcmLocalProtectionKey(context) val cipher = Cipher.getInstance(AES_GCM_CIPHER_TYPE) @@ -298,10 +275,6 @@ object CompatUtil { InvalidAlgorithmParameterException::class, IOException::class) fun createCipherInputStream(`in`: InputStream, context: Context): InputStream? { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - return `in` - } - val iv_len = `in`.read() if (iv_len != AES_GCM_IV_LENGTH) { Timber.e(TAG, "Invalid IV length $iv_len") diff --git a/matrix-sdk-android/src/main/res/values-bn-rIN/strings.xml b/matrix-sdk-android/src/main/res/values-bn-rIN/strings.xml index 805d13a62ad108ac4fdcdc2f6fa3b5232ae25bbb..c8e70a9b2048830f431b7445aef9d8077bff22b9 100644 --- a/matrix-sdk-android/src/main/res/values-bn-rIN/strings.xml +++ b/matrix-sdk-android/src/main/res/values-bn-rIN/strings.xml @@ -80,7 +80,7 @@ <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$ আপডেট করেছে</string> + <string name="notice_profile_change_redacted">%1$s তাদের পà§à¦°à§‹à¦«à¦¾à¦‡à¦² %2$s আপডেট করেছে</string> <string name="notice_profile_change_redacted_by_you">আপনি আপনার পà§à¦°à§‹à¦«à¦¾à¦‡à¦² %1$s আপডেট করেছেন</string> <string name="notice_room_third_party_invite">%1$s %2$s কে ঘরে যোগদানের জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ আমনà§à¦¤à§à¦°à¦£ পাঠিয়েছে</string> <string name="notice_room_third_party_invite_by_you">আপনি %1$s কে ঘরে যোগদানের জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ আমনà§à¦¤à§à¦°à¦£ পà§à¦°à§‡à¦°à¦£ করেছেন</string> diff --git a/matrix-sdk-android/src/main/res/values-de/strings.xml b/matrix-sdk-android/src/main/res/values-de/strings.xml index 7ec9240067cddb96a69e4c66400c797ca6f513c0..0c857e78ee25fb96ab3387dab040c3f6dad577e9 100644 --- a/matrix-sdk-android/src/main/res/values-de/strings.xml +++ b/matrix-sdk-android/src/main/res/values-de/strings.xml @@ -212,7 +212,7 @@ <string name="notice_room_invite_no_invitee_by_you">Deine Einladung</string> <string name="notice_room_created">%1$s hat den Raum erstellt</string> <string name="notice_room_created_by_you">Du hast den Raum erstellt</string> - <string name="notice_room_invite_by_you">Du hast $1$s eingeladen</string> + <string name="notice_room_invite_by_you">Du hast %1$s eingeladen</string> <string name="notice_room_join_by_you">Du bist dem Raum beigetreten</string> <string name="notice_room_leave_by_you">Du hast den Raum verlassen</string> <string name="notice_room_reject_by_you">Du hast die Einladung abgelehnt</string> diff --git a/matrix-sdk-android/src/main/res/values-et/strings.xml b/matrix-sdk-android/src/main/res/values-et/strings.xml index 657d5446eb55059669f46b535e38f27f5347b54d..b7cd202063cdf0839ac21fead9012aa3cd4c0120 100644 --- a/matrix-sdk-android/src/main/res/values-et/strings.xml +++ b/matrix-sdk-android/src/main/res/values-et/strings.xml @@ -110,7 +110,7 @@ <string name="verification_emoji_heart">Süda</string> <string name="verification_emoji_smiley">Smaili</string> <string name="verification_emoji_robot">Robot</string> - <string name="verification_emoji_hat">Müts</string> + <string name="verification_emoji_hat">Kübar</string> <string name="verification_emoji_glasses">Prillid</string> <string name="verification_emoji_wrench">Mutrivõti</string> <string name="verification_emoji_santa">Jõuluvana</string> @@ -141,7 +141,7 @@ <string name="verification_emoji_anchor">Ankur</string> <string name="verification_emoji_headphone">Kõrvaklapid</string> <string name="verification_emoji_folder">Kaust</string> - <string name="verification_emoji_pin">Knopka</string> + <string name="verification_emoji_pin">Nööpnõel</string> <string name="initial_sync_start_importing_account">Alglaadimine: \nImpordin kontot…</string> diff --git a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml index 7c5d6fe583665dca955a23817e7df08a8923d971..e1b99506c87d44da57f922043f11a1cfe5162df6 100644 --- a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml +++ b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml @@ -292,15 +292,15 @@ <string name="notice_room_guest_access_can_join">%1$s permitiu que convidados entrem na sala.</string> <string name="notice_room_guest_access_can_join_by_you">Você permitiu que convidados entrem na sala.</string> - <string name="notice_room_guest_access_forbidden">%1$s impediu que convidados entrem na sala.</string> - <string name="notice_room_guest_access_forbidden_by_you">Você impediu que convidados entrem na sala.</string> + <string name="notice_room_guest_access_forbidden">%1$s impediu que convidados entrassem na sala.</string> + <string name="notice_room_guest_access_forbidden_by_you">Você impediu que convidados entrassem na sala.</string> <string name="notice_end_to_end_ok">%1$s ativou a criptografia de ponta a ponta.</string> <string name="notice_end_to_end_ok_by_you">Você ativou a criptografia de ponta a ponta.</string> <string name="notice_end_to_end_unknown_algorithm">%1$s ativou a criptografia de ponta a ponta (algoritmo não reconhecido %2$s).</string> <string name="notice_end_to_end_unknown_algorithm_by_you">Você ativou a criptografia de ponta a ponta (algoritmo não reconhecido %1$s).</string> - <string name="key_verification_request_fallback_message">%s deseja verificar a sua chave, mas o seu aplicativo não suporta a verificação da chave da conversa. Você precisará usar a verificação tradicional de chaves para verificar chaves.</string> + <string name="key_verification_request_fallback_message">%s deseja confirmar a sua chave, mas o seu aplicativo não suporta a confirmação da chave da conversa. Você precisará usar a confirmação tradicional de chaves para confirmar chaves.</string> <string name="call_notification_answer">Aceitar</string> <string name="call_notification_reject">Recusar</string> diff --git a/matrix-sdk-android/src/main/res/values-sk/strings.xml b/matrix-sdk-android/src/main/res/values-sk/strings.xml index 8aec8fccf99b1b12290241639b8690ec192c5fac..70e3a6ebd0c376f2c41121bd09592e7a57b1d987 100644 --- a/matrix-sdk-android/src/main/res/values-sk/strings.xml +++ b/matrix-sdk-android/src/main/res/values-sk/strings.xml @@ -1,51 +1,51 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> <string name="summary_message">%1$s: %2$s</string> - <string name="summary_user_sent_image">%1$s poslal obrázok.</string> + <string name="summary_user_sent_image">%1$s poslal/a obrázok.</string> - <string name="notice_room_invite_no_invitee">Pozvanie %s</string> - <string name="notice_room_invite">%1$s pozval %2$s</string> - <string name="notice_room_invite_you">%1$s vás pozval</string> - <string name="notice_room_join">%1$s sa pripojil/a do miestnosti</string> + <string name="notice_room_invite_no_invitee">Pozvanie od %s</string> + <string name="notice_room_invite">%1$s pozval/a %2$s</string> + <string name="notice_room_invite_you">%1$s vás pozval/a</string> + <string name="notice_room_join">%1$s vstúpil/a do miestnosti</string> <string name="notice_room_leave">%1$s opustil/a miestnosÅ¥</string> - <string name="notice_room_reject">%1$s odmietol pozvanie</string> - <string name="notice_room_kick">%1$s vykázal %2$s</string> - <string name="notice_room_unban">%1$s povolil vstup %2$s</string> - <string name="notice_room_ban">%1$s zakázal vstup %2$s</string> - <string name="notice_room_withdraw">%1$s stiahol pozvanie %2$s</string> - <string name="notice_avatar_url_changed">%1$s si zmenil obrázok v profile</string> - <string name="notice_display_name_set">%1$s si nastavil zobrazované meno %2$s</string> - <string name="notice_display_name_changed_from">%1$s si zmenil zobrazované meno z %2$s na %3$s</string> - <string name="notice_display_name_removed">%1$s odstránil svoje zobrazované meno (%2$s)</string> - <string name="notice_room_topic_changed">%1$s zmenil tému na: %2$s</string> - <string name="notice_room_name_changed">%1$s zmenil názov miestnosti na: %2$s</string> - <string name="notice_placed_video_call">%s uskutoÄnil video hovor.</string> - <string name="notice_placed_voice_call">%s uskutoÄnil audio hovor.</string> - <string name="notice_answered_call">%s prijal hovor.</string> - <string name="notice_ended_call">%s ukonÄil hovor.</string> - <string name="notice_made_future_room_visibility">%1$s sprÃstupnil budúcu históriu miestnosti %2$s</string> + <string name="notice_room_reject">%1$s odmietol/a pozvanie</string> + <string name="notice_room_kick">%1$s vykázal/a %2$s</string> + <string name="notice_room_unban">%1$s povolil/a vstupovaÅ¥ %2$s</string> + <string name="notice_room_ban">%1$s zakázal/a vstupovaÅ¥ %2$s</string> + <string name="notice_room_withdraw">%1$s vzal/a späť pozvanie %2$s</string> + <string name="notice_avatar_url_changed">%1$s si zmenil/a obrázok v profile</string> + <string name="notice_display_name_set">%1$s si nastavil/a zobrazované meno %2$s</string> + <string name="notice_display_name_changed_from">%1$s si zmenil/a zobrazované meno %2$s na %3$s</string> + <string name="notice_display_name_removed">%1$s odstránil/a svoje zobrazované meno (%2$s)</string> + <string name="notice_room_topic_changed">%1$s zmenil/a tému na: %2$s</string> + <string name="notice_room_name_changed">%1$s zmenil/a názov miestnosti na: %2$s</string> + <string name="notice_placed_video_call">%s uskutoÄnil/a video hovor.</string> + <string name="notice_placed_voice_call">%s zatelefonoval/a.</string> + <string name="notice_answered_call">%s prijal/a hovor.</string> + <string name="notice_ended_call">%s ukonÄil/a hovor.</string> + <string name="notice_made_future_room_visibility">%1$s sprÃstupnil/a budúcu históriu miestnosti %2$s</string> <string name="notice_room_visibility_invited">pre vÅ¡etkých Älenov, od kedy boli pozvanÃ.</string> <string name="notice_room_visibility_joined">pre vÅ¡etkých Älenov, od kedy vstúpili.</string> <string name="notice_room_visibility_shared">pre vÅ¡etkých Älenov.</string> <string name="notice_room_visibility_world_readable">pre každého.</string> <string name="notice_room_visibility_unknown">neznámym (%s).</string> - <string name="notice_end_to_end">%1$s povolil E2E Å¡ifrovanie (%2$s)</string> + <string name="notice_end_to_end">%1$s povolil/a E2E Å¡ifrovanie (%2$s)</string> - <string name="notice_requested_voip_conference">%1$s požiadal o VoIP konferenciu</string> - <string name="notice_voip_started">ZaÄala VoIP konferencia</string> - <string name="notice_voip_finished">SkonÄila VoIP konferencia</string> + <string name="notice_requested_voip_conference">%1$s požiadal/a o VoIP konferenciu</string> + <string name="notice_voip_started">ZaÄala sa VoIP konferencia</string> + <string name="notice_voip_finished">SkonÄila sa VoIP konferencia</string> - <string name="notice_avatar_changed_too">(a tiež obrázok v profile)</string> - <string name="notice_room_name_removed">%1$s odstránil názov miestnosti</string> - <string name="notice_room_topic_removed">%1$s odstránil tému miestnosti</string> - <string name="notice_profile_change_redacted">%1$s aktualizoval svoj profil %2$s</string> - <string name="notice_room_third_party_invite">%1$s pozval %2$s vstúpiÅ¥ do miestnosti</string> - <string name="notice_room_third_party_registered_invite">%1$s prijal pozvanie do %2$s</string> + <string name="notice_avatar_changed_too">(aj obrázok v profile)</string> + <string name="notice_room_name_removed">%1$s odstránil/a názov miestnosti</string> + <string name="notice_room_topic_removed">%1$s odstránil/a tému miestnosti</string> + <string name="notice_profile_change_redacted">%1$s aktualizoval/a svoj profil %2$s</string> + <string name="notice_room_third_party_invite">%1$s pozval/a %2$s vstúpiÅ¥ do miestnosti</string> + <string name="notice_room_third_party_registered_invite">%1$s prijal/a pozvanie pre %2$s</string> <string name="notice_crypto_unable_to_decrypt">** Nie je možné deÅ¡ifrovaÅ¥: %s **</string> <string name="notice_crypto_error_unkwown_inbound_session_id">Zo zariadenia odosieľateľa nebolo možné zÃskaÅ¥ kľúÄe potrebné na deÅ¡ifrovanie tejto správy.</string> - <string name="could_not_redact">Nie je ožné vymazaÅ¥</string> + <string name="could_not_redact">Nie je možné vymazaÅ¥</string> <string name="unable_to_send_message">Nie je možné odoslaÅ¥ správu</string> <string name="message_failed_to_upload">Nepodarilo sa nahraÅ¥ obrázok</string> @@ -60,7 +60,7 @@ <string name="medium_email">Emailová adresa</string> <string name="medium_phone_number">Telefónne ÄÃslo</string> - <string name="summary_user_sent_sticker">%1$s poslal nálepku.</string> + <string name="summary_user_sent_sticker">%1$s poslal/a nálepku.</string> <string name="room_displayname_invite_from">Pozvanie od %s</string> <string name="room_displayname_room_invite">Pozvanie do miestnosti</string> @@ -75,12 +75,12 @@ </plurals> - <string name="notice_room_update">%s aktualizoval túto miestnosÅ¥.</string> + <string name="notice_room_update">%s aktualizoval/a 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="notice_event_redacted">Odstránená správa</string> + <string name="notice_event_redacted_by">Odstránená správa použÃvateľom %1$s</string> + <string name="notice_event_redacted_with_reason">Odstránená správa [dôvod: %1$s]</string> + <string name="notice_event_redacted_by_with_reason">Odstránená správa použÃvateľom %1$s [dôvod: %2$s]</string> <string name="verification_emoji_dog">Hlava psa</string> <string name="verification_emoji_cat">Hlava maÄky</string> <string name="verification_emoji_lion">Hlava leva</string> @@ -110,7 +110,7 @@ <string name="verification_emoji_corn">KukuriÄný klas</string> <string name="verification_emoji_pizza">Pizza</string> <string name="verification_emoji_cake">Narodeninová torta</string> - <string name="verification_emoji_heart">ÄŒervené</string> + <string name="verification_emoji_heart">ÄŒervené srdce</string> <string name="verification_emoji_smiley">Å keriaca sa tvár</string> <string name="verification_emoji_robot">Robot</string> <string name="verification_emoji_hat">Cylinder</string> @@ -154,8 +154,8 @@ \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_invited_rooms">Úvodná synchronizácia: +\nPrebieha import pozvanÃ</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: @@ -166,20 +166,20 @@ <string name="event_status_sending_message">Odosielanie správy…</string> <string name="clear_timeline_send_queue">VymazaÅ¥ správy na odoslanie</string> - <string name="notice_room_third_party_revoked_invite">%1$s zamietol pozvanie použÃvateľa %2$s vstúpiÅ¥ do miestnosti</string> - <string name="notice_room_invite_no_invitee_with_reason">Pozvanie od použÃvateľa %1$s. Dôvod: %2$s</string> - <string name="notice_room_invite_with_reason">%1$s pozval použÃvateľa %2$s. Dôvod: %3$s</string> - <string name="notice_room_invite_you_with_reason">%1$s vás pozval. Dôvod: %2$s</string> - <string name="notice_room_join_with_reason">%1$s sa pripojil/a do miestnosti. Dôvod: %2$s</string> - <string name="notice_room_leave_with_reason">PoužÃvateľ %1$s odiÅ¡iel z miestnosti. Dôvod: %2$s</string> - <string name="notice_room_reject_with_reason">%1$s odmietol pozvanie. Dôvod: %2$s</string> - <string name="notice_room_kick_with_reason">%1$s vyhodil použÃvateľa %2$s. Dôvod: %3$s</string> - <string name="notice_room_unban_with_reason">%1$s znovu pridaný použÃvateľom %2$s. Dôvod: %3$s</string> - <string name="notice_room_ban_with_reason">%1$s vyhodil %2$s. Dôvod: %3$s</string> - <string name="notice_room_third_party_invite_with_reason">%1$s poslal pozvánku použÃvateľovi %2$s, aby sa pripojil na miestnosÅ¥. Dôvod: %3$s</string> - <string name="notice_room_third_party_revoked_invite_with_reason">%1$s odvolal/a pozvánku pre použÃvateľa %2$s na pripojenie sa na miestnosÅ¥. Dôvod: %3$s</string> - <string name="notice_room_third_party_registered_invite_with_reason">%1$s prijal pozvanie od použÃvateľa %2$s. Dôvod: %3$s</string> - <string name="notice_room_withdraw_with_reason">%1$s odoprel/a pozvánku použÃvateľa %2$s. Dôvod: %3$s</string> + <string name="notice_room_third_party_revoked_invite">%1$s zamietol/a pozvanie použÃvateľa %2$s vstúpiÅ¥ do miestnosti</string> + <string name="notice_room_invite_no_invitee_with_reason">Pozvanie od %1$s. Dôvod: %2$s</string> + <string name="notice_room_invite_with_reason">%1$s pozval/a %2$s. Dôvod: %3$s</string> + <string name="notice_room_invite_you_with_reason">%1$s vás pozval/a. Dôvod: %2$s</string> + <string name="notice_room_join_with_reason">%1$s vstúpil/a do miestnosti. Dôvod: %2$s</string> + <string name="notice_room_leave_with_reason">%1$s opustil/a miestnosÅ¥. Dôvod: %2$s</string> + <string name="notice_room_reject_with_reason">%1$s odmietol/a pozvanie. Dôvod: %2$s</string> + <string name="notice_room_kick_with_reason">%1$s vykázal/a %2$s. Dôvod: %3$s</string> + <string name="notice_room_unban_with_reason">%1$s povolil/a vstupovaÅ¥ %2$s. Dôvod: %3$s</string> + <string name="notice_room_ban_with_reason">%1$s zakázal/a vstupovaÅ¥ %2$s. Dôvod: %3$s</string> + <string name="notice_room_third_party_invite_with_reason">%1$s pozval/a %2$s vstúpiÅ¥ do miestnosti. Dôvod: %3$s</string> + <string name="notice_room_third_party_revoked_invite_with_reason">%1$s zamietol/a pozvanie použÃvateľa %2$s vstúpiÅ¥ do miestnosti. Dôvod: %3$s</string> + <string name="notice_room_third_party_registered_invite_with_reason">%1$s prijal/a pozvanie pre %2$s. Dôvod: %3$s</string> + <string name="notice_room_withdraw_with_reason">%1$s vzal/a späť pozvanie %2$s. Dôvod: %3$s</string> <plurals name="notice_room_aliases_added"> <item quantity="one">%1$s pridal/a adresu %2$s pre túto miestnosÅ¥.</item> @@ -189,14 +189,118 @@ <plurals name="notice_room_aliases_removed"> <item quantity="one">%1$s odstránil/a adresu %2$s pre túto miestnosÅ¥.</item> - <item quantity="few">%1$s odstránil/a adresy %2$s pre túto miestnosÅ¥.</item> - <item quantity="other">%1$s odstránil/a adresy %2$s pre túto miestnosÅ¥.</item> + <item quantity="few">%1$s odstránil/a adresy %3$s pre túto miestnosÅ¥.</item> + <item quantity="other">%1$s odstránil/a adresy %3$s pre túto miestnosÅ¥.</item> </plurals> - <string name="notice_room_aliases_added_and_removed">%1$s pridal/a adresu %2$s a odstránil/a adresu %3$s pre túto miestnosÅ¥.</string> + <string name="notice_room_aliases_added_and_removed">%1$s pridal/a adresy %2$s a odstránil/a adresy %3$s pre túto miestnosÅ¥.</string> - <string name="notice_room_canonical_alias_set">%1$s nastavil/a hlavnú adresu tejto miestnosti na %2$s.</string> - <string name="notice_room_canonical_alias_unset">%1$s odstránil/a hlavnú adresu pre túto miestnosÅ¥.</string> + <string name="notice_room_canonical_alias_set">%1$s nastavil/a hlavnú adresu tejto miestnosti %2$s.</string> + <string name="notice_room_canonical_alias_unset">%1$s odstránil/a hlavnú adresu tejto miestnosti.</string> <string name="notice_room_guest_access_can_join">%1$s povolil/a hosÅ¥om///návÅ¡tevnÃkom prÃstup do tejto miestnosti.</string> + <string name="summary_you_sent_image">Poslali ste obrázok.</string> + <string name="summary_you_sent_sticker">Poslali ste nálepku.</string> + + <string name="notice_room_invite_no_invitee_by_you">Pozvanie od vás</string> + <string name="notice_room_created">%1$s vytvoril/a miestnosÅ¥</string> + <string name="notice_room_created_by_you">Vytvorili ste miestnosÅ¥</string> + <string name="notice_room_invite_by_you">Pozvali ste %1$s</string> + <string name="notice_room_join_by_you">Vstúpili ste do miestnosti</string> + <string name="notice_room_leave_by_you">Opustili ste miestnosÅ¥</string> + <string name="notice_room_reject_by_you">Odmietli ste pozvanie</string> + <string name="notice_room_kick_by_you">Vykázali ste %1$s</string> + <string name="notice_room_unban_by_you">Povolili ste vstupovaÅ¥ %1$s</string> + <string name="notice_room_ban_by_you">Zakázali ste vstupovaÅ¥ %1$s</string> + <string name="notice_room_withdraw_by_you">Vzali ste späť pozvanie %1$s</string> + <string name="notice_avatar_url_changed_by_you">Zmenili ste si obrázok v profile</string> + <string name="notice_display_name_set_by_you">Nastavili ste si zobrazované meno %1$s</string> + <string name="notice_display_name_changed_from_by_you">Zmenili ste si zobrazované meno %1$s na %2$s</string> + <string name="notice_display_name_removed_by_you">Odstránili ste svoje zobrazované meno %1$s</string> + <string name="notice_room_topic_changed_by_you">Zmenili ste tému na: %1$s</string> + <string name="notice_room_avatar_changed">%1$s zmenil/a obrázok miestnosti</string> + <string name="notice_room_avatar_changed_by_you">Zmenili ste obrázok miestnosti</string> + <string name="notice_room_name_changed_by_you">Zmenili ste názov miestnosti na: %1$s</string> + <string name="notice_placed_video_call_by_you">UskutoÄnili ste video hovor.</string> + <string name="notice_placed_voice_call_by_you">Zatelefonovali ste.</string> + <string name="notice_call_candidates">%s poslal údaje pre nastavenie hovoru.</string> + <string name="notice_call_candidates_by_you">Poslali ste údaje na nastavenie hovoru.</string> + <string name="notice_answered_call_by_you">Prijali ste hovor.</string> + <string name="notice_ended_call_by_you">UkonÄili ste hovor.</string> + <string name="notice_made_future_room_visibility_by_you">SprÃstupnili ste budúcu históriu miestnosti %1$s</string> + <string name="notice_end_to_end_by_you">Povolili ste E2E Å¡ifrovanie (%1$s)</string> + <string name="notice_room_update_by_you">Aktualizovali ste túto miestnosÅ¥.</string> + + <string name="notice_requested_voip_conference_by_you">Požiadali ste o VoIP konferenciu</string> + <string name="notice_room_name_removed_by_you">Odstránili ste názov miestnosti</string> + <string name="notice_room_topic_removed_by_you">Odstránili ste tému miestnosti</string> + <string name="notice_room_avatar_removed">%1$s odstránil obrázok miestnosti</string> + <string name="notice_room_avatar_removed_by_you">Odstránili ste obrázok miestnosti</string> + <string name="notice_profile_change_redacted_by_you">Aktualizovali ste svoj profil %1$s</string> + <string name="notice_room_third_party_invite_by_you">Pozvali ste %1$s vstúpiÅ¥ do miestnosti</string> + <string name="notice_room_third_party_revoked_invite_by_you">Zamietli ste pozvanie použÃvateľa %2$s vstúpiÅ¥ do miestnosti</string> + <string name="notice_room_third_party_registered_invite_by_you">Prijali ste pozvanie pre %1$s</string> + + <string name="notice_widget_added">%1$s pridal/a widget %2$s</string> + <string name="notice_widget_added_by_you">Pridali ste widget %1$s</string> + <string name="notice_widget_removed">%1$s odstránil/a widget %2$s</string> + <string name="notice_widget_removed_by_you">Odstránili ste widget %1$s</string> + <string name="notice_widget_modified">%1$s upravil/a widget %2$s</string> + <string name="notice_widget_modified_by_you">Upravili ste widget %1$s</string> + + <string name="power_level_admin">Správca</string> + <string name="power_level_moderator">Moderátor</string> + <string name="power_level_default">Predvolený</string> + <string name="power_level_custom">Vlastná úroveň (%1$d)</string> + <string name="power_level_custom_no_value">Vlastná úroveň</string> + + <string name="notice_power_level_changed_by_you">Zmenili ste úroveň moci použÃvateľa %1$s.</string> + <string name="notice_power_level_changed">%1$s zmenil úroveň moci použÃvateľa %2$s.</string> + <string name="notice_power_level_diff">%1$s z %2$s na %3$s</string> + + <string name="notice_room_invite_no_invitee_with_reason_by_you">Pozvanie od vás. Dôvod: %1$s</string> + <string name="notice_room_invite_with_reason_by_you">Pozvali ste %1$s. Dôvod: %2$s</string> + <string name="notice_room_join_with_reason_by_you">Vstúpili ste do miestnosti. Dôvod: %1$s</string> + <string name="notice_room_leave_with_reason_by_you">Opustili ste miestnosÅ¥. Dôvod: %1$s</string> + <string name="notice_room_reject_with_reason_by_you">Odmietli ste pozvanie. Dôvod: %1$s</string> + <string name="notice_room_kick_with_reason_by_you">Vykázali ste %1$s. Dôvod: %2$s</string> + <string name="notice_room_unban_with_reason_by_you">Povolili ste vstupovaÅ¥ %1$s. Dôvod: %2$s</string> + <string name="notice_room_ban_with_reason_by_you">Zakázali ste vstupovaÅ¥ %1$s. Dôvod: %2$s</string> + <string name="notice_room_third_party_invite_with_reason_by_you">Pozvali ste %1$s vstúpiÅ¥ do miestnosti. Dôvod: %2$s</string> + <string name="notice_room_third_party_revoked_invite_with_reason_by_you">Zamietli ste pozvanie použÃvateľa %1$s vstúpiÅ¥ do miestnosti. Dôvod: %2$s</string> + <string name="notice_room_third_party_registered_invite_with_reason_by_you">Prijali ste pozvanie pre %1$s. Dôvod: %2$s</string> + <string name="notice_room_withdraw_with_reason_by_you">Vzali ste späť pozvanie %1$s. Dôvod: %2$s</string> + + <plurals name="notice_room_aliases_added_by_you"> + <item quantity="one">Pridali ste adresu %1$s pre túto miestnosÅ¥.</item> + <item quantity="few">Pridali ste adresy %1$s pre túto miestnosÅ¥.</item> + <item quantity="other">Pridali ste adresy %1$s pre túto miestnosÅ¥.</item> + </plurals> + + <plurals name="notice_room_aliases_removed_by_you"> + <item quantity="one">Odstránili ste adresu %1$s pre túto miestnosÅ¥.</item> + <item quantity="few">Odstránili ste adresy %2$s pre túto miestnosÅ¥.</item> + <item quantity="other">Odstránili ste adresy %2$s pre túto miestnosÅ¥.</item> + </plurals> + + <string name="notice_room_aliases_added_and_removed_by_you">Pridali ste %1$s a odstránili adresy %2$s pre túto miestnosÅ¥.</string> + + <string name="notice_room_canonical_alias_set_by_you">Nastavili ste hlavnú adresu tejto miestnosti %2$s.</string> + <string name="notice_room_canonical_alias_unset_by_you">Odstránili ste hlavnú adresu tejto miestnosti.</string> + + <string name="notice_room_guest_access_can_join_by_you">Povolili ste hosÅ¥om///návÅ¡tevnÃkom prÃstup do tejto miestnosti.</string> + <string name="notice_room_guest_access_forbidden">%1$s zakázal/a hosÅ¥om///návÅ¡tevnÃkom prÃstup do tejto miestnosti.</string> + <string name="notice_room_guest_access_forbidden_by_you">Zakázali ste hosÅ¥om///návÅ¡tevnÃkom prÃstup do tejto miestnosti.</string> + + <string name="notice_end_to_end_ok">%1$s povolil/a E2E Å¡ifrovanie.</string> + <string name="notice_end_to_end_ok_by_you">Povolili ste E2E Å¡ifrovanie.</string> + <string name="notice_end_to_end_unknown_algorithm">%1$s povolil/a E2E Å¡ifrovanie (Nerozpoznaný algorytmus %2$s).</string> + <string name="notice_end_to_end_unknown_algorithm_by_you">Povolili ste E2E Å¡ifrovanie (Nerozpoznaný algorytmus %1$s).</string> + + <string name="key_verification_request_fallback_message">%s požaduje overenie vaÅ¡ich Å¡ifrovacÃch kľúÄov, ale váš klient nepodporuje overenie kľúÄov v konverzácii. Budete musieÅ¥ použiÅ¥ zastaralú metódu overenia.</string> + + <string name="call_notification_answer">PrijaÅ¥</string> + <string name="call_notification_reject">OdmietnuÅ¥</string> + <string name="call_notification_hangup">ZavesiÅ¥</string> + </resources>