diff --git a/CHANGES.md b/CHANGES.md
index 2b07b6d769..a7772bc9f2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,7 @@ Changes in RiotX 0.5.0 (2019-XX-XX)
Features:
- Handle M_CONSENT_NOT_GIVEN error (#64)
+ - Auto configure homeserver and identity server URLs of LoginActivity with a magic link
Improvements:
- Reduce default release build log level, and lab option to enable more logs.
diff --git a/tools/tests/test_configuration_link.sh b/tools/tests/test_configuration_link.sh
new file mode 100755
index 0000000000..33b1699e70
--- /dev/null
+++ b/tools/tests/test_configuration_link.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+adb shell am start -a android.intent.action.VIEW -d "https://riot.im/config/config?hs_url=https%3A%2F%2Fmozilla-test.modular.im"
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 949da5132f..01d8db467e 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -66,6 +66,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
index ccf3a19202..1395d6d433 100644
--- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
+++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
@@ -46,6 +46,7 @@ import im.vector.riotx.features.home.room.detail.timeline.action.*
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.invite.VectorInviteView
+import im.vector.riotx.features.link.LinkHandlerActivity
import im.vector.riotx.features.login.LoginActivity
import im.vector.riotx.features.login.LoginFragment
import im.vector.riotx.features.login.LoginSsoFallbackFragment
@@ -138,6 +139,8 @@ interface ScreenComponent {
fun inject(loginActivity: LoginActivity)
+ fun inject(linkHandlerActivity: LinkHandlerActivity)
+
fun inject(mainActivity: MainActivity)
fun inject(roomDirectoryActivity: RoomDirectoryActivity)
diff --git a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt
index 8ef3f0adcb..c32c35c2f1 100644
--- a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt
@@ -104,7 +104,7 @@ class MainActivity : VectorBaseActivity() {
val intent = if (sessionHolder.hasActiveSession()) {
HomeActivity.newIntent(this)
} else {
- LoginActivity.newIntent(this)
+ LoginActivity.newIntent(this, null)
}
startActivity(intent)
finish()
diff --git a/vector/src/main/java/im/vector/riotx/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/riotx/features/link/LinkHandlerActivity.kt
new file mode 100644
index 0000000000..b114e51607
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/link/LinkHandlerActivity.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.link
+
+import android.content.Intent
+import android.net.Uri
+import androidx.appcompat.app.AlertDialog
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.riotx.R
+import im.vector.riotx.core.di.ActiveSessionHolder
+import im.vector.riotx.core.di.ScreenComponent
+import im.vector.riotx.core.error.ErrorFormatter
+import im.vector.riotx.core.platform.VectorBaseActivity
+import im.vector.riotx.features.login.LoginActivity
+import im.vector.riotx.features.login.LoginConfig
+import timber.log.Timber
+import javax.inject.Inject
+
+
+/**
+ * Dummy activity used to dispatch the vector URL links.
+ */
+class LinkHandlerActivity : VectorBaseActivity() {
+
+ @Inject lateinit var sessionHolder: ActiveSessionHolder
+ @Inject lateinit var errorFormatter: ErrorFormatter
+
+ override fun injectWith(injector: ScreenComponent) {
+ injector.inject(this)
+ }
+
+ override fun getLayoutRes() = R.layout.activity_progress
+
+ override fun initUiAndData() {
+ val uri = intent.data
+
+ if (uri == null) {
+ // Should not happen
+ Timber.w("Uri is null")
+ finish()
+ return
+ }
+
+ if (uri.path == PATH_CONFIG) {
+ if (sessionHolder.hasActiveSession()) {
+ displayAlreadyLoginPopup(uri)
+ } else {
+ // user is not yet logged in, this is the nominal case
+ startLoginActivity(uri)
+ }
+ } else {
+ // Other link are not yet handled, but should not comes here (manifest configuration error?)
+ Timber.w("Unable to handle this uir: $uri")
+ finish()
+ }
+ }
+
+ /**
+ * Start the login screen with identity server and home server pre-filled
+ */
+ private fun startLoginActivity(uri: Uri) {
+ val intent = LoginActivity.newIntent(this, LoginConfig.parse(uri))
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(intent)
+ finish()
+ }
+
+ /**
+ * Propose to disconnect from a previous HS, when clicking on an auto config link
+ */
+ private fun displayAlreadyLoginPopup(uri: Uri) {
+ AlertDialog.Builder(this)
+ .setTitle(R.string.dialog_title_warning)
+ .setMessage(R.string.error_user_already_logged_in)
+ .setCancelable(false)
+ .setPositiveButton(R.string.logout) { _, _ ->
+ sessionHolder.getSafeActiveSession()?.signOut(object : MatrixCallback {
+ override fun onFailure(failure: Throwable) {
+ displayError(failure)
+ }
+
+ override fun onSuccess(data: Unit) {
+ Timber.d("## displayAlreadyLoginPopup(): logout succeeded")
+ sessionHolder.clearActiveSession()
+ startLoginActivity(uri)
+ }
+ }) ?: finish()
+ }
+ .setNegativeButton(R.string.cancel) { _, _ -> finish() }
+ .show()
+ }
+
+ private fun displayError(failure: Throwable) {
+ AlertDialog.Builder(this)
+ .setTitle(R.string.dialog_title_error)
+ .setMessage(errorFormatter.toHumanReadable(failure))
+ .setCancelable(false)
+ .setPositiveButton(R.string.ok) { _, _ -> finish() }
+ .show()
+ }
+
+ companion object {
+ private const val PATH_CONFIG = "/config/config"
+ }
+
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActions.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActions.kt
index 42a7320152..0691d41fcd 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/LoginActions.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActions.kt
@@ -24,5 +24,6 @@ sealed class LoginActions {
data class Login(val login: String, val password: String) : LoginActions()
data class SsoLoginSuccess(val credentials: Credentials) : LoginActions()
data class NavigateTo(val target: LoginActivity.Navigation) : LoginActions()
+ data class InitWith(val loginConfig: LoginConfig) : LoginActions()
}
diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt
index 543131f593..89497d4bea 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt
@@ -56,6 +56,12 @@ class LoginActivity : VectorBaseActivity() {
addFragment(LoginFragment(), R.id.simpleFragmentContainer)
}
+ // Get config extra
+ val loginConfig = intent.getParcelableExtra(EXTRA_CONFIG)
+ if (loginConfig != null && isFirstCreation()) {
+ loginViewModel.handle(LoginActions.InitWith(loginConfig))
+ }
+
loginViewModel.navigationLiveData.observeEvent(this) {
when (it) {
is Navigation.OpenSsoLoginFallback -> addFragmentToBackstack(LoginSsoFallbackFragment(), R.id.simpleFragmentContainer)
@@ -80,8 +86,12 @@ class LoginActivity : VectorBaseActivity() {
}
companion object {
- fun newIntent(context: Context): Intent {
- return Intent(context, LoginActivity::class.java)
+ private val EXTRA_CONFIG = "EXTRA_CONFIG"
+
+ fun newIntent(context: Context, loginConfig: LoginConfig?): Intent {
+ return Intent(context, LoginActivity::class.java).apply {
+ putExtra(EXTRA_CONFIG, loginConfig)
+ }
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginConfig.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginConfig.kt
new file mode 100644
index 0000000000..1613d1b041
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/login/LoginConfig.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotx.features.login
+
+import android.net.Uri
+import android.os.Parcelable
+import kotlinx.android.parcel.Parcelize
+
+/**
+ * Parameters extracted from a configuration url
+ * Ex: https://riot.im/config/config?hs_url=https%3A%2F%2Fexample.modular.im&is_url=https%3A%2F%2Fcustom.identity.org
+ *
+ * Note: On RiotX, identityServerUrl will never be used, so is declared private. Keep it for compatibility reason.
+ */
+@Parcelize
+data class LoginConfig(
+ val homeServerUrl: String?,
+ private val identityServerUrl: String?
+) : Parcelable {
+
+ companion object {
+ fun parse(from: Uri): LoginConfig {
+ return LoginConfig(
+ homeServerUrl = from.getQueryParameter("hs_url"),
+ identityServerUrl = from.getQueryParameter("is_url")
+ )
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt
index 5c9bf9e2aa..6e559bcbe0 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt
@@ -74,8 +74,12 @@ class LoginFragment : VectorBaseFragment() {
}
.disposeOnDestroy()
-
- homeServerField.setText(ServerUrlsRepository.getDefaultHomeServerUrl(requireContext()))
+ val initHsUrl = viewModel.getInitialHomeServerUrl()
+ if (initHsUrl != null) {
+ homeServerField.setText(initHsUrl)
+ } else {
+ homeServerField.setText(ServerUrlsRepository.getDefaultHomeServerUrl(requireContext()))
+ }
viewModel.handle(LoginActions.UpdateHomeServer(homeServerField.text.toString()))
}
diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt
index ec4e9e05e7..96de7cd0df 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt
@@ -55,6 +55,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
}
+ private var loginConfig: LoginConfig? = null
+
private val _navigationLiveData = MutableLiveData>()
val navigationLiveData: LiveData>
get() = _navigationLiveData
@@ -65,6 +67,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
fun handle(action: LoginActions) {
when (action) {
+ is LoginActions.InitWith -> handleInitWith(action)
is LoginActions.UpdateHomeServer -> handleUpdateHomeserver(action)
is LoginActions.Login -> handleLogin(action)
is LoginActions.SsoLoginSuccess -> handleSsoLoginSuccess(action)
@@ -72,6 +75,10 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
}
+ private fun handleInitWith(action: LoginActions.InitWith) {
+ loginConfig = action.loginConfig
+ }
+
private fun handleLogin(action: LoginActions.Login) {
val homeServerConnectionConfigFinal = homeServerConnectionConfig
@@ -186,6 +193,10 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
currentTask?.cancel()
}
+ fun getInitialHomeServerUrl(): String? {
+ return loginConfig?.homeServerUrl
+ }
+
fun getHomeServerUrl(): String {
return homeServerConnectionConfig?.homeServerUri?.toString() ?: ""
}
diff --git a/vector/src/main/res/layout/activity_progress.xml b/vector/src/main/res/layout/activity_progress.xml
new file mode 100644
index 0000000000..ae7b87b61e
--- /dev/null
+++ b/vector/src/main/res/layout/activity_progress.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
index 0ef32154b7..e02de69806 100644
--- a/vector/src/main/res/values/strings_riotX.xml
+++ b/vector/src/main/res/values/strings_riotX.xml
@@ -8,4 +8,8 @@
Please retry once you have accepted the terms and conditions of your homeserver.
+
+ It looks like you’re trying to connect to another homeserver. Do you want to sign out?
+
+
\ No newline at end of file