From afb49430be86d4ef9f75ded3e1f8c073985d675c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 7 Sep 2021 15:59:59 +0200 Subject: [PATCH 1/6] Permalink: move method implementation from service to factory --- .../session/permalinks/DefaultPermalinkService.kt | 6 +----- .../sdk/internal/session/permalinks/PermalinkFactory.kt | 9 ++++----- 2 files changed, 5 insertions(+), 10 deletions(-) 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 index 134da4ce51..70e4faf356 100644 --- 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 @@ -18,7 +18,6 @@ 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( @@ -42,9 +41,6 @@ internal class DefaultPermalinkService @Inject constructor( } override fun getLinkedId(url: String): String? { - return url - .takeIf { it.startsWith(MATRIX_TO_URL_BASE) } - ?.substring(MATRIX_TO_URL_BASE.length) - ?.substringBeforeLast("?") + return permalinkFactory.getLinkedId(url) } } 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 index 639e45582a..9332751744 100644 --- 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 @@ -60,11 +60,10 @@ internal class PermalinkFactory @Inject constructor( } 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 + return url + .takeIf { it.startsWith(MATRIX_TO_URL_BASE) } + ?.substring(MATRIX_TO_URL_BASE.length) + ?.substringBeforeLast("?") } /** From 0d344fde03a68d3c1dd3d2a53796669813ffa7af Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 8 Sep 2021 17:23:48 +0200 Subject: [PATCH 2/6] Permalink: add client url field in MatrixConfiguration --- .../org/matrix/android/sdk/api/MatrixConfiguration.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index ed809cdb04..bd47c34571 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -32,8 +32,12 @@ data class MatrixConfiguration( "https://scalar-staging.riot.im/scalar/api" ), /** - * Optional proxy to connect to the matrix servers - * You can create one using for instance Proxy(proxyType, InetSocketAddress.createUnresolved(hostname, port) + * Optional base url to create client permalinks instead of Matrix ones (matrix.to links). + */ + val clientPermalinkBaseUrl: String? = null, + /** + * Optional proxy to connect to the matrix servers. + * You can create one using for instance Proxy(proxyType, InetSocketAddress.createUnresolved(hostname, port). */ val proxy: Proxy? = null, /** @@ -47,7 +51,7 @@ data class MatrixConfiguration( ) { /** - * Can be implemented by your Application class + * Can be implemented by your Application class. */ interface Provider { fun providesMatrixConfiguration(): MatrixConfiguration From a73f0a9fa802243acc0d121e4e7fa65dfc461af2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 9 Sep 2021 09:12:30 +0200 Subject: [PATCH 3/6] Permalink: use client base url if any --- .../session/permalinks/PermalinkService.kt | 15 ++- .../permalinks/DefaultPermalinkService.kt | 16 ++-- .../session/permalinks/PermalinkFactory.kt | 91 ++++++++++++++++--- .../room/send/LocalEchoEventFactory.kt | 8 +- 4 files changed, 99 insertions(+), 31 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt index a6d4583c76..7318b7b8e0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt @@ -19,7 +19,7 @@ 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). + * Useful methods to create permalink (like matrix.to links or client permalinks). */ interface PermalinkService { @@ -32,10 +32,11 @@ interface PermalinkService { * Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org" * * @param event the event + * @param forceMatrixTo whether we should force using matrix.to base URL * * @return the permalink, or null in case of error */ - fun createPermalink(event: Event): String? + fun createPermalink(event: Event, forceMatrixTo: Boolean = false): String? /** * Creates a permalink for an id (can be a user Id, etc.). @@ -43,18 +44,21 @@ interface PermalinkService { * Ex: "https://matrix.to/#/@benoit:matrix.org" * * @param id the id + * @param forceMatrixTo whether we should force using matrix.to base URL + * * @return the permalink, or null in case of error */ - fun createPermalink(id: String): String? + fun createPermalink(id: String, forceMatrixTo: Boolean = false): String? /** * Creates a permalink for a roomId, including the via parameters * * @param roomId the room id + * @param forceMatrixTo whether we should force using matrix.to base URL * * @return the permalink, or null in case of error */ - fun createRoomPermalink(roomId: String, viaServers: List? = null): String? + fun createRoomPermalink(roomId: String, viaServers: List? = null, forceMatrixTo: Boolean = false): String? /** * Creates a permalink for an event. If you have an event you can use [createPermalink] @@ -62,10 +66,11 @@ interface PermalinkService { * * @param roomId the id of the room * @param eventId the id of the event + * @param forceMatrixTo whether we should force using matrix.to base URL * * @return the permalink */ - fun createPermalink(roomId: String, eventId: String): String + fun createPermalink(roomId: String, eventId: String, forceMatrixTo: Boolean = false): String /** * Extract the linked id from the universal link 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 index 70e4faf356..144ebb5404 100644 --- 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 @@ -24,20 +24,20 @@ internal class DefaultPermalinkService @Inject constructor( private val permalinkFactory: PermalinkFactory ) : PermalinkService { - override fun createPermalink(event: Event): String? { - return permalinkFactory.createPermalink(event) + override fun createPermalink(event: Event, forceMatrixTo: Boolean): String? { + return permalinkFactory.createPermalink(event, forceMatrixTo) } - override fun createPermalink(id: String): String? { - return permalinkFactory.createPermalink(id) + override fun createPermalink(id: String, forceMatrixTo: Boolean): String? { + return permalinkFactory.createPermalink(id, forceMatrixTo) } - override fun createRoomPermalink(roomId: String, viaServers: List?): String? { - return permalinkFactory.createRoomPermalink(roomId, viaServers) + override fun createRoomPermalink(roomId: String, viaServers: List?, forceMatrixTo: Boolean): String? { + return permalinkFactory.createRoomPermalink(roomId, viaServers, forceMatrixTo) } - override fun createPermalink(roomId: String, eventId: String): String { - return permalinkFactory.createPermalink(roomId, eventId) + override fun createPermalink(roomId: String, eventId: String, forceMatrixTo: Boolean): String { + return permalinkFactory.createPermalink(roomId, eventId, forceMatrixTo) } override fun getLinkedId(url: String): String? { 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 index 9332751744..39c1ddfdce 100644 --- 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 @@ -16,7 +16,11 @@ package org.matrix.android.sdk.internal.session.permalinks +import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.permalinks.PermalinkData +import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE import org.matrix.android.sdk.internal.di.UserId import javax.inject.Inject @@ -24,28 +28,44 @@ import javax.inject.Inject internal class PermalinkFactory @Inject constructor( @UserId private val userId: String, - private val viaParameterFinder: ViaParameterFinder + private val viaParameterFinder: ViaParameterFinder, + private val matrixConfiguration: MatrixConfiguration ) { - fun createPermalink(event: Event): String? { + fun createPermalink(event: Event, forceMatrixTo: Boolean): String? { if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) { return null } - return createPermalink(event.roomId, event.eventId) + return createPermalink(event.roomId, event.eventId, forceMatrixTo) } - fun createPermalink(id: String): String? { - return if (id.isEmpty()) { - null - } else MATRIX_TO_URL_BASE + escape(id) + fun createPermalink(id: String, forceMatrixTo: Boolean): String? { + return when { + id.isEmpty() -> null + !useClientFormat(forceMatrixTo) -> MATRIX_TO_URL_BASE + escape(id) + else -> { + buildString { + append(matrixConfiguration.clientPermalinkBaseUrl) + when { + MatrixPatterns.isRoomId(id) || MatrixPatterns.isRoomAlias(id) -> append(ROOM_PATH) + MatrixPatterns.isUserId(id) -> append(USER_PATH) + MatrixPatterns.isGroupId(id) -> append(GROUP_PATH) + } + append(escape(id)) + } + } + } } - fun createRoomPermalink(roomId: String, via: List? = null): String? { + fun createRoomPermalink(roomId: String, via: List? = null, forceMatrixTo: Boolean): String? { return if (roomId.isEmpty()) { null } else { buildString { - append(MATRIX_TO_URL_BASE) + append(baseUrl(forceMatrixTo)) + if (useClientFormat(forceMatrixTo)) { + append(ROOM_PATH) + } append(escape(roomId)) append( via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.asUrlViaParameters(it) } @@ -55,14 +75,33 @@ internal class PermalinkFactory @Inject constructor( } } - fun createPermalink(roomId: String, eventId: String): String { - return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + viaParameterFinder.computeViaParams(userId, roomId) + fun createPermalink(roomId: String, eventId: String, forceMatrixTo: Boolean): String { + return buildString { + append(baseUrl(forceMatrixTo)) + if (useClientFormat(forceMatrixTo)) { + append(ROOM_PATH) + } + append(escape(roomId)) + append("/") + append(escape(eventId)) + append(viaParameterFinder.computeViaParams(userId, roomId)) + } } fun getLinkedId(url: String): String? { - return url - .takeIf { it.startsWith(MATRIX_TO_URL_BASE) } - ?.substring(MATRIX_TO_URL_BASE.length) + val clientBaseUrl = matrixConfiguration.clientPermalinkBaseUrl + return when { + url.startsWith(MATRIX_TO_URL_BASE) -> url.substring(MATRIX_TO_URL_BASE.length) + clientBaseUrl != null && url.startsWith(clientBaseUrl) -> { + when (PermalinkParser.parse(url)) { + is PermalinkData.GroupLink -> url.substring(clientBaseUrl.length + GROUP_PATH.length) + is PermalinkData.RoomLink -> url.substring(clientBaseUrl.length + ROOM_PATH.length) + is PermalinkData.UserLink -> url.substring(clientBaseUrl.length + USER_PATH.length) + else -> null + } + } + else -> null + } ?.substringBeforeLast("?") } @@ -85,4 +124,28 @@ internal class PermalinkFactory @Inject constructor( private fun unescape(id: String): String { return id.replace("%2F", "/") } + + /** + * Get the permalink base URL according to the potential one in [MatrixConfiguration.clientPermalinkBaseUrl] + * and the [forceMatrixTo] parameter. + * + * @param forceMatrixTo whether we should force using matrix.to base URL. + * + * @return the permalink base URL. + */ + private fun baseUrl(forceMatrixTo: Boolean): String { + return matrixConfiguration.clientPermalinkBaseUrl + ?.takeUnless { forceMatrixTo } + ?: MATRIX_TO_URL_BASE + } + + private fun useClientFormat(forceMatrixTo: Boolean): Boolean { + return !forceMatrixTo && matrixConfiguration.clientPermalinkBaseUrl != null + } + + companion object { + private const val ROOM_PATH = "room/" + private const val USER_PATH = "user/" + private const val GROUP_PATH = "group/" + } } 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 c610326a94..8dd0c59387 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 @@ -165,8 +165,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 ?: "", false) + val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) val replyFormatted = REPLY_PATTERN.format( @@ -350,9 +350,9 @@ internal class LocalEchoEventFactory @Inject constructor( 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, false) ?: return null val userId = eventReplied.root.senderId ?: return null - val userLink = permalinkFactory.createPermalink(userId) ?: return null + val userLink = permalinkFactory.createPermalink(userId, false) ?: return null val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) val replyFormatted = REPLY_PATTERN.format( From 21d0a28150630264268b6d825bf90720079386f3 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 9 Sep 2021 17:17:11 +0200 Subject: [PATCH 4/6] Permalink: move supported hosts to config file --- .../vector/app/features/link/LinkHandlerActivity.kt | 11 +---------- vector/src/main/res/values/config.xml | 11 +++++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index a0b8efd5aa..487ce90dd9 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -63,7 +63,7 @@ class LinkHandlerActivity : VectorBaseActivity() { if (uri.getQueryParameter(LoginConfig.CONFIG_HS_PARAMETER) != null) { handleConfigUrl(uri) - } else if (SUPPORTED_HOSTS.contains(uri.host)) { + } else if (resources.getStringArray(R.array.permalink_supported_hosts).contains(uri.host)) { handleSupportedHostUrl(uri) } else { // Other links are not yet handled, but should not come here (manifest configuration error?) @@ -175,15 +175,6 @@ class LinkHandlerActivity : VectorBaseActivity() { } companion object { - private val SUPPORTED_HOSTS = listOf( - // Regular Element Web instance - "app.element.io", - // Other known instances of Element Web - "develop.element.io", - "staging.element.io", - // Previous Web instance, kept for compatibility reason - "riot.im" - ) private val SUPPORTED_PATHS = listOf( "/#/room/", "/#/user/", diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml index 30ca8d7f56..a8e80f82ed 100755 --- a/vector/src/main/res/values/config.xml +++ b/vector/src/main/res/values/config.xml @@ -26,4 +26,15 @@ gitter.im + + + + app.element.io + + develop.element.io + staging.element.io + + riot.im + + From e37fb313c0c6d6b8c1151dbc8773d9dce0ac9ccc Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 16 Sep 2021 15:11:46 +0200 Subject: [PATCH 5/6] Permalink: Merge LinkHandlerActivity with PermalinkHandlerActivity Also convert links to matrix.to before permalink parsing --- changelog.d/4027.feature | 1 + .../api/session/permalinks/MatrixToMapper.kt | 55 +++++++++ .../api/session/permalinks/PermalinkParser.kt | 21 ++-- vector/src/main/AndroidManifest.xml | 57 ++++----- .../im/vector/app/core/di/ScreenComponent.kt | 2 - .../vector/app/features/home/HomeActivity.kt | 26 ++--- .../app/features/link/LinkHandlerActivity.kt | 108 +++++++----------- .../features/permalink/PermalinkHandler.kt | 16 ++- .../permalink/PermalinkHandlerActivity.kt | 79 ------------- 9 files changed, 165 insertions(+), 200 deletions(-) create mode 100644 changelog.d/4027.feature create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt delete mode 100644 vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt diff --git a/changelog.d/4027.feature b/changelog.d/4027.feature new file mode 100644 index 0000000000..fa45d07ef9 --- /dev/null +++ b/changelog.d/4027.feature @@ -0,0 +1 @@ +Add client base url config to customize permalinks \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt new file mode 100644 index 0000000000..a1e7d09628 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt @@ -0,0 +1,55 @@ +/* + * 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.api.session.permalinks + +import android.net.Uri + +/** + * Mapping of an input URI to a matrix.to compliant URI. + */ +object MatrixToMapper { + + /** + * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. + * To be successfully converted, URL path should contain one of the [SUPPORTED_PATHS]. + * Examples: + * - https://riot.im/develop/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org + * - https://app.element.io/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org + * - https://www.example.org/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org + */ + fun map(uri: Uri): Uri? { + val uriString = uri.toString() + + return when { + // URL is already a matrix.to + uriString.startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> uri + // Web or client url + SUPPORTED_PATHS.any { it in uriString } -> { + val path = SUPPORTED_PATHS.first { it in uriString } + Uri.parse(PermalinkService.MATRIX_TO_URL_BASE + uriString.substringAfter(path)) + } + // URL is not supported + else -> null + } + } + + private val SUPPORTED_PATHS = listOf( + "/#/room/", + "/#/user/", + "/#/group/" + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt index 005a2edae7..9d16d09812 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt @@ -26,6 +26,7 @@ import java.net.URLDecoder * This class turns a uri to a [PermalinkData] * element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks * or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org) + * or client permalinks (e.g. https://www.example.com/#/user/@chagai95:matrix.org) */ object PermalinkParser { @@ -42,12 +43,14 @@ object PermalinkParser { * https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md */ fun parse(uri: Uri): PermalinkData { - if (!uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)) { - return PermalinkData.FallbackLink(uri) - } + // the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the + // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid + // so convert URI to matrix.to to simplify parsing process + val matrixToUri = MatrixToMapper.map(uri) ?: return PermalinkData.FallbackLink(uri) + // We can't use uri.fragment as it is decoding to early and it will break the parsing // of parameters that represents url (like signurl) - val fragment = uri.toString().substringAfter("#") // uri.fragment + val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment if (fragment.isNullOrEmpty()) { return PermalinkData.FallbackLink(uri) } @@ -61,20 +64,14 @@ object PermalinkParser { .map { URLDecoder.decode(it, "UTF-8") } .take(2) - // the element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the - // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid - var identifier = params.getOrNull(0) - if (identifier.equals("user")) { - identifier = params.getOrNull(1) - } - + val identifier = params.getOrNull(0) val extraParameter = params.getOrNull(1) return when { identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) MatrixPatterns.isRoomId(identifier) -> { - handleRoomIdCase(fragment, identifier, uri, extraParameter, viaQueryParameters) + handleRoomIdCase(fragment, identifier, matrixToUri, extraParameter, viaQueryParameters) } MatrixPatterns.isRoomAlias(identifier) -> { PermalinkData.RoomLink( diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 6c9453a564..0e84eb3bcd 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -180,7 +180,9 @@ - + @@ -196,6 +198,30 @@ + + + + + + + + + + + + + + + + @@ -230,27 +256,6 @@ - - - - - - - - - - - - - - - + android:supportsPictureInPicture="true" + android:taskAffinity=".features.call.VectorCallActivity" /> val resolvedLink = when { - deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> deepLink - deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> { - // This is a bit ugly, but for now just convert to matrix.to link for compatibility - when { + // Element custom scheme is not handled by the sdk, convert it to matrix.to link for compatibility + deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> { + val let = when { deepLink.startsWith(USER_LINK_PREFIX) -> deepLink.substring(USER_LINK_PREFIX.length) deepLink.startsWith(ROOM_LINK_PREFIX) -> deepLink.substring(ROOM_LINK_PREFIX.length) else -> null - }?.let { - activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(it) + }?.let { permalinkId -> + activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(permalinkId) } + let } - else -> return@let + else -> deepLink } - permalinkHandler.launch( context = this, deepLink = resolvedLink, @@ -290,9 +292,11 @@ class HomeActivity : .observeOn(AndroidSchedulers.mainThread()) .subscribe { isHandled -> if (!isHandled) { + val isMatrixToLink = deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) + || deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) MaterialAlertDialogBuilder(this) .setTitle(R.string.dialog_title_error) - .setMessage(R.string.permalink_malformed) + .setMessage(if (isMatrixToLink) R.string.permalink_malformed else R.string.universal_link_malformed) .setPositiveButton(R.string.ok, null) .show() } @@ -559,10 +563,6 @@ class HomeActivity : putExtra(MvRx.KEY_ARG, args) } } - - private const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://" - private const val ROOM_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/" - private const val USER_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/" } override fun create(initialState: ActiveSpaceViewState) = promoteRestrictedViewModelFactory.create(initialState) diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index 487ce90dd9..39105185b1 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -27,13 +27,12 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast import im.vector.app.databinding.ActivityProgressBinding +import im.vector.app.features.home.HomeActivity import im.vector.app.features.login.LoginConfig import im.vector.app.features.permalink.PermalinkHandler -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.permalinks.PermalinkService import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -45,30 +44,38 @@ class LinkHandlerActivity : VectorBaseActivity() { @Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var permalinkHandler: PermalinkHandler + override fun getBinding() = ActivityProgressBinding.inflate(layoutInflater) + override fun injectWith(injector: ScreenComponent) { injector.inject(this) } - override fun getBinding() = ActivityProgressBinding.inflate(layoutInflater) - override fun initUiAndData() { + handleIntent() + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + handleIntent() + } + + private fun handleIntent() { val uri = intent.data - - if (uri == null) { - // Should not happen - Timber.w("Uri is null") - finish() - return - } - - if (uri.getQueryParameter(LoginConfig.CONFIG_HS_PARAMETER) != null) { - handleConfigUrl(uri) - } else if (resources.getStringArray(R.array.permalink_supported_hosts).contains(uri.host)) { - handleSupportedHostUrl(uri) - } else { - // Other links are not yet handled, but should not come here (manifest configuration error?) - toast(R.string.universal_link_malformed) - finish() + when { + uri == null -> { + // Should not happen + Timber.w("Uri is null") + finish() + } + uri.getQueryParameter(LoginConfig.CONFIG_HS_PARAMETER) != null -> handleConfigUrl(uri) + uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> handleSupportedHostUrl() + uri.toString().startsWith(PermalinkHandler.MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> handleSupportedHostUrl() + resources.getStringArray(R.array.permalink_supported_hosts).contains(uri.host) -> handleSupportedHostUrl() + else -> { + // Other links are not yet handled, but should not come here (manifest configuration error?) + toast(R.string.universal_link_malformed) + finish() + } } } @@ -81,53 +88,28 @@ class LinkHandlerActivity : VectorBaseActivity() { } } - private fun handleSupportedHostUrl(uri: Uri) { + private fun handleSupportedHostUrl() { + // If we are not logged in, open login screen. + // In the future, we might want to relaunch the process after login. if (!sessionHolder.hasActiveSession()) { - startLoginActivity(uri) - finish() - } else { - convertUriToPermalink(uri)?.let { permalink -> - startPermalinkHandler(permalink) - } ?: run { - // Host is correct but we do not recognize path - Timber.w("Unable to handle this uri: $uri") - finish() - } + startLoginActivity() + return } + + // We forward intent to HomeActivity (singleTask) to avoid the dueling app problem + // https://stackoverflow.com/questions/25884954/deep-linking-and-multiple-app-instances + intent.setClass(this, HomeActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + startActivity(intent) } /** - * Convert a URL of element web instance to a matrix.to url - * Examples: - * - https://riot.im/develop/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org - * - https://app.element.io/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org + * Start the login screen with identity server and homeserver pre-filled, if any */ - private fun convertUriToPermalink(uri: Uri): String? { - val uriString = uri.toString() - val path = SUPPORTED_PATHS.find { it in uriString } ?: return null - return PermalinkService.MATRIX_TO_URL_BASE + uriString.substringAfter(path) - } - - private fun startPermalinkHandler(permalink: String) { - permalinkHandler.launch(this, permalink, buildTask = true) - .delay(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { isHandled -> - if (!isHandled) { - toast(R.string.universal_link_malformed) - } - finish() - } - .disposeOnDestroy() - } - - /** - * Start the login screen with identity server and homeserver pre-filled - */ - private fun startLoginActivity(uri: Uri) { + private fun startLoginActivity(uri: Uri? = null) { navigator.openLogin( context = this, - loginConfig = LoginConfig.parse(uri), + loginConfig = uri?.let { LoginConfig.parse(uri) }, flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK ) finish() @@ -173,12 +155,4 @@ class LinkHandlerActivity : VectorBaseActivity() { .setPositiveButton(R.string.ok) { _, _ -> finish() } .show() } - - companion object { - private val SUPPORTED_PATHS = listOf( - "/#/room/", - "/#/user/", - "/#/group/" - ) - } } diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index ecaeea1899..fd5fea0fe8 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -29,6 +29,7 @@ import io.reactivex.schedulers.Schedulers import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.util.Optional @@ -55,7 +56,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti navigationInterceptor: NavigationInterceptor? = null, buildTask: Boolean = false ): Single { - if (deepLink == null) { + if (deepLink == null || !isPermalinkSupported(context, deepLink.toString())) { return Single.just(false) } return Single @@ -122,6 +123,13 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti } } + private fun isPermalinkSupported(context: Context, url: String): Boolean { + return url.startsWith(PermalinkService.MATRIX_TO_URL_BASE) + || context.resources.getStringArray(R.array.permalink_supported_hosts).any { + url.startsWith(it) + } + } + private fun PermalinkData.RoomLink.getRoomId(): Single> { val session = activeSessionHolder.getSafeActiveSession() return if (isRoomAlias && session != null) { @@ -179,6 +187,12 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti } } } + + companion object { + const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://" + const val ROOM_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/" + const val USER_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/" + } } interface NavigationInterceptor { diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt deleted file mode 100644 index ee4e0e05b5..0000000000 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.permalink - -import android.content.Intent -import android.os.Bundle -import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.di.ScreenComponent -import im.vector.app.core.extensions.replaceFragment -import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.databinding.FragmentProgressBinding -import im.vector.app.features.home.HomeActivity -import im.vector.app.features.home.LoadingFragment -import javax.inject.Inject - -class PermalinkHandlerActivity : VectorBaseActivity() { - - @Inject lateinit var permalinkHandler: PermalinkHandler - @Inject lateinit var sessionHolder: ActiveSessionHolder - - override fun getBinding() = FragmentProgressBinding.inflate(layoutInflater) - - override fun injectWith(injector: ScreenComponent) { - injector.inject(this) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_simple) - if (isFirstCreation()) { - replaceFragment(R.id.simpleFragmentContainer, LoadingFragment::class.java) - } - handleIntent() - } - - private fun handleIntent() { - // If we are not logged in, open login screen. - // In the future, we might want to relaunch the process after login. - if (!sessionHolder.hasActiveSession()) { - startLoginActivity() - return - } - // We forward intent to HomeActivity (singleTask) to avoid the dueling app problem - // https://stackoverflow.com/questions/25884954/deep-linking-and-multiple-app-instances - intent.setClass(this, HomeActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - startActivity(intent) - - finish() - } - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - handleIntent() - } - - private fun startLoginActivity() { - navigator.openLogin( - context = this, - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK - ) - finish() - } -} From fa3abecf8f3f96be288b8dc66bc4cab163887ec5 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 17 Sep 2021 16:44:18 +0200 Subject: [PATCH 6/6] Fix review --- .../android/sdk/api/MatrixConfiguration.kt | 6 ++++- ...MatrixToMapper.kt => MatrixToConverter.kt} | 4 ++-- .../api/session/permalinks/PermalinkParser.kt | 23 +++++++++---------- .../session/permalinks/PermalinkService.kt | 1 + vector/src/main/AndroidManifest.xml | 9 ++++++-- 5 files changed, 26 insertions(+), 17 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/{MatrixToMapper.kt => MatrixToConverter.kt} (97%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index bd47c34571..03f9f0b707 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -32,7 +32,11 @@ data class MatrixConfiguration( "https://scalar-staging.riot.im/scalar/api" ), /** - * Optional base url to create client permalinks instead of Matrix ones (matrix.to links). + * Optional base url to create client permalinks (eg. https://www.example.com/#/) instead of Matrix ones (matrix.to links). + * Do not forget to add the "#" which is required by the permalink parser. + * + * Note: this field is only used for permalinks creation, you will also have to edit the string-array `permalink_supported_hosts` in the config file + * and add it to your manifest to handle these links in the application. */ val clientPermalinkBaseUrl: String? = null, /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToConverter.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToConverter.kt index a1e7d09628..a904e89681 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToConverter.kt @@ -21,7 +21,7 @@ import android.net.Uri /** * Mapping of an input URI to a matrix.to compliant URI. */ -object MatrixToMapper { +object MatrixToConverter { /** * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. @@ -31,7 +31,7 @@ object MatrixToMapper { * - https://app.element.io/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org * - https://www.example.org/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org */ - fun map(uri: Uri): Uri? { + fun convert(uri: Uri): Uri? { val uriString = uri.toString() return when { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt index 9d16d09812..edb748c76e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt @@ -26,7 +26,7 @@ import java.net.URLDecoder * This class turns a uri to a [PermalinkData] * element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks * or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org) - * or client permalinks (e.g. https://www.example.com/#/user/@chagai95:matrix.org) + * or client permalinks (e.g. user/@chagai95:matrix.org) */ object PermalinkParser { @@ -46,12 +46,12 @@ object PermalinkParser { // the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid // so convert URI to matrix.to to simplify parsing process - val matrixToUri = MatrixToMapper.map(uri) ?: return PermalinkData.FallbackLink(uri) + val matrixToUri = MatrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri) // We can't use uri.fragment as it is decoding to early and it will break the parsing // of parameters that represents url (like signurl) val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment - if (fragment.isNullOrEmpty()) { + if (fragment.isEmpty()) { return PermalinkData.FallbackLink(uri) } val safeFragment = fragment.substringBefore('?') @@ -122,12 +122,13 @@ object PermalinkParser { } } - private fun safeExtractParams(fragment: String) = fragment.substringAfter("?").split('&').mapNotNull { - val splitNameValue = it.split("=") - if (splitNameValue.size == 2) { - Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8")) - } else null - } + private fun safeExtractParams(fragment: String) = + fragment.substringAfter("?").split('&').mapNotNull { + val splitNameValue = it.split("=") + if (splitNameValue.size == 2) { + Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8")) + } else null + } private fun String.getViaParameters(): List { return UrlQuerySanitizer(this) @@ -135,9 +136,7 @@ object PermalinkParser { .filter { it.mParameter == "via" }.map { - it.mValue.let { - URLDecoder.decode(it, "UTF-8") - } + URLDecoder.decode(it.mValue, "UTF-8") } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt index 7318b7b8e0..920dc85c7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.session.events.model.Event /** * Useful methods to create permalink (like matrix.to links or client permalinks). + * See [org.matrix.android.sdk.api.MatrixConfiguration.clientPermalinkBaseUrl] to setup a custom permalink base url. */ interface PermalinkService { diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 0e84eb3bcd..b38df10ce0 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -105,8 +105,8 @@ @@ -122,8 +122,8 @@ @@ -180,8 +180,11 @@ + + @@ -198,8 +201,10 @@ +