diff options
17 files changed, 118 insertions, 54 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/extensions/AnyExtension.kt b/app/src/main/java/foundation/e/privacycentralapp/common/extensions/AnyExtension.kt index 5c73df9..71de99a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/extensions/AnyExtension.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/extensions/AnyExtension.kt @@ -19,10 +19,4 @@ package foundation.e.privacycentralapp.common.extensions import android.content.Context -fun Any.toText(context: Context) = when (this) { - is Int -> context.getString(this) - is String -> this - else -> this.toString() -} - fun Int.dpToPxF(context: Context): Float = this.toFloat() * context.resources.displayMetrics.density diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt index 672f260..d39ee43 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt @@ -20,6 +20,7 @@ package foundation.e.privacycentralapp.data.repositories import android.content.Context import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacycentralapp.domain.entities.LocationMode +import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -51,12 +52,12 @@ class LocalStateRepository(context: Context) { return isFirstActivation } - private val _otherVpnRunning = MutableSharedFlow<Boolean>() - suspend fun emitOtherVpnRunning() { - _otherVpnRunning.emit(true) + private val _otherVpnRunning = MutableSharedFlow<ApplicationDescription>() + suspend fun emitOtherVpnRunning(appDesc: ApplicationDescription) { + _otherVpnRunning.emit(appDesc) } - val otherVpnRunning: SharedFlow<Boolean> = _otherVpnRunning + val otherVpnRunning: SharedFlow<ApplicationDescription> = _otherVpnRunning var quickPrivacyEnabledFlow: StateFlow<Boolean> = quickPrivacyEnabledMutableFlow diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt index e1f773f..688bdc4 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt @@ -22,6 +22,7 @@ import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacycentralapp.domain.entities.LocationMode import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState import foundation.e.privacycentralapp.domain.entities.TrackerMode +import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow @@ -106,6 +107,6 @@ class GetQuickPrivacyStateUseCase( localStateRepository.setShowQuickPrivacyDisabledMessage(false) } - val otherVpnRunning: SharedFlow<Boolean> = localStateRepository.otherVpnRunning + val otherVpnRunning: SharedFlow<ApplicationDescription> = localStateRepository.otherVpnRunning } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt index 3320721..a0003eb 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt @@ -126,30 +126,32 @@ class IpScramblingStateUseCase( } private fun applySettings(isQuickPrivacyEnabled: Boolean, isIpScramblingEnabled: Boolean) { + val mode = localStateRepository.internetPrivacyMode.value when { - isQuickPrivacyEnabled && isIpScramblingEnabled -> when (localStateRepository.internetPrivacyMode.value) { - InternetPrivacyMode.REAL_IP, InternetPrivacyMode.REAL_IP_LOADING -> { - var intent = ipScramblerModule.prepareAndroidVpn() - if (intent != null) { - permissionsPrivacyModule.setVpnPackageAuthorization(appDesc.packageName) - intent = ipScramblerModule.prepareAndroidVpn() - } - - if (intent != null) { - coroutineScope.launch { - localStateRepository.emitOtherVpnRunning() - } - localStateRepository.setIpScramblingSetting(enabled = false) - } else { - ipScramblerModule.start(enableNotification = false) - } - } - else -> {} + isQuickPrivacyEnabled && isIpScramblingEnabled && + mode in setOf(InternetPrivacyMode.REAL_IP, InternetPrivacyMode.REAL_IP_LOADING) -> + applyStartIpScrambling() + + mode in setOf(InternetPrivacyMode.HIDE_IP, InternetPrivacyMode.HIDE_IP_LOADING) -> + ipScramblerModule.stop() + else -> {} } - else -> when (localStateRepository.internetPrivacyMode.value) { - InternetPrivacyMode.HIDE_IP, InternetPrivacyMode.HIDE_IP_LOADING -> ipScramblerModule.stop() - else -> {} + } + + private fun applyStartIpScrambling() { + ipScramblerModule.prepareAndroidVpn()?.let { + permissionsPrivacyModule.setVpnPackageAuthorization(appDesc.packageName) + permissionsPrivacyModule.getAlwaysOnVpnPackage() + }?.let { + coroutineScope.launch { + localStateRepository.emitOtherVpnRunning( + permissionsPrivacyModule.getApplicationDescription(packageName = it, withIcon = false) + ) } + localStateRepository.setIpScramblingSetting(enabled = false) + + }?: run { + ipScramblerModule.start(enableNotification = false) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt index 4d38ec8..21950a4 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt @@ -163,8 +163,10 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } } is SingleEvent.ToastMessageSingleEvent -> - Toast.makeText(requireContext(), event.message, Toast.LENGTH_LONG) - .show() + Toast.makeText(requireContext(), + getString(event.message, *event.args.toTypedArray()), + Toast.LENGTH_LONG + ).show() } } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt index cd7e414..a2fca86 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.features.dashboard +import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.R @@ -77,7 +78,8 @@ class DashboardViewModel( }, getPrivacyStateUseCase.otherVpnRunning.map { _singleEvents.emit(SingleEvent.ToastMessageSingleEvent( - R.string.ipscrambling_error_always_on_vpn_already_running + R.string.ipscrambling_error_always_on_vpn_already_running, + listOf(it.label?: "") )) } ).collect {} @@ -139,7 +141,9 @@ class DashboardViewModel( object NavigateToLocationSingleEvent : SingleEvent() object NavigateToPermissionsSingleEvent : SingleEvent() data class NavigateToAppDetailsEvent(val appDesc: ApplicationDescription) : SingleEvent() - data class ToastMessageSingleEvent(val message: Int) : SingleEvent() + data class ToastMessageSingleEvent( + @StringRes val message: Int, val args: List<Any> = emptyList() + ) : SingleEvent() } sealed class Action { 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 749fae4..99aa217 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 @@ -33,7 +33,6 @@ import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.NavToolbarFragment import foundation.e.privacycentralapp.common.ToggleAppsAdapter -import foundation.e.privacycentralapp.common.extensions.toText import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar import foundation.e.privacycentralapp.common.setToolTipForAsterisk import foundation.e.privacycentralapp.databinding.FragmentInternetActivityPolicyBinding @@ -137,7 +136,7 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac viewModel.singleEvents.collect { event -> when (event) { is InternetPrivacyViewModel.SingleEvent.ErrorEvent -> { - displayToast(event.error.toText(requireContext())) + displayToast(getString(event.errorResId, *event.args.toTypedArray())) } } } 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 ab5e24d..8bdb16e 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 @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.features.internetprivacy +import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.R @@ -108,7 +109,10 @@ class InternetPrivacyViewModel( launch { getQuickPrivacyStateUseCase.otherVpnRunning.collect { - _singleEvents.emit(SingleEvent.ErrorEvent(R.string.ipscrambling_error_always_on_vpn_already_running)) + _singleEvents.emit(SingleEvent.ErrorEvent( + R.string.ipscrambling_error_always_on_vpn_already_running, + listOf(it.label?: "")) + ) _state.update { it.copy(forceRedraw = !it.forceRedraw)} } @@ -148,7 +152,9 @@ class InternetPrivacyViewModel( } sealed class SingleEvent { - data class ErrorEvent(val error: Any) : SingleEvent() + data class ErrorEvent( + @StringRes val errorResId: Int, val args: List<Any> = emptyList() + ): SingleEvent() } sealed class Action { diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt index ae169b4..69e520c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt @@ -36,7 +36,6 @@ import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.NavToolbarFragment import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar import foundation.e.privacycentralapp.databinding.ApptrackersFragmentBinding -import foundation.e.privacycentralapp.common.extensions.toText import kotlinx.coroutines.launch class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { @@ -110,7 +109,7 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { viewModel.singleEvents.collect { event -> when (event) { is AppTrackersViewModel.SingleEvent.ErrorEvent -> - displayToast(event.error.toText(requireContext())) + displayToast(getString(event.errorResId)) is AppTrackersViewModel.SingleEvent.OpenUrl -> try { startActivity(Intent(Intent.ACTION_VIEW, event.url)) diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt index faa4e6b..4a81b2c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt @@ -18,6 +18,7 @@ package foundation.e.privacycentralapp.features.trackers.apptrackers import android.net.Uri +import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase @@ -123,7 +124,7 @@ class AppTrackersViewModel( sealed class SingleEvent { - data class ErrorEvent(val error: Any) : SingleEvent() + data class ErrorEvent(@StringRes val errorResId: Int) : SingleEvent() data class OpenUrl(val url: Uri) : SingleEvent() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3157d5..8705ac5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -54,7 +54,7 @@ <string name="ipscrambling_select_app">Apply this setting to all selected applications * :</string> <string name="ipscrambling_app_list_infos">Only apps with Internet permission are listed.</string> <string name="ipscrambling_error_quickprivacy_disabled">Enabled Quick Privacy to use functionalities</string> - <string name="ipscrambling_error_always_on_vpn_already_running">Please disable any 3rd-party VPN in order for Advanced Privacy to hide your real IP address.</string> + <string name="ipscrambling_error_always_on_vpn_already_running">Please disable the 3rd-party VPN %s in order for Advanced Privacy to hide your real IP address.</string> <string name="ipscrambling_warning_starting_long">Our scrambling IP service is taking time to launch. It can take a few minutes. Leaving the screen won\'t interrupt the process.</string> <!-- Location --> <string name="location_title">Manage my location</string> diff --git a/permissionse/libs/hidden-apis-stub/src/main/java/android/net/IConnectivityManager.java b/permissionse/libs/hidden-apis-stub/src/main/java/android/net/IConnectivityManager.java index d7f6ccf..53440e0 100644 --- a/permissionse/libs/hidden-apis-stub/src/main/java/android/net/IConnectivityManager.java +++ b/permissionse/libs/hidden-apis-stub/src/main/java/android/net/IConnectivityManager.java @@ -32,7 +32,7 @@ public interface IConnectivityManager { @TargetApi(29) @DeprecatedSinceApi( api = 31, - message = "TODO" + message = "Moved to android.net.VpnManager" ) boolean prepareVpn(String oldPackage, String newPackage, int userId) throws RemoteException; @@ -53,9 +53,9 @@ public interface IConnectivityManager { @TargetApi(29) @DeprecatedSinceApi( api = 31, - message = "TODO" + message = "Moved to android.net.VpnManager" ) - String getAlwaysOnVpnPackage(int userId) throws RemoteException; + public String getAlwaysOnVpnPackage(int userId) throws RemoteException; public abstract static class Stub extends Binder implements IConnectivityManager { public static IConnectivityManager asInterface(IBinder obj) { diff --git a/permissionse/libs/hidden-apis-stub/src/main/java/android/net/VpnManager.java b/permissionse/libs/hidden-apis-stub/src/main/java/android/net/VpnManager.java index 7cc9b6a..bdb9e95 100644 --- a/permissionse/libs/hidden-apis-stub/src/main/java/android/net/VpnManager.java +++ b/permissionse/libs/hidden-apis-stub/src/main/java/android/net/VpnManager.java @@ -21,6 +21,7 @@ import android.annotation.TargetApi; import androidx.annotation.DeprecatedSinceApi; import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; // Stub based on: // https://gitlab.e.foundation/e/os/android_frameworks_base/-/blob/[SDK_VERSION]/core/java/android/net/VpnManager.java @@ -50,4 +51,14 @@ public class VpnManager { int userId, int vpnType ) {} + + @TargetApi(31) + @DeprecatedSinceApi( + api = 33, + message = "Check disponibility in SDK33" + ) + @RequiresPermission("android.permission.CONTROL_ALWAYS_ON_VPN") + public String getAlwaysOnVpnPackageForUser(int userId) { + return null; + } } diff --git a/permissionse/src/main/AndroidManifest.xml b/permissionse/src/main/AndroidManifest.xml index 6a7e416..428a612 100644 --- a/permissionse/src/main/AndroidManifest.xml +++ b/permissionse/src/main/AndroidManifest.xml @@ -33,4 +33,6 @@ /> <uses-permission android:name="android.permission.CONTROL_VPN" tools:ignore="ProtectedPermissions" /> + <uses-permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN" + tools:ignore="ProtectedPermissions" /> </manifest> diff --git a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt index fe5b7ac..22be90d 100644 --- a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt +++ b/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt @@ -136,4 +136,39 @@ class PermissionsPrivacyModule(context: Context): APermissionsPrivacyModule(cont } return false } + + override fun getAlwaysOnVpnPackage(): String? { + return when(Build.VERSION.SDK_INT) { + 29, 30 -> getAlwaysOnVpnPackageSDK29() + 31, 32 -> getAlwaysOnVpnPackageSDK32() + else -> { + Log.e("Permissions-e", "Bad android sdk version") + null + } + } + } + + @TargetApi(29) + private fun getAlwaysOnVpnPackageSDK29(): String? { + val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE)) + + return try { + service.getAlwaysOnVpnPackage(UserHandle.myUserId()) + } catch(e: java.lang.Exception) { + Log.e("Permissions-e", "Bad android sdk version ", e) + return null + } + } + + @TargetApi(31) + private fun getAlwaysOnVpnPackageSDK32(): String? { + val vpnManager = context.getSystemService(Context.VPN_MANAGEMENT_SERVICE) as VpnManager + return try { + vpnManager.getAlwaysOnVpnPackageForUser(UserHandle.myUserId()) + } catch(e: java.lang.Exception) { + Log.e("Permissions-e", "Bad android sdk version ", e) + return null + } + } } diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt index 68f7ee1..1fcfd44 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt +++ b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt @@ -59,9 +59,11 @@ abstract class APermissionsPrivacyModule(protected val context: Context): IPermi /** * @see IPermissionsPrivacyModule.getInstalledApplications */ - override fun getApplicationDescription(packageName: String): ApplicationDescription { + override fun getApplicationDescription(packageName: String, withIcon: Boolean): ApplicationDescription { val appDesc = buildApplicationDescription(context.packageManager.getApplicationInfo(packageName, 0), false) - appDesc.icon = getApplicationIcon(appDesc.packageName) + if (withIcon) { + appDesc.icon = getApplicationIcon(appDesc.packageName) + } return appDesc } diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt index ba85f13..8eb3fd4 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt +++ b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt @@ -28,7 +28,10 @@ import foundation.e.privacymodules.permissions.data.PermissionDescription */ interface IPermissionsPrivacyModule { - fun buildApplicationDescription(appInfo: ApplicationInfo, withIcon: Boolean = true): ApplicationDescription + fun buildApplicationDescription( + appInfo: ApplicationInfo, + withIcon: Boolean = true + ): ApplicationDescription /** * List the installed application on the device which have not the FLAGS_SYSTEM. @@ -51,13 +54,12 @@ interface IPermissionsPrivacyModule { fun getPermissionDescription(permissionName: String): PermissionDescription - /** * Get the filled up [ApplicationDescription] for the app specified by its [packageName] * @param packageName the appId of the app * @return the informations about the app. */ - fun getApplicationDescription(packageName: String): ApplicationDescription + fun getApplicationDescription(packageName: String, withIcon: Boolean = true): ApplicationDescription /** * Check if the current runtime permission is granted for the specified app. @@ -68,7 +70,6 @@ interface IPermissionsPrivacyModule { */ fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean - /** * Get the appOps mode for the specified [appOpPermissionName] of the specified application. * @@ -94,7 +95,6 @@ interface IPermissionsPrivacyModule { grant: Boolean ): Boolean - /** * Change the appOp Mode for the specified appOpPermission and application. * @param appDesc the application @@ -124,4 +124,9 @@ interface IPermissionsPrivacyModule { */ fun setVpnPackageAuthorization(packageName: String): Boolean + /** + * Returns the package name of the currently set always-on VPN application, or null. + */ + fun getAlwaysOnVpnPackage(): String? + }
\ No newline at end of file |