diff options
Diffstat (limited to 'android')
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 & 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> |
