summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-03-01 13:00:25 +0100
committerDavid Göransson <david.goransson@mullvad.net>2025-03-07 09:07:43 +0100
commit0d733c7eac966ecd198e5f604dac2035739bda5a (patch)
treec2b294d6bfbbe535c5742aae7d0afd51654a7857 /android
parent7de489bb2aebaca7226c570a7c772c91ad0fa252 (diff)
downloadmullvadvpn-0d733c7eac966ecd198e5f604dac2035739bda5a.tar.xz
mullvadvpn-0d733c7eac966ecd198e5f604dac2035739bda5a.zip
Offer to store accout number in password manager
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt15
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt13
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt2
-rw-r--r--android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt11
-rw-r--r--android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt2
5 files changed, 40 insertions, 3 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt
index 5146d65bb0..20396c4849 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt
@@ -29,9 +29,13 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CredentialManager
+import androidx.credentials.exceptions.CreateCredentialException
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.dropUnlessResumed
+import co.touchlab.kermit.Logger
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.NavGraphs
@@ -140,6 +144,17 @@ fun Welcome(
snackbarHostState.showSnackbarImmediately(
message = context.getString(R.string.error_occurred)
)
+ is WelcomeViewModel.UiSideEffect.StoreCredentialsRequest -> {
+ // UserId is not allowed to be empty
+ val createPasswordRequest =
+ CreatePasswordRequest(id = "-", password = uiSideEffect.accountNumber.value)
+ val credentialsManager = CredentialManager.create(context)
+ try {
+ credentialsManager.createCredential(context, createPasswordRequest)
+ } catch (e: CreateCredentialException) {
+ Logger.w("Unable to create Credentials")
+ }
+ }
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt
index 0e91390262..e22055cba7 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.state.WelcomeUiState
import net.mullvad.mullvadvpn.lib.common.util.isAfterNowInstant
+import net.mullvad.mullvadvpn.lib.model.AccountNumber
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
@@ -39,12 +40,18 @@ class WelcomeViewModel(
val uiState =
combine(
connectionProxy.tunnelState,
- deviceRepository.deviceState.filterNotNull(),
+ deviceRepository.deviceState.filterNotNull().onEach {
+ viewModelScope.launch {
+ it.accountNumber()?.let { accountNumber ->
+ _uiSideEffect.send(UiSideEffect.StoreCredentialsRequest(accountNumber))
+ }
+ }
+ },
paymentUseCase.paymentAvailability,
) { tunnelState, accountState, paymentAvailability ->
WelcomeUiState(
tunnelState = tunnelState,
- accountNumber = accountState.token(),
+ accountNumber = accountState.accountNumber(),
deviceName = accountState.displayName(),
showSitePayment = !isPlayBuild,
billingPaymentState = paymentAvailability?.toPaymentState(),
@@ -122,6 +129,8 @@ class WelcomeViewModel(
data object OpenConnectScreen : UiSideEffect
+ data class StoreCredentialsRequest(val accountNumber: AccountNumber) : UiSideEffect
+
data object GenericError : UiSideEffect
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt
index ccec166a47..12131832ed 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt
@@ -15,7 +15,7 @@ sealed class DeviceState : Parcelable {
return (this as? LoggedIn)?.device?.displayName()
}
- fun token(): AccountNumber? {
+ fun accountNumber(): AccountNumber? {
return (this as? LoggedIn)?.accountNumber
}
}
diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt
index 2b9b008ad0..c940f9bfba 100644
--- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt
+++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt
@@ -152,4 +152,15 @@ class AppInteractor(
device.findObjectWithTimeout(By.desc("Remove")).click()
clickActionButtonByText("Yes, log out device")
}
+
+ fun dismissStorePasswordPromptIfShown() {
+ try {
+ device.waitForIdle()
+ val selector = By.textContains("password")
+ device.wait(Until.hasObject(selector), DEFAULT_TIMEOUT)
+ device.pressBack()
+ } catch (e: IllegalArgumentException) {
+ // This is OK since it means the password prompt wasn't shown.
+ }
+ }
}
diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt
index 05418cb34b..9b5cf85b95 100644
--- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt
+++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt
@@ -24,6 +24,8 @@ class CreateAccountMockApiTest : MockApiTest() {
app.waitForLoginPrompt()
app.attemptCreateAccount()
+ app.dismissStorePasswordPromptIfShown()
+
// Assert
val expectedResult = "1234 1234 1234 1234"
app.ensureAccountCreated(expectedResult)