Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit cd554d88 authored by Sunik Kupfer's avatar Sunik Kupfer Committed by GitHub
Browse files

Skip login type selection when logging in via intent (#1267)



* Move companion object to the end of class

* Skip login type selection when logging in via intent

* Skip login type page if not default login type

* Add test for implicit email intent

* Fix test

* Update KDoc

* Refactor URI handling in LoginActivity and StandardLoginTypesProvider

* Skip login type page if intent is clear, but don't skip when using defaultLoginType

* Log unclear intents

* Use data class instead of pair

---------

Co-authored-by: default avatarRicki Hirner <hirner@bitfire.at>
parent d9b4149d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -50,4 +50,14 @@ class LoginActivityTest {
        assertEquals("user", loginInfo.credentials!!.username)
        assertEquals("password", loginInfo.credentials.password)
    }

    @Test
    fun loginInfoFromIntent_implicit_email() {
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse("mailto:user@example.com"))
        val loginInfo = LoginActivity.loginInfoFromIntent(intent)
        assertEquals(null, loginInfo.baseUri)
        assertEquals("user@example.com", loginInfo.credentials!!.username)
        assertEquals(null, loginInfo.credentials.password)
    }
    
}
 No newline at end of file
+43 −37
Original line number Diff line number Diff line
@@ -23,6 +23,32 @@ import javax.inject.Inject
@AndroidEntryPoint
class LoginActivity @Inject constructor(): AppCompatActivity() {

    @Inject lateinit var loginTypesProvider: LoginTypesProvider

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val (initialLoginType, skipLoginTypePage) = loginTypesProvider.intentToInitialLoginType(intent)

        setContent {
            LoginScreen(
                initialLoginType = initialLoginType,
                skipLoginTypePage = skipLoginTypePage,
                initialLoginInfo = loginInfoFromIntent(intent),
                onNavUp = { onSupportNavigateUp() },
                onFinish = { newAccount ->
                    finish()

                    if (newAccount != null) {
                        val intent = Intent(this, AccountActivity::class.java)
                        intent.putExtra(AccountActivity.EXTRA_ACCOUNT, newAccount)
                        startActivity(intent)
                    }
                }
            )
        }
    }

    companion object {

        /**
@@ -59,18 +85,23 @@ class LoginActivity @Inject constructor(): AppCompatActivity() {
            var givenUsername: String? = null
            var givenPassword: String? = null

            // extract URI and optionally username/password from Intent data
            // extract URI or email and optionally username/password from Intent data
            val logger = Logger.getGlobal()
            intent.data?.normalizeScheme()?.let { uri ->
                try {
                    // replace caldav[s]:// and carddav[s]:// with http[s]://
                val realScheme = when (uri.scheme) {
                    // replace caldav[s]:// and carddav[s]:// with http[s]://
                    "caldav", "carddav" -> "http"
                    "caldavs", "carddavs", "davx5" -> "https"
                        "http", "https" -> uri.scheme

                    // keep these
                    "http", "https", "mailto" -> uri.scheme

                    // unknown scheme
                    else -> null
                }
                    if (realScheme != null) {

                when (realScheme) {
                    "http", "https" -> {
                        // extract user info
                        uri.userInfo?.split(':')?.let { userInfo ->
                            givenUsername = userInfo.getOrNull(0)
@@ -85,8 +116,9 @@ class LoginActivity @Inject constructor(): AppCompatActivity() {
                            null
                        }
                    }
                } catch (_: URISyntaxException) {
                    logger.warning("Got invalid URI from login Intent: $uri")

                    "mailto" ->
                        givenUsername = uri.schemeSpecificPart
                }
            }

@@ -114,30 +146,4 @@ class LoginActivity @Inject constructor(): AppCompatActivity() {

    }

    @Inject lateinit var loginTypesProvider: LoginTypesProvider

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val (initialLoginType, skipLoginTypePage) = loginTypesProvider.intentToInitialLoginType(intent)

        setContent {
            LoginScreen(
                initialLoginType = initialLoginType,
                skipLoginTypePage = skipLoginTypePage,
                initialLoginInfo = loginInfoFromIntent(intent),
                onNavUp = { onSupportNavigateUp() },
                onFinish = { newAccount ->
                    finish()

                    if (newAccount != null) {
                        val intent = Intent(this, AccountActivity::class.java)
                        intent.putExtra(AccountActivity.EXTRA_ACCOUNT, newAccount)
                        startActivity(intent)
                    }
                }
            )
        }
    }

}
 No newline at end of file
+8 −3
Original line number Diff line number Diff line
@@ -10,13 +10,18 @@ import androidx.compose.runtime.Composable

interface LoginTypesProvider {

    data class LoginAction(
        val loginType: LoginType,
        val skipLoginTypePage: Boolean
    )

    val defaultLoginType: LoginType

    /**
     * Which login type to use and whether to skip the login type selection page. Used for Nextcloud
     * login flow. May be used by other login flows.
     * Which login type to use and whether to skip the login type page. Used for Nextcloud login
     * flow and may be used for other intent started flows.
     */
    fun intentToInitialLoginType(intent: Intent): Pair<LoginType, Boolean> = Pair(defaultLoginType, false)
    fun intentToInitialLoginType(intent: Intent): LoginAction = LoginAction(defaultLoginType, false)

    /** Whether the [LoginTypePage] may be non-interactive. This causes it to be skipped in back navigation. */
    val maybeNonInteractive: Boolean
+20 −6
Original line number Diff line number Diff line
@@ -7,9 +7,13 @@ package at.bitfire.davdroid.ui.setup
import android.content.Intent
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import at.bitfire.davdroid.ui.setup.LoginTypesProvider.LoginAction
import java.util.logging.Logger
import javax.inject.Inject

class StandardLoginTypesProvider @Inject constructor() : LoginTypesProvider {
class StandardLoginTypesProvider @Inject constructor(
    private val logger: Logger
) : LoginTypesProvider {

    companion object {
        val genericLoginTypes = listOf(
@@ -26,11 +30,21 @@ class StandardLoginTypesProvider @Inject constructor() : LoginTypesProvider {

    override val defaultLoginType = UrlLogin

    override fun intentToInitialLoginType(intent: Intent) =
        if (intent.hasExtra(LoginActivity.EXTRA_LOGIN_FLOW))
            Pair(NextcloudLogin, true)
        else
            Pair(defaultLoginType, false)
    override fun intentToInitialLoginType(intent: Intent): LoginAction =
        intent.data?.normalizeScheme().let { uri ->
            when {
                intent.hasExtra(LoginActivity.EXTRA_LOGIN_FLOW) ->
                    LoginAction(NextcloudLogin, true)
                uri?.scheme == "mailto" ->
                    LoginAction(EmailLogin, true)
                listOf("caldavs", "carddavs", "davx5", "http", "https").any { uri?.scheme == it } ->
                    LoginAction(UrlLogin, true)
                else -> {
                    logger.warning("Did not understand login intent: $intent")
                    LoginAction(defaultLoginType, false) // Don't skip login type page if intent is unclear
                }
            }
        }

    @Composable
    override fun LoginTypePage(