summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/build.gradle3
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt8
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt73
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt210
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt163
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt24
-rw-r--r--app/src/main/res/layout/fragment_internet_activity_policy.xml84
-rw-r--r--app/src/main/res/values/strings.xml9
-rw-r--r--build.gradle4
-rw-r--r--dependencies.gradle2
-rw-r--r--embeddedmavenrepository/.gitignore1
-rw-r--r--embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.aarbin0 -> 879628 bytes
-rw-r--r--embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.pom50
-rw-r--r--embeddedmavenrepository/foundation/e/orbotservice/maven-metadata-local.xml13
-rw-r--r--embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.aarbin0 -> 28552 bytes
-rw-r--r--embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.pom35
-rw-r--r--embeddedmavenrepository/foundation/e/privacymodule.tor/maven-metadata-local.xml13
17 files changed, 632 insertions, 60 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 8d94e2b..0940721 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -95,6 +95,9 @@ dependencies {
googleImplementation project(":privacymodulesgoogle")
// include the e specific version of the modules, just for the e flavor
eImplementation project(":privacymodulese")
+
+ implementation 'foundation.e:privacymodule.tor:0.1.0'
+
implementation project(":flow-mvi")
implementation Libs.Kotlin.stdlib
implementation Libs.AndroidX.coreKtx
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
index fcc2eaa..1ab848c 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
@@ -20,8 +20,11 @@ package foundation.e.privacycentralapp
import android.app.Application
import android.content.Context
import android.os.Process
+import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyViewModelFactory
import foundation.e.privacycentralapp.features.location.FakeLocationViewModelFactory
import foundation.e.privacycentralapp.features.location.LocationApiDelegate
+import foundation.e.privacymodules.ipscrambler.IpScramblerModule
+import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
import foundation.e.privacymodules.location.FakeLocation
import foundation.e.privacymodules.location.IFakeLocation
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
@@ -39,6 +42,7 @@ class DependencyContainer constructor(val app: Application) {
private val fakeLocationModule: IFakeLocation by lazy { FakeLocation(app.applicationContext) }
private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) }
+ private val ipScramblerModule: IIpScramblerModule by lazy { IpScramblerModule(app.applicationContext) }
private val appDesc by lazy {
ApplicationDescription(
@@ -58,4 +62,8 @@ class DependencyContainer constructor(val app: Application) {
}
val blockerService = BlockerInterface.getInstance(context)
+
+ val internetPrivacyViewModelFactory by lazy {
+ InternetPrivacyViewModelFactory(ipScramblerModule, permissionsModule)
+ }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
new file mode 100644
index 0000000..4f9a6fc
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * 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
+ * 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.privacycentralapp.common
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.Switch
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import foundation.e.privacycentralapp.R
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
+open class ToggleAppsAdapter(
+ private val listener: (String, Boolean) -> Unit
+) :
+ RecyclerView.Adapter<ToggleAppsAdapter.PermissionViewHolder>() {
+
+ class PermissionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val appName: TextView = view.findViewById(R.id.app_title)
+
+ @SuppressLint("UseSwitchCompatOrMaterialCode")
+ val togglePermission: Switch = view.findViewById(R.id.toggle)
+
+ fun bind(item: Pair<ApplicationDescription, Boolean>) {
+ appName.text = item.first.label
+ togglePermission.isChecked = item.second
+
+ itemView.findViewById<ImageView>(R.id.app_icon).setImageDrawable(item.first.icon)
+ }
+ }
+
+ var dataSet: List<Pair<ApplicationDescription, Boolean>> = emptyList()
+ set(value) {
+ field = value
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_app_toggle, parent, false)
+ val holder = PermissionViewHolder(view)
+ holder.togglePermission.setOnCheckedChangeListener { _, isChecked ->
+ listener(dataSet[holder.adapterPosition].first.packageName, isChecked)
+ }
+ view.findViewById<Switch>(R.id.toggle)
+ return holder
+ }
+
+ override fun onBindViewHolder(holder: PermissionViewHolder, position: Int) {
+ val permission = dataSet[position]
+ holder.bind(permission)
+ }
+
+ override fun getItemCount(): Int = dataSet.size
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt
index b34024e..41ce9ad 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt
@@ -17,16 +17,25 @@
package foundation.e.privacycentralapp.features.internetprivacy
+import android.Manifest
+import android.app.Activity
+import android.content.Intent
import android.util.Log
import foundation.e.flowmvi.Actor
import foundation.e.flowmvi.Reducer
import foundation.e.flowmvi.SingleEventProducer
import foundation.e.flowmvi.feature.BaseFeature
-import foundation.e.privacycentralapp.dummy.DummyDataSource
-import foundation.e.privacycentralapp.dummy.InternetPrivacyMode
+import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
+import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.merge
// Define a state machine for Internet privacy feature
class InternetPrivacyFeature(
@@ -43,11 +52,34 @@ class InternetPrivacyFeature(
{ message -> Log.d("InternetPrivacyFeature", message) },
singleEventProducer
) {
- data class State(val mode: InternetPrivacyMode)
+ data class State(
+ val mode: IIpScramblerModule.Status,
+ val availableApps: List<ApplicationDescription>,
+ val ipScrambledApps: Collection<String>,
+ val selectedLocation: String,
+ val availableLocationIds: List<String>
+ ) {
+
+ val isAllAppsScrambled get() = ipScrambledApps.isEmpty()
+ fun getScrambledApps(): List<Pair<ApplicationDescription, Boolean>> {
+ return availableApps
+ .filter { it.packageName in ipScrambledApps }
+ .map { it to true }
+ }
+
+ fun getApps(): List<Pair<ApplicationDescription, Boolean>> {
+ return availableApps
+ .filter { it.packageName !in ipScrambledApps }
+ .map { it to false }
+ }
+
+ val selectedLocationPosition get() = availableLocationIds.indexOf(selectedLocation)
+ }
sealed class SingleEvent {
object RealIPSelectedEvent : SingleEvent()
object HiddenIPSelectedEvent : SingleEvent()
+ data class StartAndroidVpnActivityEvent(val intent: Intent) : SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
}
@@ -55,53 +87,171 @@ class InternetPrivacyFeature(
object LoadInternetModeAction : Action()
object UseRealIPAction : Action()
object UseHiddenIPAction : Action()
+ data class AndroidVpnActivityResultAction(val resultCode: Int) : Action()
+ data class ToggleAppIpScrambled(val packageName: String, val isIpScrambled: Boolean) : Action()
+ data class SelectLocationAction(val position: Int) : Action()
}
sealed class Effect {
- data class ModeUpdatedEffect(val mode: InternetPrivacyMode) : Effect()
+ data class ModeUpdatedEffect(val mode: IIpScramblerModule.Status) : Effect()
+ object NoEffect : Effect()
+ data class ShowAndroidVpnDisclaimerEffect(val intent: Intent) : Effect()
+ data class IpScrambledAppsUpdatedEffect(val ipScrambledApps: Collection<String>) : Effect()
+ data class AvailableAppsListEffect(val apps: List<ApplicationDescription>) : Effect()
+ data class LocationSelectedEffect(val locationId: String) : Effect()
+ data class AvailableCountriesEffect(val availableLocationsIds: List<String>) : Effect()
data class ErrorEffect(val message: String) : Effect()
}
companion object {
fun create(
- initialState: State = State(InternetPrivacyMode.REAL_IP),
- coroutineScope: CoroutineScope
+ initialState: State = State(
+ IIpScramblerModule.Status.STOPPING,
+ availableApps = emptyList(),
+ ipScrambledApps = emptyList(),
+ availableLocationIds = emptyList(),
+ selectedLocation = ""
+ ),
+ coroutineScope: CoroutineScope,
+ ipScramblerModule: IIpScramblerModule,
+ permissionsModule: PermissionsPrivacyModule
) = InternetPrivacyFeature(
initialState, coroutineScope,
reducer = { state, effect ->
when (effect) {
is Effect.ModeUpdatedEffect -> state.copy(mode = effect.mode)
- is Effect.ErrorEffect -> state
+ is Effect.IpScrambledAppsUpdatedEffect -> state.copy(ipScrambledApps = effect.ipScrambledApps)
+ is Effect.AvailableAppsListEffect -> state.copy(availableApps = effect.apps)
+ is Effect.AvailableCountriesEffect -> state.copy(availableLocationIds = effect.availableLocationsIds)
+ is Effect.LocationSelectedEffect -> state.copy(selectedLocation = effect.locationId)
+ else -> state
}
},
- actor = { _, action ->
- when (action) {
- Action.LoadInternetModeAction -> flowOf(Effect.ModeUpdatedEffect(DummyDataSource.internetActivityMode.value))
- Action.UseHiddenIPAction, Action.UseRealIPAction -> flow {
- val success =
- DummyDataSource.setInternetPrivacyMode(if (action is Action.UseHiddenIPAction) InternetPrivacyMode.HIDE_IP else InternetPrivacyMode.REAL_IP)
- emit(
- if (success) Effect.ModeUpdatedEffect(DummyDataSource.internetActivityMode.value) else Effect.ErrorEffect(
- "Couldn't update internet mode"
- )
- )
- }
- }
- },
- singleEventProducer = { _, action, effect ->
- when (action) {
- Action.UseRealIPAction, Action.UseHiddenIPAction -> when (effect) {
- is Effect.ModeUpdatedEffect -> {
- if (effect.mode == InternetPrivacyMode.REAL_IP) {
- SingleEvent.RealIPSelectedEvent
+ actor = { state, action ->
+ when {
+ action is Action.LoadInternetModeAction -> merge(
+ callbackFlow {
+ val listener = object : IIpScramblerModule.Listener {
+ override fun onStatusChanged(newStatus: IIpScramblerModule.Status) {
+ offer(Effect.ModeUpdatedEffect(newStatus))
+ }
+
+ override fun log(message: String) {}
+ override fun onTrafficUpdate(upload: Long, download: Long, read: Long, write: Long) {}
+ }
+ ipScramblerModule.addListener(listener)
+ ipScramblerModule.requestStatus()
+ awaitClose { ipScramblerModule.removeListener(listener) }
+ },
+ flow {
+ // TODO: filter deactivated apps"
+ val apps = permissionsModule.getInstalledApplications()
+ .filter {
+ permissionsModule.getPermissions(it.packageName)
+ .contains(Manifest.permission.INTERNET)
+ }.map {
+ it.icon = permissionsModule.getApplicationIcon(it.packageName)
+ it
+ }.sortedWith(object : Comparator<ApplicationDescription> {
+ override fun compare(
+ p0: ApplicationDescription?,
+ p1: ApplicationDescription?
+ ): Int {
+ return if (p0?.icon != null && p1?.icon != null) {
+ p0.label.toString().compareTo(p1.label.toString())
+ } else if (p0?.icon == null) {
+ 1
+ } else {
+ -1
+ }
+ }
+ })
+ emit(Effect.AvailableAppsListEffect(apps))
+ },
+ flowOf(Effect.IpScrambledAppsUpdatedEffect(ipScramblerModule.appList)),
+ flow {
+ val locationIds = mutableListOf("")
+ locationIds.addAll(ipScramblerModule.getAvailablesLocations().sorted())
+ emit(Effect.AvailableCountriesEffect(locationIds))
+ },
+ flowOf(Effect.LocationSelectedEffect(ipScramblerModule.exitCountry))
+ ).flowOn(Dispatchers.Default)
+ action is Action.AndroidVpnActivityResultAction ->
+ if (action.resultCode == Activity.RESULT_OK) {
+ if (state.mode in listOf(
+ IIpScramblerModule.Status.OFF,
+ IIpScramblerModule.Status.STOPPING
+ )
+ ) {
+ ipScramblerModule.start()
+ flowOf(Effect.ModeUpdatedEffect(IIpScramblerModule.Status.STARTING))
} else {
- SingleEvent.HiddenIPSelectedEvent
+ flowOf(Effect.ErrorEffect("Vpn already started"))
}
+ } else {
+ flowOf(Effect.ErrorEffect("Vpn wasn't allowed to start"))
}
- is Effect.ErrorEffect -> {
- SingleEvent.ErrorEvent(effect.message)
+
+ action is Action.UseRealIPAction && state.mode in listOf(
+ IIpScramblerModule.Status.ON,
+ IIpScramblerModule.Status.STARTING,
+ IIpScramblerModule.Status.STOPPING
+ ) -> {
+ ipScramblerModule.stop()
+ flowOf(Effect.ModeUpdatedEffect(IIpScramblerModule.Status.STOPPING))
+ }
+ action is Action.UseHiddenIPAction
+ && state.mode in listOf(
+ IIpScramblerModule.Status.OFF,
+ IIpScramblerModule.Status.STOPPING
+ ) -> {
+ ipScramblerModule.prepareAndroidVpn()?.let {
+ flowOf(Effect.ShowAndroidVpnDisclaimerEffect(it))
+ } ?: run {
+ ipScramblerModule.start()
+ flowOf(Effect.ModeUpdatedEffect(IIpScramblerModule.Status.STARTING))
}
}
+
+ action is Action.ToggleAppIpScrambled -> {
+ val ipScrambledApps = mutableSetOf<String>()
+ ipScrambledApps.addAll(ipScramblerModule.appList)
+ if (action.isIpScrambled) {
+ ipScrambledApps.add(action.packageName)
+ } else {
+ ipScrambledApps.remove(action.packageName)
+ }
+ ipScramblerModule.appList = ipScrambledApps
+ flowOf(Effect.IpScrambledAppsUpdatedEffect(ipScrambledApps = ipScrambledApps))
+ }
+ action is Action.SelectLocationAction -> {
+ val locationId = state.availableLocationIds[action.position]
+ ipScramblerModule.exitCountry = locationId
+ flowOf(Effect.LocationSelectedEffect(locationId))
+ }
+ else -> flowOf(Effect.NoEffect)
+ }
+ },
+ singleEventProducer = { _, action, effect ->
+ when {
+ effect is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
+
+ action is Action.UseHiddenIPAction
+ && effect is Effect.ShowAndroidVpnDisclaimerEffect ->
+ SingleEvent.StartAndroidVpnActivityEvent(effect.intent)
+
+ // Action.UseRealIPAction, Action.UseHiddenIPAction -> when (effect) {
+ // is Effect.ModeUpdatedEffect -> {
+ // if (effect.mode == InternetPrivacyMode.REAL_IP) {
+ // SingleEvent.RealIPSelectedEvent
+ // } else {
+ // SingleEvent.HiddenIPSelectedEvent
+ // }
+ // }
+ // is Effect.ErrorEffect -> {
+ // SingleEvent.ErrorEvent(effect.message)
+ // }
+ // }
else -> null
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt
index 5baae81..22e63e3 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt
@@ -19,22 +19,41 @@ package foundation.e.privacycentralapp.features.internetprivacy
import android.os.Bundle
import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.ProgressBar
import android.widget.RadioButton
+import android.widget.Spinner
+import android.widget.TextView
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import foundation.e.flowmvi.MVIView
+import foundation.e.privacycentralapp.DependencyContainer
+import foundation.e.privacycentralapp.PrivacyCentralApplication
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.common.NavToolbarFragment
-import foundation.e.privacycentralapp.dummy.InternetPrivacyMode
+import foundation.e.privacycentralapp.common.ToggleAppsAdapter
+import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf
+import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
+import java.util.Locale
class InternetPrivacyFragment :
NavToolbarFragment(R.layout.fragment_internet_activity_policy),
MVIView<InternetPrivacyFeature.State, InternetPrivacyFeature.Action> {
- private val viewModel: InternetPrivacyViewModel by viewModels()
+ private val dependencyContainer: DependencyContainer by lazy {
+ (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
+ }
+
+ private val viewModel: InternetPrivacyViewModel by viewModels {
+ viewModelProviderFactoryOf { dependencyContainer.internetPrivacyViewModelFactory.create() }
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -45,6 +64,8 @@ class InternetPrivacyFragment :
viewModel.internetPrivacyFeature.singleEvents.collect { event ->
when (event) {
is InternetPrivacyFeature.SingleEvent.ErrorEvent -> displayToast(event.error)
+ is InternetPrivacyFeature.SingleEvent.StartAndroidVpnActivityEvent ->
+ launchAndroidVpnDisclaimer.launch(event.intent)
InternetPrivacyFeature.SingleEvent.HiddenIPSelectedEvent -> displayToast("Your IP is hidden")
InternetPrivacyFeature.SingleEvent.RealIPSelectedEvent -> displayToast("Your IP is visible to internet")
}
@@ -60,8 +81,28 @@ class InternetPrivacyFragment :
.show()
}
+ private val launchAndroidVpnDisclaimer = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ viewModel.submitAction(InternetPrivacyFeature.Action.AndroidVpnActivityResultAction(it.resultCode))
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+
+ listOf(R.id.recycler_view_scrambled, R.id.recycler_view_to_select).forEach { viewId ->
+ view.findViewById<RecyclerView>(viewId)?.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ setHasFixedSize(true)
+ adapter = ToggleAppsAdapter { packageName, isIpScrambled ->
+ viewModel.submitAction(
+ InternetPrivacyFeature.Action.ToggleAppIpScrambled(
+ packageName,
+ isIpScrambled
+ )
+ )
+ }
+ }
+ }
+
bindClickListeners(view)
}
@@ -70,38 +111,112 @@ class InternetPrivacyFragment :
private fun bindClickListeners(fragmentView: View) {
fragmentView.let {
it.findViewById<RadioButton>(R.id.radio_use_real_ip)
- .setOnClickListener { radioButton ->
- toggleIP(radioButton)
+ .setOnClickListener {
+ viewModel.submitAction(InternetPrivacyFeature.Action.UseRealIPAction)
}
it.findViewById<RadioButton>(R.id.radio_use_hidden_ip)
- .setOnClickListener { radioButton ->
- toggleIP(radioButton)
+ .setOnClickListener {
+ viewModel.submitAction(InternetPrivacyFeature.Action.UseHiddenIPAction)
}
}
}
- private fun toggleIP(radioButton: View?) {
- if (radioButton is RadioButton) {
- val checked = radioButton.isChecked
- when (radioButton.id) {
- R.id.radio_use_real_ip ->
- if (checked) {
- viewModel.submitAction(InternetPrivacyFeature.Action.UseRealIPAction)
+ override fun render(state: InternetPrivacyFeature.State) {
+ view?.let {
+ it.findViewById<RadioButton>(R.id.radio_use_hidden_ip).apply {
+ isChecked = state.mode in listOf(
+ IIpScramblerModule.Status.ON,
+ IIpScramblerModule.Status.STARTING
+ )
+ isEnabled = state.mode != IIpScramblerModule.Status.STARTING
+ }
+ it.findViewById<RadioButton>(R.id.radio_use_real_ip)?.apply {
+ isChecked =
+ state.mode in listOf(
+ IIpScramblerModule.Status.OFF,
+ IIpScramblerModule.Status.STOPPING
+ )
+ isEnabled = state.mode != IIpScramblerModule.Status.STOPPING
+ }
+ it.findViewById<TextView>(R.id.ipscrambling_tor_status)?.apply {
+ when (state.mode) {
+ IIpScramblerModule.Status.STARTING -> {
+ text = getString(R.string.ipscrambling_is_starting)
+ visibility = View.VISIBLE
+ }
+ IIpScramblerModule.Status.STOPPING -> {
+ text = getString(R.string.ipscrambling_is_stopping)
+ visibility = View.VISIBLE
}
- R.id.radio_use_hidden_ip ->
- if (checked) {
- viewModel.submitAction(InternetPrivacyFeature.Action.UseHiddenIPAction)
+ else -> {
+ text = ""
+ visibility = View.GONE
}
+ }
}
- }
- }
- override fun render(state: InternetPrivacyFeature.State) {
- view?.let {
- it.findViewById<RadioButton>(R.id.radio_use_hidden_ip).isChecked =
- state.mode == InternetPrivacyMode.HIDE_IP
- it.findViewById<RadioButton>(R.id.radio_use_real_ip).isChecked =
- state.mode == InternetPrivacyMode.REAL_IP
+ it.findViewById<Spinner>(R.id.ipscrambling_select_location)?.apply {
+ adapter = ArrayAdapter(
+ requireContext(), android.R.layout.simple_spinner_item,
+ state.availableLocationIds.map {
+ if (it == "") {
+ getString(R.string.ipscrambling_any_location)
+ } else {
+ Locale("", it).displayCountry
+ }
+ }
+ ).apply {
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ }
+
+ setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View, position: Int, id: Long) {
+ viewModel.submitAction(InternetPrivacyFeature.Action.SelectLocationAction(position))
+ }
+
+ override fun onNothingSelected(parentView: AdapterView<*>?) {}
+ })
+
+ setSelection(state.selectedLocationPosition)
+ }
+
+ it.findViewById<TextView>(R.id.ipscrambling_activated)?.apply {
+ text = getString(
+ if (state.isAllAppsScrambled) R.string.ipscrambling_all_apps_scrambled
+ else R.string.ipscrambling_only_selected_apps_scrambled
+ )
+ }
+
+ it.findViewById<RecyclerView>(R.id.recycler_view_scrambled)?.apply {
+ (adapter as ToggleAppsAdapter?)?.dataSet = state.getScrambledApps()
+ }
+ it.findViewById<RecyclerView>(R.id.recycler_view_to_select)?.apply {
+ (adapter as ToggleAppsAdapter?)?.dataSet = state.getApps()
+ }
+
+ val viewIdsToHide = listOf(
+ R.id.ipscrambling_activated,
+ R.id.recycler_view_scrambled,
+ R.id.ipscrambling_select_apps,
+ R.id.recycler_view_to_select,
+ R.id.ipscrambling_location
+ )
+ val progressBar = it.findViewById<ProgressBar>(R.id.ipscrambling_loading)
+
+ when {
+ state.mode in listOf(
+ IIpScramblerModule.Status.STARTING,
+ IIpScramblerModule.Status.STOPPING
+ )
+ || state.availableApps.isEmpty() -> {
+ progressBar?.visibility = View.VISIBLE
+ viewIdsToHide.forEach { viewId -> it.findViewById<View>(viewId)?.visibility = View.GONE }
+ }
+ else -> {
+ progressBar?.visibility = View.GONE
+ viewIdsToHide.forEach { viewId -> it.findViewById<View>(viewId)?.visibility = View.VISIBLE }
+ }
+ }
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt
index b66b611..a6455ee 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt
@@ -19,17 +19,27 @@ package foundation.e.privacycentralapp.features.internetprivacy
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import foundation.e.privacycentralapp.common.Factory
+import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
+import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
-class InternetPrivacyViewModel : ViewModel() {
+class InternetPrivacyViewModel(
+ private val ipScramblerModule: IIpScramblerModule,
+ private val permissionsModule: PermissionsPrivacyModule
+) : ViewModel() {
private val _actions = MutableSharedFlow<InternetPrivacyFeature.Action>()
val actions = _actions.asSharedFlow()
val internetPrivacyFeature: InternetPrivacyFeature by lazy {
- InternetPrivacyFeature.create(coroutineScope = viewModelScope)
+ InternetPrivacyFeature.create(
+ coroutineScope = viewModelScope,
+ ipScramblerModule = ipScramblerModule,
+ permissionsModule = permissionsModule
+ )
}
fun submitAction(action: InternetPrivacyFeature.Action) {
@@ -38,3 +48,13 @@ class InternetPrivacyViewModel : ViewModel() {
}
}
}
+
+class InternetPrivacyViewModelFactory(
+ private val ipScramblerModule: IIpScramblerModule,
+ private val permissionsModule: PermissionsPrivacyModule
+) :
+ Factory<InternetPrivacyViewModel> {
+ override fun create(): InternetPrivacyViewModel {
+ return InternetPrivacyViewModel(ipScramblerModule, permissionsModule)
+ }
+}
diff --git a/app/src/main/res/layout/fragment_internet_activity_policy.xml b/app/src/main/res/layout/fragment_internet_activity_policy.xml
index c3021df..12094ab 100644
--- a/app/src/main/res/layout/fragment_internet_activity_policy.xml
+++ b/app/src/main/res/layout/fragment_internet_activity_policy.xml
@@ -11,11 +11,14 @@
<androidx.core.widget.NestedScrollView
android:layout_height="match_parent"
- android:layout_marginBottom="32dp"
android:layout_width="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
-
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
@@ -97,6 +100,83 @@
android:textSize="14sp"
/>
</RadioGroup>
+ <TextView android:id="@+id/ipscrambling_tor_status"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingTop="24dp"
+ android:text="@string/ipscrambling_is_starting"
+ android:textColor="@color/black"
+ android:textSize="16sp"
+ android:visibility="gone"
+ />
+
+ <ProgressBar
+ android:id="@+id/ipscrambling_loading"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_margin="24dp"
+ android:layout_gravity="center"
+ />
+
+ <LinearLayout
+ android:id="@+id/ipscrambling_location"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="32dp"
+ >
+ <TextView
+ android:id="@+id/ipscrambling_location_label"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" android:text="@string/ipscrambling_location_label"
+ android:textColor="@color/black"
+ android:textSize="16sp"
+ />
+ <Spinner android:id="@+id/ipscrambling_select_location"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/ipscrambling_activated"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingTop="32dp"
+ android:text="@string/ipscrambling_all_apps_scrambled"
+ android:textColor="@color/black"
+ android:paddingBottom="8dp"
+ android:textSize="16sp"
+ android:visibility="gone"
+ />
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/recycler_view_scrambled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
+ <TextView
+ android:id="@+id/ipscrambling_select_apps"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingStart="32dp"
+ android:paddingEnd="32dp"
+ android:paddingTop="32dp"
+ android:paddingBottom="8dp"
+ android:text="@string/ipscrambling_select_app"
+ android:textColor="@color/black"
+ android:textSize="16sp"
+ android:visibility="gone"
+ />
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/recycler_view_to_select"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3105ddb..d18ccf5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -28,6 +28,15 @@
<string name="hidden_ip">Hide IP address</string>
<string name="i_am_anonymous">I am anonymous on the internet</string>
<string name="i_am_exposing">I am exposing my real IP address</string>
+ <string name="ipscrambling_all_apps_scrambled">All apps use hidden IP</string>
+ <string name="ipscrambling_only_selected_apps_scrambled">Only the following apps use hidden IP</string>
+ <string name="ipscrambling_select_app">Select Apps to hide IP</string>
+ <string name="ipscrambling_is_starting">Tor is starting...</string>
+ <string name="ipscrambling_is_stopping">Tor is stopping...</string>
+ <string name="ipscrambling_location_label">Hidden IP\'s location</string>
+ <string name="ipscrambling_any_location">any</string>
+
+ ipscrambling_any_location
<string name="permission_control_info">Manage and control apps requesting various permissions.</string>
<string name="apps_allowed">%1$d of %2$d apps allowed</string>
diff --git a/build.gradle b/build.gradle
index 0567d32..83de839 100644
--- a/build.gradle
+++ b/build.gradle
@@ -70,7 +70,9 @@ allprojects {
password = MAPBOX_SECRET_KEY ?: ""
}
}
-
+ maven { url "https://raw.githubusercontent.com/guardianproject/gpmaven/master" }
+ maven { url 'https://jitpack.io' }
+ maven { url '../embeddedmavenrepository/'}
}
}
diff --git a/dependencies.gradle b/dependencies.gradle
index e30d4bb..00c77de 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -86,4 +86,4 @@ libs.Hilt = [
versions.mapbox="9.6.1"
libs.MapBox = [
sdk: "com.mapbox.mapboxsdk:mapbox-android-sdk:$versions.mapbox"
-] \ No newline at end of file
+]
diff --git a/embeddedmavenrepository/.gitignore b/embeddedmavenrepository/.gitignore
new file mode 100644
index 0000000..afa0879
--- /dev/null
+++ b/embeddedmavenrepository/.gitignore
@@ -0,0 +1 @@
+!*.aar \ No newline at end of file
diff --git a/embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.aar b/embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.aar
new file mode 100644
index 0000000..f8bd05b
--- /dev/null
+++ b/embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.aar
Binary files differ
diff --git a/embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.pom b/embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.pom
new file mode 100644
index 0000000..5736b70
--- /dev/null
+++ b/embeddedmavenrepository/foundation/e/orbotservice/16.4.1/orbotservice-16.4.1.pom
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>foundation.e</groupId>
+ <artifactId>orbotservice</artifactId>
+ <version>16.4.1</version>
+ <packaging>aar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.torproject</groupId>
+ <artifactId>tor-android-binary</artifactId>
+ <version>0.4.4.6</version>
+ </dependency>
+ <dependency>
+ <groupId>info.pluggabletransports.aptds</groupId>
+ <artifactId>jsocksAndroid</artifactId>
+ <version>1.0.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.jaredrummler</groupId>
+ <artifactId>android-shell</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>androidx.core</groupId>
+ <artifactId>core</artifactId>
+ <version>1.3.2</version>
+ </dependency>
+ <dependency>
+ <groupId>androidx.localbroadcastmanager</groupId>
+ <artifactId>localbroadcastmanager</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.offbynull.portmapper</groupId>
+ <artifactId>portmapper</artifactId>
+ <version>2.0.5</version>
+ </dependency>
+ <dependency>
+ <groupId>info.guardianproject</groupId>
+ <artifactId>jtorctl</artifactId>
+ <version>0.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.tladesignz</groupId>
+ <artifactId>IPtProxy</artifactId>
+ <version>0.5.2</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/embeddedmavenrepository/foundation/e/orbotservice/maven-metadata-local.xml b/embeddedmavenrepository/foundation/e/orbotservice/maven-metadata-local.xml
new file mode 100644
index 0000000..c2ae644
--- /dev/null
+++ b/embeddedmavenrepository/foundation/e/orbotservice/maven-metadata-local.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+ <groupId>foundation.e</groupId>
+ <artifactId>orbotservice</artifactId>
+ <versioning>
+ <latest>16.4.1</latest>
+ <release>16.4.1</release>
+ <versions>
+ <version>16.4.1</version>
+ </versions>
+ <lastUpdated>20210826064859</lastUpdated>
+ </versioning>
+</metadata>
diff --git a/embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.aar b/embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.aar
new file mode 100644
index 0000000..b385feb
--- /dev/null
+++ b/embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.aar
Binary files differ
diff --git a/embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.pom b/embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.pom
new file mode 100644
index 0000000..8f96af5
--- /dev/null
+++ b/embeddedmavenrepository/foundation/e/privacymodule.tor/0.1.0/privacymodule.tor-0.1.0.pom
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>foundation.e</groupId>
+ <artifactId>privacymodule.tor</artifactId>
+ <version>0.1.0</version>
+ <packaging>aar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-stdlib</artifactId>
+ <version>1.4.10</version>
+ </dependency>
+ <dependency>
+ <groupId>androidx.core</groupId>
+ <artifactId>core-ktx</artifactId>
+ <version>1.6.0</version>
+ </dependency>
+ <dependency>
+ <groupId>androidx.localbroadcastmanager</groupId>
+ <artifactId>localbroadcastmanager</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>androidx.appcompat</groupId>
+ <artifactId>appcompat</artifactId>
+ <version>1.2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>foundation.e</groupId>
+ <artifactId>orbotservice</artifactId>
+ <version>16.4.1</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/embeddedmavenrepository/foundation/e/privacymodule.tor/maven-metadata-local.xml b/embeddedmavenrepository/foundation/e/privacymodule.tor/maven-metadata-local.xml
new file mode 100644
index 0000000..0b05fb4
--- /dev/null
+++ b/embeddedmavenrepository/foundation/e/privacymodule.tor/maven-metadata-local.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+ <groupId>foundation.e</groupId>
+ <artifactId>privacymodule.tor</artifactId>
+ <versioning>
+ <latest>0.1.0</latest>
+ <release>0.1.0</release>
+ <versions>
+ <version>0.1.0</version>
+ </versions>
+ <lastUpdated>20210830142258</lastUpdated>
+ </versioning>
+</metadata>