mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 09:25:49 +03:00
Merge pull request #4027 from vector-im/feature/fre/permalink
Add client base url support for permalinks
This commit is contained in:
commit
c0adde56df
15 changed files with 305 additions and 262 deletions
1
changelog.d/4027.feature
Normal file
1
changelog.d/4027.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add client base url config to customize permalinks
|
|
@ -32,8 +32,16 @@ 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 (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,
|
||||
/**
|
||||
* 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 +55,7 @@ data class MatrixConfiguration(
|
|||
) {
|
||||
|
||||
/**
|
||||
* Can be implemented by your Application class
|
||||
* Can be implemented by your Application class.
|
||||
*/
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
|
|
|
@ -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 MatrixToConverter {
|
||||
|
||||
/**
|
||||
* 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 convert(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/"
|
||||
)
|
||||
}
|
|
@ -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. <clientPermalinkBaseUrl>user/@chagai95:matrix.org)
|
||||
*/
|
||||
object PermalinkParser {
|
||||
|
||||
|
@ -42,13 +43,15 @@ 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 = 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 = uri.toString().substringAfter("#") // uri.fragment
|
||||
if (fragment.isNullOrEmpty()) {
|
||||
val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment
|
||||
if (fragment.isEmpty()) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
val safeFragment = fragment.substringBefore('?')
|
||||
|
@ -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(
|
||||
|
@ -125,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<String> {
|
||||
return UrlQuerySanitizer(this)
|
||||
|
@ -138,9 +136,7 @@ object PermalinkParser {
|
|||
.filter {
|
||||
it.mParameter == "via"
|
||||
}.map {
|
||||
it.mValue.let {
|
||||
URLDecoder.decode(it, "UTF-8")
|
||||
}
|
||||
URLDecoder.decode(it.mValue, "UTF-8")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ 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).
|
||||
* See [org.matrix.android.sdk.api.MatrixConfiguration.clientPermalinkBaseUrl] to setup a custom permalink base url.
|
||||
*/
|
||||
interface PermalinkService {
|
||||
|
||||
|
@ -32,10 +33,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 +45,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<String>? = null): String?
|
||||
fun createRoomPermalink(roomId: String, viaServers: List<String>? = null, forceMatrixTo: Boolean = false): String?
|
||||
|
||||
/**
|
||||
* Creates a permalink for an event. If you have an event you can use [createPermalink]
|
||||
|
@ -62,10 +67,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
|
||||
|
|
|
@ -18,33 +18,29 @@ 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(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>?): String? {
|
||||
return permalinkFactory.createRoomPermalink(roomId, viaServers)
|
||||
override fun createRoomPermalink(roomId: String, viaServers: List<String>?, 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? {
|
||||
return url
|
||||
.takeIf { it.startsWith(MATRIX_TO_URL_BASE) }
|
||||
?.substring(MATRIX_TO_URL_BASE.length)
|
||||
?.substringBeforeLast("?")
|
||||
return permalinkFactory.getLinkedId(url)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String>? = null): String? {
|
||||
fun createRoomPermalink(roomId: String, via: List<String>? = 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,16 +75,34 @@ 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? {
|
||||
val isSupported = url.startsWith(MATRIX_TO_URL_BASE)
|
||||
|
||||
return if (isSupported) {
|
||||
url.substring(MATRIX_TO_URL_BASE.length)
|
||||
} else null
|
||||
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("?")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,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/"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -105,8 +105,8 @@
|
|||
<activity android:name=".features.home.HomeActivity" />
|
||||
<activity
|
||||
android:name=".features.login.LoginActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:enabled="@bool/useLoginV1"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Add intent filter to handle redirection URL after SSO login in external browser -->
|
||||
<intent-filter>
|
||||
|
@ -122,8 +122,8 @@
|
|||
</activity>
|
||||
<activity
|
||||
android:name=".features.login2.LoginActivity2"
|
||||
android:launchMode="singleTask"
|
||||
android:enabled="@bool/useLoginV2"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Add intent filter to handle redirection URL after SSO login in external browser -->
|
||||
<intent-filter>
|
||||
|
@ -180,7 +180,12 @@
|
|||
<activity android:name=".features.createdirect.CreateDirectRoomActivity" />
|
||||
<activity android:name=".features.invite.InviteUsersToRoomActivity" />
|
||||
<activity android:name=".features.webview.VectorWebViewActivity" />
|
||||
<activity android:name=".features.link.LinkHandlerActivity">
|
||||
|
||||
<!-- Activity to intercept links coming from a web instance -->
|
||||
<activity
|
||||
android:name=".features.link.LinkHandlerActivity"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
@ -196,6 +201,32 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Activity alias for matrix.to or element permalinks -->
|
||||
<activity-alias
|
||||
android:name=".features.permalink.PermalinkHandlerActivity"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:targetActivity=".features.link.LinkHandlerActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="matrix.to" />
|
||||
<data
|
||||
android:host="user"
|
||||
android:scheme="element" />
|
||||
<data
|
||||
android:host="room"
|
||||
android:scheme="element" />
|
||||
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".features.share.IncomingShareActivity"
|
||||
android:parentActivityName=".features.home.HomeActivity">
|
||||
|
@ -230,27 +261,6 @@
|
|||
<activity
|
||||
android:name=".features.signout.soft.SoftLogoutActivity"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".features.permalink.PermalinkHandlerActivity"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="matrix.to" />
|
||||
<data
|
||||
android:host="user"
|
||||
android:scheme="element" />
|
||||
<data
|
||||
android:host="room"
|
||||
android:scheme="element" />
|
||||
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".features.roommemberprofile.RoomMemberProfileActivity"
|
||||
|
@ -271,12 +281,12 @@
|
|||
android:name=".features.attachments.preview.AttachmentsPreviewActivity"
|
||||
android:theme="@style/Theme.Vector.Black.AttachmentsPreview" />
|
||||
<activity
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
android:name=".features.call.VectorCallActivity"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=".features.call.VectorCallActivity"
|
||||
android:excludeFromRecents="true" />
|
||||
android:supportsPictureInPicture="true"
|
||||
android:taskAffinity=".features.call.VectorCallActivity" />
|
||||
<!-- PIP Support https://developer.android.com/guide/topics/ui/picture-in-picture -->
|
||||
<activity
|
||||
android:name=".features.call.conference.VectorJitsiActivity"
|
||||
|
|
|
@ -62,7 +62,6 @@ import im.vector.app.features.matrixto.MatrixToBottomSheet
|
|||
import im.vector.app.features.media.BigImageViewerActivity
|
||||
import im.vector.app.features.media.VectorAttachmentViewerActivity
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.app.features.permalink.PermalinkHandlerActivity
|
||||
import im.vector.app.features.pin.PinLocker
|
||||
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
||||
import im.vector.app.features.rageshake.BugReportActivity
|
||||
|
@ -155,7 +154,6 @@ interface ScreenComponent {
|
|||
fun inject(activity: CreateDirectRoomActivity)
|
||||
fun inject(activity: IncomingShareActivity)
|
||||
fun inject(activity: SoftLogoutActivity)
|
||||
fun inject(activity: PermalinkHandlerActivity)
|
||||
fun inject(activity: QrCodeScannerActivity)
|
||||
fun inject(activity: DebugMenuActivity)
|
||||
fun inject(activity: SharedSecureStorageActivity)
|
||||
|
|
|
@ -51,6 +51,9 @@ import im.vector.app.features.navigation.Navigator
|
|||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import im.vector.app.features.permalink.NavigationInterceptor
|
||||
import im.vector.app.features.permalink.PermalinkHandler
|
||||
import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE
|
||||
import im.vector.app.features.permalink.PermalinkHandler.Companion.ROOM_LINK_PREFIX
|
||||
import im.vector.app.features.permalink.PermalinkHandler.Companion.USER_LINK_PREFIX
|
||||
import im.vector.app.features.popup.DefaultVectorAlert
|
||||
import im.vector.app.features.popup.PopupAlertManager
|
||||
import im.vector.app.features.popup.VerificationVectorAlert
|
||||
|
@ -272,20 +275,19 @@ class HomeActivity :
|
|||
private fun handleIntent(intent: Intent?) {
|
||||
intent?.dataString?.let { deepLink ->
|
||||
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,
|
||||
|
@ -296,9 +298,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()
|
||||
}
|
||||
|
@ -579,10 +583,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)
|
||||
|
|
|
@ -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<ActivityProgressBinding>() {
|
|||
@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 (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<ActivityProgressBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
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,21 +155,4 @@ class LinkHandlerActivity : VectorBaseActivity<ActivityProgressBinding>() {
|
|||
.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
||||
.show()
|
||||
}
|
||||
|
||||
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/",
|
||||
"/#/group/"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Boolean> {
|
||||
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<Optional<String>> {
|
||||
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 {
|
||||
|
|
|
@ -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<FragmentProgressBinding>() {
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
|
@ -26,4 +26,15 @@
|
|||
<item>gitter.im</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Permalink config -->
|
||||
<string-array name="permalink_supported_hosts" translatable="false">
|
||||
<!-- Regular Element Web instance -->
|
||||
<item>app.element.io</item>
|
||||
<!-- Other known instances of Element Web -->
|
||||
<item>develop.element.io</item>
|
||||
<item>staging.element.io</item>
|
||||
<!-- Previous Web instance, kept for compatibility reason -->
|
||||
<item>riot.im</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue