From c421acd91db4decbf9a9f136ecfa2719ffada665 Mon Sep 17 00:00:00 2001
From: Guillaume Jacquart <guillaume.jacquart@hoodbrains.com>
Date: Tue, 19 Sep 2023 06:59:31 +0000
Subject: epic18: make IPScrambling work standalone

---
 .../foundation/e/advancedprivacy/KoinModule.kt     | 17 ++++++
 .../e/advancedprivacy/common/WarningDialog.kt      | 64 ++++++++++++++--------
 .../data/repositories/LocalStateRepository.kt      |  9 +++
 .../domain/entities/ShowFeaturesWarning.kt         | 29 ++++++++++
 .../domain/usecases/IpScramblingStateUseCase.kt    | 50 ++++++++++++-----
 .../domain/usecases/ShowFeaturesWarningUseCase.kt  | 25 +++++----
 .../features/dashboard/DashboardFragment.kt        |  2 +-
 .../internetprivacy/InternetPrivacyViewModel.kt    |  5 +-
 8 files changed, 151 insertions(+), 50 deletions(-)
 create mode 100644 app/src/main/java/foundation/e/advancedprivacy/domain/entities/ShowFeaturesWarning.kt

(limited to 'app/src/main/java/foundation/e/advancedprivacy')

diff --git a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt
index 534aa6b..3fbb636 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) 2023 MURENA SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
 package foundation.e.advancedprivacy
 
 import android.content.res.Resources
diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt
index 1a83692..80fc760 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt
@@ -23,23 +23,23 @@ import android.content.Context
 import android.content.Intent
 import android.graphics.drawable.ColorDrawable
 import android.os.Bundle
-import android.util.Log
 import android.view.View
 import android.widget.CheckBox
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
 import foundation.e.advancedprivacy.R
-import foundation.e.advancedprivacy.domain.entities.MainFeatures
-import foundation.e.advancedprivacy.domain.entities.MainFeatures.FAKE_LOCATION
-import foundation.e.advancedprivacy.domain.entities.MainFeatures.IP_SCRAMBLING
-import foundation.e.advancedprivacy.domain.entities.MainFeatures.TRACKERS_CONTROL
+import foundation.e.advancedprivacy.domain.entities.ShowFeaturesWarning
+import foundation.e.advancedprivacy.domain.usecases.IpScramblingStateUseCase
 import foundation.e.advancedprivacy.domain.usecases.ShowFeaturesWarningUseCase
 import foundation.e.advancedprivacy.main.MainActivity
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import org.koin.java.KoinJavaComponent.get
+import timber.log.Timber
 
-class WarningDialog : Activity() {
+class WarningDialog : AppCompatActivity() {
     companion object {
         private const val PARAM_FEATURE = "feature"
 
@@ -57,33 +57,34 @@ class WarningDialog : Activity() {
 
         private fun createIntent(
             context: Context,
-            feature: MainFeatures,
+            feature: ShowFeaturesWarning,
         ): Intent {
             val intent = Intent(context, WarningDialog::class.java)
-            intent.putExtra(PARAM_FEATURE, feature.name)
+            intent.putExtra(PARAM_FEATURE, feature)
             intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
             return intent
         }
     }
 
+    private var isWaitingForResult = false
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        getWindow().setBackgroundDrawable(ColorDrawable(0))
+        window.setBackgroundDrawable(ColorDrawable(0))
 
         val feature = try {
-            MainFeatures.valueOf(intent.getStringExtra(PARAM_FEATURE) ?: "")
+            intent.getParcelableExtra<ShowFeaturesWarning>(PARAM_FEATURE)!!
         } catch (e: Exception) {
-            Log.e("WarningDialog", "Missing mandatory activity parameter", e)
+            Timber.e("Missing mandatory activity parameter", e)
             finish()
             return
         }
-
         showWarningDialog(feature)
     }
 
-    private fun showWarningDialog(feature: MainFeatures) {
+    private fun showWarningDialog(feature: ShowFeaturesWarning) {
         val builder = AlertDialog.Builder(this)
-        builder.setOnDismissListener { finish() }
+        builder.setOnDismissListener { if (!isWaitingForResult) finish() }
 
         val content: View = layoutInflater.inflate(R.layout.alertdialog_do_not_show_again, null)
         val checkbox = content.findViewById<CheckBox>(R.id.checkbox)
@@ -91,23 +92,23 @@ class WarningDialog : Activity() {
 
         builder.setMessage(
             when (feature) {
-                TRACKERS_CONTROL -> R.string.warningdialog_trackers_message
-                FAKE_LOCATION -> R.string.warningdialog_location_message
-                IP_SCRAMBLING -> R.string.warningdialog_ipscrambling_message
+                ShowFeaturesWarning.TrackersControl -> R.string.warningdialog_trackers_message
+                ShowFeaturesWarning.FakeLocation -> R.string.warningdialog_location_message
+                is ShowFeaturesWarning.IpScrambling -> R.string.warningdialog_ipscrambling_message
             }
         )
 
         builder.setTitle(
             when (feature) {
-                TRACKERS_CONTROL -> R.string.warningdialog_trackers_title
-                FAKE_LOCATION -> R.string.warningdialog_location_title
-                IP_SCRAMBLING -> R.string.warningdialog_ipscrambling_title
+                ShowFeaturesWarning.TrackersControl -> R.string.warningdialog_trackers_title
+                ShowFeaturesWarning.FakeLocation -> R.string.warningdialog_location_title
+                is ShowFeaturesWarning.IpScrambling -> R.string.warningdialog_ipscrambling_title
             }
         )
 
         builder.setPositiveButton(
             when (feature) {
-                IP_SCRAMBLING -> R.string.warningdialog_ipscrambling_cta
+                is ShowFeaturesWarning.IpScrambling -> R.string.warningdialog_ipscrambling_cta
                 else -> R.string.ok
             }
         ) { _, _ ->
@@ -115,10 +116,17 @@ class WarningDialog : Activity() {
                 get<ShowFeaturesWarningUseCase>(ShowFeaturesWarningUseCase::class.java)
                     .doNotShowAgain(feature)
             }
-            finish()
+
+            val vpnDisclaimerIntent = (feature as? ShowFeaturesWarning.IpScrambling)
+                ?.startVpnDisclaimer
+
+            if (vpnDisclaimerIntent != null) {
+                isWaitingForResult = true
+                launchAndroidVpnDisclaimer.launch(vpnDisclaimerIntent)
+            } else finish()
         }
 
-        if (feature == TRACKERS_CONTROL) {
+        if (feature == ShowFeaturesWarning.TrackersControl) {
             builder.setNeutralButton(R.string.warningdialog_trackers_secondary_cta) { _, _ ->
                 MainActivity.deepLinkBuilder(this)
                     .setDestination(R.id.trackersFragment)
@@ -130,4 +138,14 @@ class WarningDialog : Activity() {
 
         builder.show()
     }
+
+    private val launchAndroidVpnDisclaimer = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+        val ipScramblingStateUseCase = get<IpScramblingStateUseCase>(IpScramblingStateUseCase::class.java)
+        if (result.resultCode == Activity.RESULT_OK) {
+            ipScramblingStateUseCase.startIpScrambling()
+        } else {
+            ipScramblingStateUseCase.toggle(false)
+        }
+        finish()
+    }
 }
diff --git a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt
index ba2836f..abc4de0 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2023 MURENA SAS
  * Copyright (C) 2021 E FOUNDATION
  *
  * This program is free software: you can redistribute it and/or modify
@@ -18,9 +19,11 @@
 package foundation.e.advancedprivacy.data.repositories
 
 import android.content.Context
+import android.content.Intent
 import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
 import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode
 import foundation.e.advancedprivacy.domain.entities.LocationMode
+import foundation.e.advancedprivacy.domain.entities.ShowFeaturesWarning
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharedFlow
@@ -88,6 +91,12 @@ class LocalStateRepository(context: Context) {
 
     val internetPrivacyMode: MutableStateFlow<InternetPrivacyMode> = MutableStateFlow(InternetPrivacyMode.REAL_IP)
 
+    private val _startVpnDisclaimer = MutableSharedFlow<ShowFeaturesWarning.IpScrambling>()
+    suspend fun emitStartVpnDisclaimer(intent: Intent?) {
+        _startVpnDisclaimer.emit(ShowFeaturesWarning.IpScrambling(startVpnDisclaimer = intent))
+    }
+    val startVpnDisclaimer: SharedFlow<ShowFeaturesWarning.IpScrambling> = _startVpnDisclaimer
+
     private val _otherVpnRunning = MutableSharedFlow<ApplicationDescription>()
     suspend fun emitOtherVpnRunning(appDesc: ApplicationDescription) {
         _otherVpnRunning.emit(appDesc)
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/ShowFeaturesWarning.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/ShowFeaturesWarning.kt
new file mode 100644
index 0000000..221f4e1
--- /dev/null
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/ShowFeaturesWarning.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 MURENA SAS
+ * Copyright (C) 2022 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package foundation.e.advancedprivacy.domain.entities
+
+import android.content.Intent
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+sealed class ShowFeaturesWarning : Parcelable {
+    object TrackersControl : ShowFeaturesWarning()
+    object FakeLocation : ShowFeaturesWarning()
+    data class IpScrambling(val startVpnDisclaimer: Intent? = null) : ShowFeaturesWarning()
+}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt
index a7ed660..27e7fe4 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2021 E FOUNDATION, 2023 MURENA SAS
+ * Copyright (C) 2023 MURENA SAS
+ * Copyright (C) 2021 E FOUNDATION
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +18,7 @@
 
 package foundation.e.advancedprivacy.domain.usecases
 
+import android.content.Intent
 import foundation.e.advancedprivacy.data.repositories.AppListsRepository
 import foundation.e.advancedprivacy.data.repositories.LocalStateRepository
 import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
@@ -139,7 +141,7 @@ class IpScramblingStateUseCase(
         ipScramblerModule.appList = rawList
     }
 
-    private fun applySettings(isIpScramblingEnabled: Boolean) {
+    private suspend fun applySettings(isIpScramblingEnabled: Boolean) {
         val currentMode = localStateRepository.internetPrivacyMode.value
         when {
             isIpScramblingEnabled && currentMode in setOf(REAL_IP, REAL_IP_LOADING) ->
@@ -152,23 +154,45 @@ class IpScramblingStateUseCase(
         }
     }
 
-    private fun applyStartIpScrambling() {
-        localStateRepository.internetPrivacyMode.value = HIDE_IP_LOADING
-        ipScramblerModule.prepareAndroidVpn()?.let {
-            permissionsPrivacyModule.setVpnPackageAuthorization(appDesc.packageName)
-            permissionsPrivacyModule.getAlwaysOnVpnPackage()
-        }?.let {
-            coroutineScope.launch {
+    private suspend fun applyStartIpScrambling() {
+        val authorizeVpnIntent = ipScramblerModule.prepareAndroidVpn()
+        if (authorizeVpnIntent == null) {
+            localStateRepository.emitStartVpnDisclaimer(null)
+
+            startIpScrambling()
+            return
+        }
+
+        acquireVpnAuthorization(authorizeVpnIntent)
+    }
+
+    private suspend fun acquireVpnAuthorization(authorizeVpnIntent: Intent) {
+        val authorized = permissionsPrivacyModule.setVpnPackageAuthorization(appDesc.packageName)
+        val alwaysOnVpnPackage = permissionsPrivacyModule.getAlwaysOnVpnPackage()
+
+        when {
+            authorized && alwaysOnVpnPackage == null -> {
+                localStateRepository.emitStartVpnDisclaimer(null)
+                startIpScrambling()
+            }
+            authorized && alwaysOnVpnPackage != null -> {
                 localStateRepository.emitOtherVpnRunning(
-                    permissionsPrivacyModule.getApplicationDescription(packageName = it, withIcon = false)
+                    permissionsPrivacyModule.getApplicationDescription(
+                        packageName = alwaysOnVpnPackage,
+                        withIcon = false
+                    )
                 )
+                localStateRepository.setIpScramblingSetting(enabled = false)
             }
-            localStateRepository.setIpScramblingSetting(enabled = false)
-        } ?: run {
-            ipScramblerModule.start(enableNotification = false)
+            else -> localStateRepository.emitStartVpnDisclaimer(authorizeVpnIntent)
         }
     }
 
+    fun startIpScrambling() {
+        localStateRepository.internetPrivacyMode.value = HIDE_IP_LOADING
+        ipScramblerModule.start(enableNotification = false) // change the false ?
+    }
+
     private fun map(status: IpScramblerModule.Status): InternetPrivacyMode {
         return when (status) {
             IpScramblerModule.Status.OFF -> REAL_IP
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt
index 11bce86..c99d5f1 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt
@@ -18,7 +18,10 @@
 package foundation.e.advancedprivacy.domain.usecases
 
 import foundation.e.advancedprivacy.data.repositories.LocalStateRepository
-import foundation.e.advancedprivacy.domain.entities.MainFeatures
+import foundation.e.advancedprivacy.domain.entities.ShowFeaturesWarning
+import foundation.e.advancedprivacy.domain.entities.ShowFeaturesWarning.FakeLocation
+import foundation.e.advancedprivacy.domain.entities.ShowFeaturesWarning.IpScrambling
+import foundation.e.advancedprivacy.domain.entities.ShowFeaturesWarning.TrackersControl
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.dropWhile
@@ -30,25 +33,25 @@ class ShowFeaturesWarningUseCase(
     private val localStateRepository: LocalStateRepository
 ) {
 
-    fun showWarning(): Flow<MainFeatures> {
+    fun showWarning(): Flow<ShowFeaturesWarning> {
         return merge(
             localStateRepository.blockTrackers.drop(1).dropWhile { !it }
                 .filter { it && !localStateRepository.hideWarningTrackers }
-                .map { MainFeatures.TRACKERS_CONTROL },
+                .map { TrackersControl },
             localStateRepository.fakeLocationEnabled.drop(1).dropWhile { !it }
                 .filter { it && !localStateRepository.hideWarningLocation }
-                .map { MainFeatures.FAKE_LOCATION },
-            localStateRepository.ipScramblingSetting.drop(1).dropWhile { !it }
-                .filter { it && !localStateRepository.hideWarningIpScrambling }
-                .map { MainFeatures.IP_SCRAMBLING }
+                .map { FakeLocation },
+            localStateRepository.startVpnDisclaimer.filter {
+                it.startVpnDisclaimer != null || !localStateRepository.hideWarningIpScrambling
+            }
         )
     }
 
-    fun doNotShowAgain(feature: MainFeatures) {
+    fun doNotShowAgain(feature: ShowFeaturesWarning) {
         when (feature) {
-            MainFeatures.TRACKERS_CONTROL -> localStateRepository.hideWarningTrackers = true
-            MainFeatures.FAKE_LOCATION -> localStateRepository.hideWarningLocation = true
-            MainFeatures.IP_SCRAMBLING -> localStateRepository.hideWarningIpScrambling = true
+            TrackersControl -> localStateRepository.hideWarningTrackers = true
+            FakeLocation -> localStateRepository.hideWarningLocation = true
+            is IpScrambling -> localStateRepository.hideWarningIpScrambling = true
         }
     }
 }
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
index 5eb0bb6..999955e 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
@@ -105,7 +105,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) {
         }
 
         viewLifecycleOwner.lifecycleScope.launch {
-            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                 render(viewModel.state.value)
                 viewModel.state.collect(::render)
             }
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt
index 80e00bc..059e11d 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2023 MURENA SAS
  * Copyright (C) 2021 E FOUNDATION
  *
  * This program is free software: you can redistribute it and/or modify
@@ -120,11 +121,11 @@ class InternetPrivacyViewModel(
         }
     }
 
-    private fun actionUseRealIP() {
+    private suspend fun actionUseRealIP() {
         ipScramblingStateUseCase.toggle(hideIp = false)
     }
 
-    private fun actionUseHiddenIP() {
+    private suspend fun actionUseHiddenIP() {
         ipScramblingStateUseCase.toggle(hideIp = true)
     }
 
-- 
cgit v1.2.1