summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt117
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt11
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/PrivacyDisclaimerRepository.kt17
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt25
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/PrivacyDisclaimerFragment.kt61
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt10
-rw-r--r--android/app/src/main/res/layout/settings.xml2
-rw-r--r--android/app/src/main/res/values/strings.xml12
8 files changed, 248 insertions, 7 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt
new file mode 100644
index 0000000000..8ef4f59853
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt
@@ -0,0 +1,117 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.Dimension
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.component.ActionButton
+
+@Composable
+fun PrivacyDisclaimerScreen(
+ onPrivacyPolicyLinkClicked: () -> Unit,
+ onAcceptClicked: () -> Unit,
+) {
+ ConstraintLayout(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth()
+ .background(colorResource(id = R.color.darkBlue))
+ ) {
+ val (body, actionButtons) = createRefs()
+ val sideMargin = dimensionResource(id = R.dimen.side_margin)
+
+ Column(
+ modifier = Modifier
+ .constrainAs(body) {
+ top.linkTo(parent.top, margin = sideMargin)
+ start.linkTo(parent.start, margin = sideMargin)
+ end.linkTo(parent.end, margin = sideMargin)
+ width = Dimension.fillToConstraints
+ },
+ ) {
+ Text(
+ text = stringResource(id = R.string.privacy_disclaimer_title),
+ fontSize = 24.sp,
+ color = Color.White,
+ fontWeight = FontWeight.Bold
+ )
+
+ Text(
+ text = stringResource(id = R.string.privacy_disclaimer_body),
+ fontSize = 14.sp,
+ color = Color.White,
+ modifier = Modifier.padding(top = 10.dp)
+ )
+
+ Row(
+ modifier = Modifier.padding(top = 10.dp)
+ ) {
+ ClickableText(
+ text = AnnotatedString(stringResource(id = R.string.privacy_policy_label)),
+ onClick = { onPrivacyPolicyLinkClicked.invoke() },
+ style = TextStyle(
+ fontSize = 12.sp,
+ color = Color.White,
+ textDecoration = TextDecoration.Underline
+ )
+ )
+
+ Image(
+ painter = painterResource(id = R.drawable.icon_extlink),
+ contentDescription = null,
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .padding(start = 2.dp, top = 2.dp)
+ .width(10.dp)
+ .height(10.dp)
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .constrainAs(actionButtons) {
+ bottom.linkTo(parent.bottom, margin = sideMargin)
+ start.linkTo(parent.start, margin = sideMargin)
+ end.linkTo(parent.end, margin = sideMargin)
+ width = Dimension.fillToConstraints
+ }
+ ) {
+ ActionButton(
+ text = stringResource(id = R.string.agree_and_continue),
+ onClick = onAcceptClicked::invoke,
+ colors = ButtonDefaults.buttonColors(
+ contentColor = Color.White,
+ backgroundColor = colorResource(
+ R.color.blue
+ )
+ )
+ )
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index 4aefe0abd4..bbe12baf2e 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -12,6 +12,7 @@ import net.mullvad.mullvadvpn.ipc.EventDispatcher
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.ChangelogRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
+import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
import net.mullvad.mullvadvpn.ui.notification.AccountExpiryNotification
import net.mullvad.mullvadvpn.ui.notification.TunnelStateNotification
import net.mullvad.mullvadvpn.ui.notification.VersionInfoNotification
@@ -24,6 +25,7 @@ import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel
import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel
import net.mullvad.mullvadvpn.viewmodel.DeviceRevokedViewModel
import net.mullvad.mullvadvpn.viewmodel.LoginViewModel
+import net.mullvad.mullvadvpn.viewmodel.PrivacyDisclaimerViewModel
import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
@@ -65,6 +67,14 @@ val uiModule = module {
single { AccountRepository(get()) }
single { DeviceRepository(get()) }
+ single {
+ PrivacyDisclaimerRepository(
+ androidContext().getSharedPreferences(
+ APP_PREFERENCES_NAME,
+ Context.MODE_PRIVATE
+ )
+ )
+ }
single<IChangelogDataProvider> { ChangelogDataProvider(get()) }
@@ -80,6 +90,7 @@ val uiModule = module {
BuildConfig.ALWAYS_SHOW_CHANGELOG
)
}
+ viewModel { PrivacyDisclaimerViewModel(get()) }
}
const val APPS_SCOPE = "APPS_SCOPE"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/PrivacyDisclaimerRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/PrivacyDisclaimerRepository.kt
new file mode 100644
index 0000000000..4c43c1feb2
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/PrivacyDisclaimerRepository.kt
@@ -0,0 +1,17 @@
+package net.mullvad.mullvadvpn.repository
+
+import android.content.SharedPreferences
+
+private const val IS_PRIVACY_DISCLOSURE_ACCEPTED_KEY = "is_privacy_disclosure_accepted"
+
+class PrivacyDisclaimerRepository(
+ private val sharedPreferences: SharedPreferences
+) {
+ fun hasAcceptedPrivacyDisclosure(): Boolean {
+ return sharedPreferences.getBoolean(IS_PRIVACY_DISCLOSURE_ACCEPTED_KEY, false)
+ }
+
+ fun setPrivacyDisclosureAccepted() {
+ sharedPreferences.edit().putBoolean(IS_PRIVACY_DISCLOSURE_ACCEPTED_KEY, true).commit()
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
index e333eef965..7852d0b9f6 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
@@ -39,6 +39,7 @@ import net.mullvad.mullvadvpn.model.AccountExpiry
import net.mullvad.mullvadvpn.model.DeviceState
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
+import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
import net.mullvad.mullvadvpn.ui.fragment.ConnectFragment
import net.mullvad.mullvadvpn.ui.fragment.DeviceRevokedFragment
import net.mullvad.mullvadvpn.ui.fragment.LoadingFragment
@@ -74,6 +75,7 @@ open class MainActivity : FragmentActivity() {
private lateinit var accountRepository: AccountRepository
private lateinit var deviceRepository: DeviceRepository
+ private lateinit var privacyDisclaimerRepository: PrivacyDisclaimerRepository
private lateinit var serviceConnectionManager: ServiceConnectionManager
private lateinit var changelogViewModel: ChangelogViewModel
@@ -83,6 +85,7 @@ open class MainActivity : FragmentActivity() {
getKoin().apply {
accountRepository = get()
deviceRepository = get()
+ privacyDisclaimerRepository = get()
serviceConnectionManager = get()
changelogViewModel = get()
}
@@ -106,13 +109,18 @@ open class MainActivity : FragmentActivity() {
override fun onStart() {
Log.d("mullvad", "Starting main activity")
super.onStart()
- initializeStateHandlerAndServiceConnection(
- apiEndpointConfiguration = intent?.getApiEndpointConfigurationExtras()
- )
- checkForNotificationPermission()
+
+ if (privacyDisclaimerRepository.hasAcceptedPrivacyDisclosure()) {
+ initializeStateHandlerAndServiceConnection(
+ apiEndpointConfiguration = intent?.getApiEndpointConfigurationExtras()
+ )
+ checkForNotificationPermission()
+ } else {
+ openPrivacyDisclaimerFragment()
+ }
}
- private fun initializeStateHandlerAndServiceConnection(
+ fun initializeStateHandlerAndServiceConnection(
apiEndpointConfiguration: ApiEndpointConfiguration?
) {
launchDeviceStateHandler()
@@ -251,6 +259,13 @@ open class MainActivity : FragmentActivity() {
}
}
+ private fun openPrivacyDisclaimerFragment() {
+ supportFragmentManager.beginTransaction().apply {
+ replace(R.id.main_fragment, PrivacyDisclaimerFragment())
+ commitAllowingStateLoss()
+ }
+ }
+
private suspend fun openLoggedInView(accountToken: String, shouldDelayLogin: Boolean) {
val isNewAccount = accountToken == accountRepository.cachedCreatedAccount.value
val isExpired = isNewAccount.not() && isExpired(LOGIN_AWAIT_EXPIRY_MILLIS)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/PrivacyDisclaimerFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/PrivacyDisclaimerFragment.kt
new file mode 100644
index 0000000000..56d4937037
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/PrivacyDisclaimerFragment.kt
@@ -0,0 +1,61 @@
+package net.mullvad.mullvadvpn.ui
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.colorResource
+import androidx.fragment.app.Fragment
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
+import net.mullvad.mullvadvpn.compose.screen.PrivacyDisclaimerScreen
+import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras
+import net.mullvad.mullvadvpn.viewmodel.PrivacyDisclaimerViewModel
+import org.koin.android.ext.android.inject
+
+class PrivacyDisclaimerFragment : Fragment(), StatusBarPainter, NavigationBarPainter {
+
+ private val privacyDisclaimerViewModel: PrivacyDisclaimerViewModel by inject()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_compose, container, false).apply {
+ findViewById<ComposeView>(R.id.compose_view).setContent {
+ val topColor = colorResource(R.color.blue)
+ ScaffoldWithTopBar(
+ topBarColor = topColor,
+ statusBarColor = topColor,
+ navigationBarColor = colorResource(id = R.color.darkBlue),
+ onSettingsClicked = null,
+ content = {
+ PrivacyDisclaimerScreen(
+ onPrivacyPolicyLinkClicked = { openPrivacyPolicy() },
+ onAcceptClicked = { handleAcceptedPrivacyDisclaimer() }
+ )
+ }
+ )
+ }
+ }
+ }
+
+ private fun handleAcceptedPrivacyDisclaimer() {
+ privacyDisclaimerViewModel.setPrivacyDisclosureAccepted()
+ (activity as? MainActivity)?.initializeStateHandlerAndServiceConnection(
+ apiEndpointConfiguration = activity?.intent?.getApiEndpointConfigurationExtras()
+ )
+ }
+
+ private fun openPrivacyPolicy() {
+ val privacyPolicyUrlIntent = Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse(getString(R.string.faqs_and_guides_url))
+ )
+ context?.startActivity(privacyPolicyUrlIntent)
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt
new file mode 100644
index 0000000000..c3b63bb818
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt
@@ -0,0 +1,10 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import androidx.lifecycle.ViewModel
+import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
+
+class PrivacyDisclaimerViewModel(
+ private val privacyDisclaimerRepository: PrivacyDisclaimerRepository
+) : ViewModel() {
+ fun setPrivacyDisclosureAccepted() = privacyDisclaimerRepository.setPrivacyDisclosureAccepted()
+}
diff --git a/android/app/src/main/res/layout/settings.xml b/android/app/src/main/res/layout/settings.xml
index d590759e2e..8a00334fff 100644
--- a/android/app/src/main/res/layout/settings.xml
+++ b/android/app/src/main/res/layout/settings.xml
@@ -78,7 +78,7 @@
<net.mullvad.mullvadvpn.ui.widget.UrlCell android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
- mullvad:text="@string/privacy_policy"
+ mullvad:text="@string/privacy_policy_label"
mullvad:url="@string/privacy_policy_url" />
</LinearLayout>
</net.mullvad.mullvadvpn.ui.widget.ListenableScrollView>
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 33121b0502..2e4c5f0275 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -55,7 +55,7 @@
<string name="update_available_footer">Update available, download to remain safe.</string>
<string name="report_a_problem">Report a problem</string>
<string name="faqs_and_guides">FAQs &amp; Guides</string>
- <string name="privacy_policy">Privacy policy</string>
+ <string name="privacy_policy_label">Privacy policy</string>
<string name="account_number">Account number</string>
<string name="device_name">Device name</string>
<string name="mullvad_account_number">Mullvad account number</string>
@@ -193,4 +193,14 @@
<string name="vpn_permission_error_notification_title">VPN permission error</string>
<string name="vpn_permission_error_notification_message">Always-on VPN might be enabled for
another app</string>
+ <string name="agree_and_continue">Agree and continue</string>
+ <string name="privacy_disclaimer_title">Privacy</string>
+ <string name="privacy_disclaimer_body">To make sure that you have the most secure version and
+ to inform you of any issues with the current version that is running, the app performs version
+ checks automatically. This sends the app version and Android system version to Mullvad servers.
+ Mullvad keeps counters on number of used app versions and Android versions. The data is never
+ stored or used in any way that can identify you.\n\nIf the split tunneling feature is used,
+ then the app queries your system for a list of all installed applications. This list is only
+ retrieved in the split tunneling view. The list of installed applications is never sent from
+ the device.</string>
</resources>