summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-03-07 09:14:47 +0100
committerDavid Göransson <david.goransson@mullvad.net>2025-03-07 09:14:47 +0100
commit22867279b2973e560e0b5aae2f2b13c736b34d88 (patch)
tree700d547b8d02b5031595728012130c997769ff48
parent5e8dfc2adf9bdd3b2b886c7c927fcca2012559db (diff)
parent1907cedb2130d1ac279a11829ada44a623572492 (diff)
downloadmullvadvpn-22867279b2973e560e0b5aae2f2b13c736b34d88.tar.xz
mullvadvpn-22867279b2973e560e0b5aae2f2b13c736b34d88.zip
Merge branch 'offer-to-store-the-account-with-credentialmanager-on-droid-1854'
-rw-r--r--android/CHANGELOG.md6
-rw-r--r--android/app/build.gradle.kts3
-rw-r--r--android/app/src/main/AndroidManifest.xml2
-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/gradle/libs.versions.toml2
-rw-r--r--android/gradle/verification-metadata.xml14
-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
10 files changed, 64 insertions, 6 deletions
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index ef57ebdef7..76be993cd6 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -22,8 +22,12 @@ Line wrap the file at 100 chars. Th
* **Security**: in case of vulnerabilities.
## [Unreleased]
+
+### Added
+- Prompt password manager to store new account number on account creation.
+
### Changed
-- Disable Wireguard port setting when a obfuscation is selected since it is not used when an
+- Disable Wireguard port setting when a obfuscation is selected since it is not used when an
obfuscation is applied.
### Removed
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index d73f673626..51d91ad6b9 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -378,8 +378,9 @@ dependencies {
implementation(libs.commons.validator)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.datastore)
- implementation(libs.androidx.ktx)
implementation(libs.androidx.coresplashscreen)
+ implementation(libs.androidx.credentials)
+ implementation(libs.androidx.ktx)
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index dd660cda4b..7066e06bc9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -33,7 +33,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/Theme.App.Starting"
- tools:ignore="GoogleAppIndexingWarning">
+ tools:ignore="CredManMissingDal,CredentialDependency,GoogleAppIndexingWarning">
<!--
MainActivity
Must be exported in order to be launchable.
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/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
index 69abe85c2e..29a3c3e105 100644
--- a/android/gradle/libs.versions.toml
+++ b/android/gradle/libs.versions.toml
@@ -12,6 +12,7 @@ android-volley = "1.2.1"
androidx-activitycompose = "1.10.1"
androidx-appcompat = "1.7.0"
androidx-ktx = "1.15.0"
+androidx-credentials = "1.3.0"
androidx-coresplashscreen = "1.1.0-rc01"
androidx-datastore = "1.1.3"
androidx-espresso = "3.6.1"
@@ -85,6 +86,7 @@ android-volley = { module = "com.android.volley:volley", version.ref = "android-
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activitycompose" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-coresplashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-coresplashscreen" }
+androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" }
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "androidx-datastore" }
androidx-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
androidx-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-ktx" }
diff --git a/android/gradle/verification-metadata.xml b/android/gradle/verification-metadata.xml
index e08cd1d92b..29c3c60ff7 100644
--- a/android/gradle/verification-metadata.xml
+++ b/android/gradle/verification-metadata.xml
@@ -187,6 +187,7 @@
<trusting group="androidx.collection"/>
<trusting group="androidx.constraintlayout"/>
<trusting group="androidx.core"/>
+ <trusting group="androidx.credentials" name="credentials"/>
<trusting group="androidx.databinding"/>
<trusting group="androidx.datastore"/>
<trusting group="androidx.fragment"/>
@@ -413,6 +414,11 @@
<sha256 value="9516c2ae44284ea0bd3d0eade0ee638879b708cbe31e3af92ba96c300604ebc3" origin="Generated by Gradle"/>
</artifact>
</component>
+ <component group="androidx.annotation" name="annotation" version="1.5.0">
+ <artifact name="annotation-1.5.0.module">
+ <sha256 value="4c84feee2db891ff6b97d613a0d40ab96ce297b034a6927ca8479f09e82d7c2e" origin="Generated by Gradle"/>
+ </artifact>
+ </component>
<component group="androidx.annotation" name="annotation" version="1.6.0">
<artifact name="annotation-1.6.0.module">
<sha256 value="6146b6138643b2ac0590df509dd51abaea769c79fd7602eb217168fe5af78cd2" origin="Generated by Gradle"/>
@@ -1477,6 +1483,14 @@
<sha256 value="11386cfa46cbbfddb6a4059f14354c00691cf65d3d63c3618818a83326ef3c7f" origin="Generated by Gradle"/>
</artifact>
</component>
+ <component group="androidx.credentials" name="credentials" version="1.3.0">
+ <artifact name="credentials-1.3.0.aar">
+ <sha256 value="b33c1a3e2d41fc3a163dd161d3334d2510d9b2086ed923c60a6f79ee22b78984" origin="Generated by Gradle"/>
+ </artifact>
+ <artifact name="credentials-1.3.0.module">
+ <sha256 value="bd5d6f9628aa958f03823e7a913a83cf11a403e5df2150491f4ce7e1684a708a" origin="Generated by Gradle"/>
+ </artifact>
+ </component>
<component group="androidx.cursoradapter" name="cursoradapter" version="1.0.0">
<artifact name="cursoradapter-1.0.0.aar">
<sha256 value="a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564" origin="Generated by Gradle"/>
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)