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 00000000000..077fafc9ed9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToMapper.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ * 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
+ * - element://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))
+ }
+ // Element custom scheme
+ uriString.startsWith(PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> {
+ val permalinkId = when {
+ uriString.startsWith(USER_LINK_PREFIX) -> uriString.substring(USER_LINK_PREFIX.length)
+ uriString.startsWith(ROOM_LINK_PREFIX) -> uriString.substring(ROOM_LINK_PREFIX.length)
+ else -> null
+ }
+ Uri.parse(PermalinkService.MATRIX_TO_URL_BASE + permalinkId)
+ }
+ // URL is not supported
+ else -> null
+ }
+ }
+
+ private const val ROOM_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
+ private const val USER_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
+
+ 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 005a2edae73..9d16d098129 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/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 7318b7b8e05..017452d7d5b 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
@@ -25,6 +25,7 @@ interface PermalinkService {
companion object {
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
+ const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
}
/**
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 6c9453a5641..0e84eb3bcd9 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 {
- 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)
- }
- }
- else -> return@let
- }
-
+ intent?.data?.let { deeplink ->
permalinkHandler.launch(
context = this,
- deepLink = resolvedLink,
+ deepLink = deeplink,
navigationInterceptor = this,
buildTask = true
)
@@ -290,9 +275,11 @@ class HomeActivity :
.observeOn(AndroidSchedulers.mainThread())
.subscribe { isHandled ->
if (!isHandled) {
+ val isMatrixToLink = deeplink.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)
+ || deeplink.toString().startsWith(PermalinkService.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 +546,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 487ce90dd95..525b890da03 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() {
- val uri = intent.data
+ handleIntent()
+ }
- if (uri == null) {
- // Should not happen
- Timber.w("Uri is null")
- finish()
- return
- }
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ handleIntent()
+ }
- 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()
+ private fun handleIntent() {
+ val uri = intent.data
+ 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(PermalinkService.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
}
- }
- /**
- * 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
- */
- 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()
+ // 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)
}
/**
- * Start the login screen with identity server and homeserver pre-filled
+ * Start the login screen with identity server and homeserver pre-filled, if any
*/
- 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/PermalinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt
deleted file mode 100644
index ee4e0e05b56..00000000000
--- 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()
- }
-}