summaryrefslogtreecommitdiff
path: root/app/src/main/java/foundation/e/privacycentralapp
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt211
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/Notifications.kt210
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt36
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt59
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt71
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt36
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt23
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt333
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt33
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/RightRadioButton.kt43
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/TextViewHelpers.kt63
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/ThrottleFlow.kt36
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt76
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/ToolbarFragment.kt45
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/WarningDialog.kt130
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/extensions/AnyExtension.kt22
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt281
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/CityDataSource.kt46
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt116
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt129
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt59
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt29
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt22
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/MainFeatures.kt22
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt24
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackerMode.kt22
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt25
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt39
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt209
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt89
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt170
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/ShowFeaturesWarningUseCase.kt54
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt105
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt278
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt33
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt307
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt37
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt158
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt201
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt36
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt157
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt376
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt53
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt29
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt126
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt218
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt28
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt95
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt189
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt42
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt172
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt92
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt106
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt156
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt42
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt381
56 files changed, 0 insertions, 6180 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
deleted file mode 100644
index aab81d5..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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
- * 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
-
-import android.app.Application
-import android.content.Context
-import android.os.Process
-import androidx.lifecycle.DEFAULT_ARGS_KEY
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewmodel.CreationExtras
-import foundation.e.privacycentralapp.common.WarningDialog
-import foundation.e.privacycentralapp.data.repositories.AppListsRepository
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-import foundation.e.privacycentralapp.data.repositories.TrackersRepository
-import foundation.e.privacycentralapp.domain.usecases.AppListUseCase
-import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.ShowFeaturesWarningUseCase
-import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
-import foundation.e.privacycentralapp.dummy.CityDataSource
-import foundation.e.privacycentralapp.features.dashboard.DashboardViewModel
-import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyViewModel
-import foundation.e.privacycentralapp.features.location.FakeLocationViewModel
-import foundation.e.privacycentralapp.features.trackers.TrackersViewModel
-import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment
-import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersViewModel
-import foundation.e.privacymodules.fakelocation.FakeLocationModule
-import foundation.e.privacymodules.ipscrambler.IpScramblerModule
-import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
-import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import foundation.e.privacymodules.permissions.data.ProfileType
-import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule
-import foundation.e.privacymodules.trackers.api.TrackTrackersPrivacyModule
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.GlobalScope
-
-/**
- * Simple container to hold application wide dependencies.
- *
- */
-@OptIn(DelicateCoroutinesApi::class)
-class DependencyContainer(val app: Application) {
- val context: Context by lazy { app.applicationContext }
-
- // Drivers
- private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(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(
- packageName = context.packageName,
- uid = Process.myUid(),
- label = context.resources.getString(R.string.app_name),
- icon = null,
- profileId = -1,
- profileType = ProfileType.MAIN
- )
- }
-
- private val blockTrackersPrivacyModule by lazy { BlockTrackersPrivacyModule.getInstance(context) }
- private val trackTrackersPrivacyModule by lazy { TrackTrackersPrivacyModule.getInstance(context) }
-
- // Repositories
- private val localStateRepository by lazy { LocalStateRepository(context) }
- private val trackersRepository by lazy { TrackersRepository(context) }
- private val appListsRepository by lazy { AppListsRepository(permissionsModule, context, GlobalScope) }
-
- // Usecases
- val getQuickPrivacyStateUseCase by lazy {
- GetQuickPrivacyStateUseCase(localStateRepository)
- }
- private val ipScramblingStateUseCase by lazy {
- IpScramblingStateUseCase(
- ipScramblerModule, permissionsModule, appDesc, localStateRepository,
- appListsRepository, GlobalScope
- )
- }
- private val appListUseCase = AppListUseCase(appListsRepository)
-
- val trackersStatisticsUseCase by lazy {
- TrackersStatisticsUseCase(trackTrackersPrivacyModule, blockTrackersPrivacyModule, appListsRepository, context.resources)
- }
-
- val trackersStateUseCase by lazy {
- TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, localStateRepository, trackersRepository, appListsRepository, GlobalScope)
- }
-
- private val fakeLocationStateUseCase by lazy {
- FakeLocationStateUseCase(
- fakeLocationModule, permissionsModule, localStateRepository, CityDataSource, appDesc, context, GlobalScope
- )
- }
-
- val showFeaturesWarningUseCase by lazy {
- ShowFeaturesWarningUseCase(localStateRepository = localStateRepository)
- }
-
- val viewModelsFactory by lazy {
- ViewModelsFactory(
- getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase,
- trackersStatisticsUseCase = trackersStatisticsUseCase,
- trackersStateUseCase = trackersStateUseCase,
- fakeLocationStateUseCase = fakeLocationStateUseCase,
- ipScramblerModule = ipScramblerModule,
- ipScramblingStateUseCase = ipScramblingStateUseCase,
- appListUseCase = appListUseCase
- )
- }
-
- // Background
- fun initBackgroundSingletons() {
- trackersStateUseCase
- ipScramblingStateUseCase
- fakeLocationStateUseCase
-
- UpdateTrackersWorker.periodicUpdate(context)
-
- WarningDialog.startListening(
- showFeaturesWarningUseCase,
- GlobalScope,
- context
- )
-
- Widget.startListening(
- context,
- getQuickPrivacyStateUseCase,
- trackersStatisticsUseCase,
- )
-
- Notifications.startListening(
- context,
- getQuickPrivacyStateUseCase,
- permissionsModule,
- GlobalScope
- )
- }
-}
-
-@Suppress("LongParameterList")
-class ViewModelsFactory(
- private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
- private val trackersStateUseCase: TrackersStateUseCase,
- private val fakeLocationStateUseCase: FakeLocationStateUseCase,
- private val ipScramblerModule: IIpScramblerModule,
- private val ipScramblingStateUseCase: IpScramblingStateUseCase,
- private val appListUseCase: AppListUseCase
-) : ViewModelProvider.Factory {
-
- @Suppress("UNCHECKED_CAST")
- override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
- return when (modelClass) {
- AppTrackersViewModel::class.java -> {
- val app = extras[DEFAULT_ARGS_KEY]?.getInt(AppTrackersFragment.PARAM_APP_UID)?.let {
- appListUseCase.getApp(it)
- } ?: appListUseCase.dummySystemApp
-
- AppTrackersViewModel(
- app = app,
- trackersStateUseCase = trackersStateUseCase,
- trackersStatisticsUseCase = trackersStatisticsUseCase,
- getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase
- )
- }
-
- TrackersViewModel::class.java ->
- TrackersViewModel(
- trackersStatisticsUseCase = trackersStatisticsUseCase
- )
- FakeLocationViewModel::class.java ->
- FakeLocationViewModel(
- getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase,
- fakeLocationStateUseCase = fakeLocationStateUseCase
- )
- InternetPrivacyViewModel::class.java ->
- InternetPrivacyViewModel(
- ipScramblerModule = ipScramblerModule,
- getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase,
- ipScramblingStateUseCase = ipScramblingStateUseCase,
- appListUseCase = appListUseCase
- )
- DashboardViewModel::class.java ->
- DashboardViewModel(
- getPrivacyStateUseCase = getQuickPrivacyStateUseCase,
- trackersStatisticsUseCase = trackersStatisticsUseCase
- )
- else -> throw IllegalArgumentException("Unknown class $modelClass")
- } as T
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/Notifications.kt b/app/src/main/java/foundation/e/privacycentralapp/Notifications.kt
deleted file mode 100644
index 0df3e18..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/Notifications.kt
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2022 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.privacycentralapp
-
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import androidx.annotation.StringRes
-import androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationManagerCompat
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
-import foundation.e.privacycentralapp.domain.entities.MainFeatures
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-import foundation.e.privacycentralapp.main.MainActivity
-import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-
-object Notifications {
- const val CHANNEL_FIRST_BOOT = "first_boot_notification"
- const val CHANNEL_FAKE_LOCATION_FLAG = "fake_location_flag"
- const val CHANNEL_IPSCRAMBLING_FLAG = "ipscrambling_flag"
-
- const val NOTIFICATION_FIRST_BOOT = 1000
- const val NOTIFICATION_FAKE_LOCATION_FLAG = NOTIFICATION_FIRST_BOOT + 1
- const val NOTIFICATION_IPSCRAMBLING_FLAG = NOTIFICATION_FAKE_LOCATION_FLAG + 1
-
- fun showFirstBootNotification(context: Context) {
- createNotificationFirstBootChannel(context)
- val notificationBuilder: NotificationCompat.Builder = notificationBuilder(
- context,
- NotificationContent(
- channelId = CHANNEL_FIRST_BOOT,
- icon = R.drawable.ic_notification_logo,
- title = R.string.first_notification_title,
- description = R.string.first_notification_summary,
- destinationIntent =
- context.packageManager.getLaunchIntentForPackage(context.packageName)
- )
- )
- .setAutoCancel(true)
-
- NotificationManagerCompat.from(context).notify(
- NOTIFICATION_FIRST_BOOT, notificationBuilder.build()
- )
- }
-
- fun startListening(
- appContext: Context,
- getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- permissionsPrivacyModule: PermissionsPrivacyModule,
- appScope: CoroutineScope
- ) {
- createNotificationFlagChannel(
- context = appContext,
- permissionsPrivacyModule = permissionsPrivacyModule,
- channelId = CHANNEL_FAKE_LOCATION_FLAG,
- channelName = R.string.notifications_fake_location_channel_name,
- channelDescription = R.string.notifications_fake_location_channel_description
- )
-
- createNotificationFlagChannel(
- context = appContext,
- permissionsPrivacyModule = permissionsPrivacyModule,
- channelId = CHANNEL_IPSCRAMBLING_FLAG,
- channelName = R.string.notifications_ipscrambling_channel_name,
- channelDescription = R.string.notifications_ipscrambling_channel_description
- )
-
- getQuickPrivacyStateUseCase.isLocationHidden.onEach {
- if (it) {
- showFlagNotification(appContext, MainFeatures.FAKE_LOCATION)
- } else {
- hideFlagNotification(appContext, MainFeatures.FAKE_LOCATION)
- }
- }.launchIn(appScope)
-
- getQuickPrivacyStateUseCase.ipScramblingMode.map {
- it != InternetPrivacyMode.REAL_IP
- }.distinctUntilChanged().onEach {
- if (it) {
- showFlagNotification(appContext, MainFeatures.IP_SCRAMBLING)
- } else {
- hideFlagNotification(appContext, MainFeatures.IP_SCRAMBLING)
- }
- }.launchIn(appScope)
- }
-
- private fun createNotificationFirstBootChannel(context: Context) {
- val channel = NotificationChannel(
- CHANNEL_FIRST_BOOT,
- context.getString(R.string.notifications_first_boot_channel_name),
- NotificationManager.IMPORTANCE_HIGH
- )
- NotificationManagerCompat.from(context).createNotificationChannel(channel)
- }
-
- private fun createNotificationFlagChannel(
- context: Context,
- permissionsPrivacyModule: PermissionsPrivacyModule,
- channelId: String,
- @StringRes channelName: Int,
- @StringRes channelDescription: Int,
- ) {
- val channel = NotificationChannel(
- channelId, context.getString(channelName), NotificationManager.IMPORTANCE_LOW
- )
- channel.description = context.getString(channelDescription)
- permissionsPrivacyModule.setBlockable(channel)
- NotificationManagerCompat.from(context).createNotificationChannel(channel)
- }
-
- private fun showFlagNotification(context: Context, feature: MainFeatures) {
- when (feature) {
- MainFeatures.FAKE_LOCATION -> showFlagNotification(
- context = context,
- id = NOTIFICATION_FAKE_LOCATION_FLAG,
- content = NotificationContent(
- channelId = CHANNEL_FAKE_LOCATION_FLAG,
- icon = R.drawable.ic_fmd_bad,
- title = R.string.notifications_fake_location_title,
- description = R.string.notifications_fake_location_content,
- destinationIntent = MainActivity.createFakeLocationIntent(context),
- )
- )
- MainFeatures.IP_SCRAMBLING -> showFlagNotification(
- context = context,
- id = NOTIFICATION_IPSCRAMBLING_FLAG,
- content = NotificationContent(
- channelId = CHANNEL_IPSCRAMBLING_FLAG,
- icon = R.drawable.ic_language,
- title = R.string.notifications_ipscrambling_title,
- description = R.string.notifications_ipscrambling_content,
- destinationIntent = MainActivity.createIpScramblingIntent(context),
- )
- )
- else -> {}
- }
- }
-
- private fun showFlagNotification(
- context: Context,
- id: Int,
- content: NotificationContent,
- ) {
- val builder = notificationBuilder(context, content)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setOngoing(true)
-
- NotificationManagerCompat.from(context).notify(id, builder.build())
- }
-
- private fun hideFlagNotification(context: Context, feature: MainFeatures) {
- val id = when (feature) {
- MainFeatures.FAKE_LOCATION -> NOTIFICATION_FAKE_LOCATION_FLAG
- MainFeatures.IP_SCRAMBLING -> NOTIFICATION_IPSCRAMBLING_FLAG
- else -> return
- }
- NotificationManagerCompat.from(context).cancel(id)
- }
-
- private data class NotificationContent(
- val channelId: String,
- val icon: Int,
- val title: Int,
- val description: Int,
- val destinationIntent: Intent?
- )
-
- private fun notificationBuilder(
- context: Context,
- content: NotificationContent
- ): NotificationCompat.Builder {
- val builder = NotificationCompat.Builder(context, content.channelId)
- .setSmallIcon(content.icon)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setContentTitle(context.getString(content.title))
- .setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(content.description)))
-
- content.destinationIntent?.let {
- it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- val pendingIntent: PendingIntent = PendingIntent.getActivity(
- context, 0, it, PendingIntent.FLAG_IMMUTABLE
- )
- builder.setContentIntent(pendingIntent)
- }
-
- return builder
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt b/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt
deleted file mode 100644
index 2f718b5..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION, 2022 - 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.privacycentralapp
-
-import android.app.Application
-import com.mapbox.mapboxsdk.Mapbox
-import foundation.e.lib.telemetry.Telemetry
-
-class PrivacyCentralApplication : Application() {
-
- // Initialize the dependency container.
- val dependencyContainer: DependencyContainer by lazy { DependencyContainer(this) }
-
- override fun onCreate() {
- super.onCreate()
- Telemetry.init(BuildConfig.SENTRY_DSN, this, true)
- Mapbox.getTelemetry()?.setUserTelemetryRequestState(false)
-
- dependencyContainer.initBackgroundSingletons()
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt b/app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt
deleted file mode 100644
index 13511da..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.privacycentralapp
-
-import android.content.Context
-import androidx.work.Constraints
-import androidx.work.CoroutineWorker
-import androidx.work.ExistingPeriodicWorkPolicy
-import androidx.work.NetworkType
-import androidx.work.PeriodicWorkRequestBuilder
-import androidx.work.WorkManager
-import androidx.work.WorkerParameters
-import java.util.concurrent.TimeUnit
-
-class UpdateTrackersWorker(appContext: Context, workerParams: WorkerParameters) :
- CoroutineWorker(appContext, workerParams) {
-
- override suspend fun doWork(): Result {
- val trackersStateUseCase = (applicationContext as PrivacyCentralApplication)
- .dependencyContainer.trackersStateUseCase
-
- trackersStateUseCase.updateTrackers()
- return Result.success()
- }
-
- companion object {
- private val constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)
- .build()
-
- fun periodicUpdate(context: Context) {
- val request = PeriodicWorkRequestBuilder<UpdateTrackersWorker>(
- 7, TimeUnit.DAYS
- )
- .setConstraints(constraints).build()
-
- WorkManager.getInstance(context).enqueueUniquePeriodicWork(
- UpdateTrackersWorker::class.qualifiedName ?: "",
- ExistingPeriodicWorkPolicy.KEEP,
- request
- )
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
deleted file mode 100644
index 2fbbc34..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION, 2022 - 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.privacycentralapp.common
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.domain.entities.AppWithCounts
-
-class AppsAdapter(
- private val itemsLayout: Int,
- private val listener: (Int) -> Unit
-) :
- RecyclerView.Adapter<AppsAdapter.ViewHolder>() {
-
- class ViewHolder(view: View, private val listener: (Int) -> Unit) : RecyclerView.ViewHolder(view) {
- val appName: TextView = view.findViewById(R.id.title)
- val counts: TextView = view.findViewById(R.id.counts)
- val icon: ImageView = view.findViewById(R.id.icon)
- fun bind(item: AppWithCounts) {
- appName.text = item.label
- counts.text = if (item.trackersCount > 0) itemView.context.getString(
- R.string.trackers_app_trackers_counts,
- item.blockedTrackersCount,
- item.trackersCount,
- item.leaks
- ) else ""
- icon.setImageDrawable(item.icon)
-
- itemView.setOnClickListener { listener(item.uid) }
- }
- }
-
- var dataSet: List<AppWithCounts> = emptyList()
- set(value) {
- field = value
- notifyDataSetChanged()
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(itemsLayout, parent, false)
- return ViewHolder(view, listener)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val app = dataSet[position]
- holder.bind(app)
- }
-
- override fun getItemCount(): Int = dataSet.size
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt b/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt
deleted file mode 100644
index d7902ee..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.privacycentralapp.common
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import foundation.e.privacycentralapp.Notifications
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-
-class BootCompletedReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent?) {
- if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
- val localStateRepository = LocalStateRepository(context)
- if (localStateRepository.firstBoot) {
- Notifications.showFirstBootNotification(context)
- localStateRepository.firstBoot = false
- }
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt b/app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt
deleted file mode 100644
index 4c7f436..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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
-
-// Definition of a Factory interface with a function to create objects of a type
-interface Factory<T> {
- fun create(): T
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt b/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt
deleted file mode 100644
index a25b68e..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * 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.content.Context
-import android.graphics.Canvas
-import android.text.Spannable
-import android.text.SpannableStringBuilder
-import android.text.style.DynamicDrawableSpan
-import android.text.style.ImageSpan
-import android.view.View
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.core.text.toSpannable
-import androidx.core.view.isVisible
-import com.github.mikephil.charting.charts.BarChart
-import com.github.mikephil.charting.components.AxisBase
-import com.github.mikephil.charting.components.MarkerView
-import com.github.mikephil.charting.components.XAxis
-import com.github.mikephil.charting.components.YAxis
-import com.github.mikephil.charting.components.YAxis.AxisDependency
-import com.github.mikephil.charting.data.BarData
-import com.github.mikephil.charting.data.BarDataSet
-import com.github.mikephil.charting.data.BarEntry
-import com.github.mikephil.charting.data.Entry
-import com.github.mikephil.charting.formatter.ValueFormatter
-import com.github.mikephil.charting.highlight.Highlight
-import com.github.mikephil.charting.listener.OnChartValueSelectedListener
-import com.github.mikephil.charting.renderer.XAxisRenderer
-import com.github.mikephil.charting.utils.MPPointF
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.common.extensions.dpToPxF
-import kotlin.math.floor
-
-class GraphHolder(val barChart: BarChart, val context: Context, val isMarkerAbove: Boolean = true) {
- var data = emptyList<Pair<Int, Int>>()
- set(value) {
- field = value
- refreshDataSet()
- }
- var labels = emptyList<String>()
-
- var graduations: List<String?>? = null
-
- private var isHighlighted = false
-
- init {
- barChart.description = null
- barChart.setTouchEnabled(true)
- barChart.setScaleEnabled(false)
-
- barChart.setDrawGridBackground(false)
- barChart.setDrawBorders(false)
- barChart.axisLeft.isEnabled = false
- barChart.axisRight.isEnabled = false
-
- barChart.legend.isEnabled = false
-
- if (isMarkerAbove) prepareXAxisDashboardDay() else prepareXAxisMarkersBelow()
-
- val periodMarker = PeriodMarkerView(context, isMarkerAbove)
- periodMarker.chartView = barChart
- barChart.marker = periodMarker
-
- barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
- override fun onValueSelected(e: Entry?, h: Highlight?) {
- h?.let {
- val index = it.x.toInt()
- if (index >= 0 &&
- index < labels.size &&
- index < this@GraphHolder.data.size
- ) {
- val period = labels[index]
- val (blocked, leaked) = this@GraphHolder.data[index]
- periodMarker.setLabel(period, blocked, leaked)
- }
- }
- isHighlighted = true
- }
-
- override fun onNothingSelected() {
- isHighlighted = false
- }
- })
- }
-
- private fun prepareXAxisDashboardDay() {
- barChart.extraTopOffset = 44f
-
- barChart.offsetTopAndBottom(0)
-
- barChart.setXAxisRenderer(object : XAxisRenderer(barChart.viewPortHandler, barChart.xAxis, barChart.getTransformer(AxisDependency.LEFT)) {
- override fun renderAxisLine(c: Canvas) {
- mAxisLinePaint.color = mXAxis.axisLineColor
- mAxisLinePaint.strokeWidth = mXAxis.axisLineWidth
- mAxisLinePaint.pathEffect = mXAxis.axisLineDashPathEffect
-
- // Top line
- c.drawLine(
- mViewPortHandler.contentLeft(),
- mViewPortHandler.contentTop(), mViewPortHandler.contentRight(),
- mViewPortHandler.contentTop(), mAxisLinePaint
- )
-
- // Bottom line
- c.drawLine(
- mViewPortHandler.contentLeft(),
- mViewPortHandler.contentBottom() - 7.dpToPxF(context),
- mViewPortHandler.contentRight(),
- mViewPortHandler.contentBottom() - 7.dpToPxF(context),
- mAxisLinePaint
- )
- }
-
- override fun renderGridLines(c: Canvas) {
- if (!mXAxis.isDrawGridLinesEnabled || !mXAxis.isEnabled) return
- val clipRestoreCount = c.save()
- c.clipRect(gridClippingRect)
- if (mRenderGridLinesBuffer.size != mAxis.mEntryCount * 2) {
- mRenderGridLinesBuffer = FloatArray(mXAxis.mEntryCount * 2)
- }
- val positions = mRenderGridLinesBuffer
- run {
- var i = 0
- while (i < positions.size) {
- positions[i] = mXAxis.mEntries[i / 2]
- positions[i + 1] = mXAxis.mEntries[i / 2]
- i += 2
- }
- }
-
- mTrans.pointValuesToPixel(positions)
- setupGridPaint()
- val gridLinePath = mRenderGridLinesPath
- gridLinePath.reset()
- var i = 0
- while (i < positions.size) {
- val bottomY = if (graduations?.getOrNull(i / 2) != null) 0 else 3
- val x = positions[i]
- gridLinePath.moveTo(x, mViewPortHandler.contentBottom() - 7.dpToPxF(context))
- gridLinePath.lineTo(x, mViewPortHandler.contentBottom() - bottomY.dpToPxF(context))
-
- c.drawPath(gridLinePath, mGridPaint)
-
- gridLinePath.reset()
-
- i += 2
- }
- c.restoreToCount(clipRestoreCount)
- }
- })
-
- barChart.setDrawValueAboveBar(false)
- barChart.xAxis.apply {
- isEnabled = true
- position = XAxis.XAxisPosition.BOTTOM
-
- setDrawGridLines(true)
- setDrawLabels(true)
- setCenterAxisLabels(false)
- setLabelCount(25, true)
- textColor = context.getColor(R.color.primary_text)
- valueFormatter = object : ValueFormatter() {
- override fun getAxisLabel(value: Float, axis: AxisBase?): String {
- return graduations?.getOrNull(floor(value).toInt() + 1) ?: ""
- }
- }
- }
- }
-
- private fun prepareXAxisMarkersBelow() {
- barChart.extraBottomOffset = 44f
-
- barChart.offsetTopAndBottom(0)
- barChart.setDrawValueAboveBar(false)
-
- barChart.xAxis.apply {
- isEnabled = true
- position = XAxis.XAxisPosition.BOTH_SIDED
- setDrawGridLines(false)
- setDrawLabels(false)
- }
- }
-
- fun highlightIndex(index: Int) {
- if (index >= 0 && index < data.size) {
- val xPx = barChart.getTransformer(YAxis.AxisDependency.LEFT)
- .getPixelForValues(index.toFloat(), 0f)
- .x
- val highlight = Highlight(
- index.toFloat(), 0f,
- xPx.toFloat(), 0f,
- 0, YAxis.AxisDependency.LEFT
- )
-
- barChart.highlightValue(highlight, true)
- }
- }
-
- private fun refreshDataSet() {
- val trackersDataSet = BarDataSet(
- data.mapIndexed { index, value ->
- BarEntry(
- index.toFloat(),
- floatArrayOf(value.first.toFloat(), value.second.toFloat())
- )
- },
- ""
- ).apply {
-
- val blockedColor = ContextCompat.getColor(context, R.color.accent)
- val leakedColor = ContextCompat.getColor(context, R.color.red_off)
-
- colors = listOf(
- blockedColor,
- leakedColor
- )
-
- setDrawValues(false)
- }
-
- barChart.data = BarData(trackersDataSet)
- barChart.invalidate()
- }
-}
-
-class PeriodMarkerView(context: Context, private val isMarkerAbove: Boolean = true) : MarkerView(context, R.layout.chart_tooltip) {
- enum class ArrowPosition { LEFT, CENTER, RIGHT }
-
- private val arrowMargins = 10.dpToPxF(context)
- private val mOffset2 = MPPointF(0f, 0f)
-
- private fun getArrowPosition(posX: Float): ArrowPosition {
- val halfWidth = width / 2
-
- return chartView?.let { chart ->
- if (posX < halfWidth) {
- ArrowPosition.LEFT
- } else if (chart.width - posX < halfWidth) {
- ArrowPosition.RIGHT
- } else {
- ArrowPosition.CENTER
- }
- } ?: ArrowPosition.CENTER
- }
-
- private fun showArrow(position: ArrowPosition?) {
- val ids = listOf(
- R.id.arrow_top_left, R.id.arrow_top_center, R.id.arrow_top_right,
- R.id.arrow_bottom_left, R.id.arrow_bottom_center, R.id.arrow_bottom_right
- )
-
- val toShow = if (isMarkerAbove) when (position) {
- ArrowPosition.LEFT -> R.id.arrow_bottom_left
- ArrowPosition.CENTER -> R.id.arrow_bottom_center
- ArrowPosition.RIGHT -> R.id.arrow_bottom_right
- else -> null
- } else when (position) {
- ArrowPosition.LEFT -> R.id.arrow_top_left
- ArrowPosition.CENTER -> R.id.arrow_top_center
- ArrowPosition.RIGHT -> R.id.arrow_top_right
- else -> null
- }
-
- ids.forEach { id ->
- val showIt = id == toShow
- findViewById<View>(id)?.let {
- if (it.isVisible != showIt) {
- it.isVisible = showIt
- }
- }
- }
- }
-
- fun setLabel(period: String, blocked: Int, leaked: Int) {
- val span = SpannableStringBuilder(period)
- span.append(": $blocked ")
- span.setSpan(
- ImageSpan(context, R.drawable.ic_legend_blocked, DynamicDrawableSpan.ALIGN_BASELINE),
- span.length - 1,
- span.length,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- span.append(" $leaked ")
- span.setSpan(
- ImageSpan(context, R.drawable.ic_legend_leaked, DynamicDrawableSpan.ALIGN_BASELINE),
- span.length - 1,
- span.length,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- findViewById<TextView>(R.id.label).text = span.toSpannable()
- }
-
- override fun refreshContent(e: Entry?, highlight: Highlight?) {
- highlight?.let {
- showArrow(getArrowPosition(highlight.xPx))
- }
- super.refreshContent(e, highlight)
- }
-
- override fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF {
- val x = when (getArrowPosition(posX)) {
- ArrowPosition.LEFT -> -arrowMargins
- ArrowPosition.RIGHT -> -width + arrowMargins
- ArrowPosition.CENTER -> -width.toFloat() / 2
- }
-
- mOffset2.x = x
- mOffset2.y = if (isMarkerAbove) -posY
- else -posY + (chartView?.height?.toFloat() ?: 0f) - height
-
- return mOffset2
- }
-
- override fun draw(canvas: Canvas?, posX: Float, posY: Float) {
- super.draw(canvas, posX, posY)
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt
deleted file mode 100644
index 6955405..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 androidx.annotation.LayoutRes
-import com.google.android.material.appbar.MaterialToolbar
-
-abstract class NavToolbarFragment(@LayoutRes contentLayoutId: Int) : ToolbarFragment(contentLayoutId) {
-
- override fun setupToolbar(toolbar: MaterialToolbar) {
- super.setupToolbar(toolbar)
- toolbar.apply {
- setNavigationOnClickListener {
- requireActivity().onBackPressed()
- }
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/RightRadioButton.kt b/app/src/main/java/foundation/e/privacycentralapp/common/RightRadioButton.kt
deleted file mode 100644
index bbc108b..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/RightRadioButton.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.content.Context
-import android.util.AttributeSet
-import android.widget.RadioButton
-
-/**
- * A custom [RadioButton] which displays the radio drawable on the right side.
- */
-@SuppressLint("AppCompatCustomView")
-class RightRadioButton : RadioButton {
-
- constructor(context: Context) : super(context)
- constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
- constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
- context,
- attrs,
- defStyleAttr
- )
-
- // Returns layout direction as right-to-left to draw the compound button on right side.
- override fun getLayoutDirection(): Int {
- return LAYOUT_DIRECTION_RTL
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/TextViewHelpers.kt b/app/src/main/java/foundation/e/privacycentralapp/common/TextViewHelpers.kt
deleted file mode 100644
index d85f4a7..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/TextViewHelpers.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.privacycentralapp.common
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.text.Spannable
-import android.text.SpannableString
-import android.text.style.DynamicDrawableSpan
-import android.text.style.ImageSpan
-import android.widget.TextView
-import androidx.annotation.StringRes
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.appcompat.widget.TooltipCompat
-import foundation.e.privacycentralapp.R
-
-fun setToolTipForAsterisk(
- textView: TextView,
- @StringRes textId: Int,
- @StringRes tooltipTextId: Int
-) {
- textView.text = asteriskAsInfoIconSpannable(textView.context, textId, textView.textColors)
- TooltipCompat.setTooltipText(textView, textView.context.getString(tooltipTextId))
-
- textView.setOnClickListener { it.performLongClick() }
-}
-
-private fun asteriskAsInfoIconSpannable(
- context: Context,
- @StringRes textId: Int,
- tint: ColorStateList
-): Spannable {
- val spannable = SpannableString(context.getString(textId))
- val index = spannable.lastIndexOf("*")
- if (index != -1) {
- AppCompatResources.getDrawable(context, R.drawable.ic_info_16dp)?.let {
- it.setTintList(tint)
- it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
- spannable.setSpan(
- ImageSpan(it, DynamicDrawableSpan.ALIGN_CENTER),
- index,
- index + 1,
- Spannable.SPAN_INCLUSIVE_INCLUSIVE
- )
- }
- }
- return spannable
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/ThrottleFlow.kt b/app/src/main/java/foundation/e/privacycentralapp/common/ThrottleFlow.kt
deleted file mode 100644
index 21e1542..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/ThrottleFlow.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.privacycentralapp.common
-
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
-import kotlin.time.Duration
-
-@FlowPreview
-fun <T> Flow<T>.throttleFirst(windowDuration: Duration): Flow<T> = flow {
- var lastEmissionTime = 0L
- collect { upstream ->
- val currentTime = System.currentTimeMillis()
- val mayEmit = currentTime - lastEmissionTime > windowDuration.inWholeMilliseconds
- if (mayEmit) {
- lastEmissionTime = currentTime
- emit(upstream)
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
deleted file mode 100644
index c41c0cf..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.CheckBox
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import foundation.e.privacycentralapp.R
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-
-class ToggleAppsAdapter(
- private val itemsLayout: Int,
- private val listener: (String) -> Unit
-) :
- RecyclerView.Adapter<ToggleAppsAdapter.ViewHolder>() {
-
- class ViewHolder(view: View, private val listener: (String) -> Unit) : RecyclerView.ViewHolder(view) {
- val appName: TextView = view.findViewById(R.id.title)
-
- val togglePermission: CheckBox = view.findViewById(R.id.toggle)
-
- fun bind(item: Pair<ApplicationDescription, Boolean>, isEnabled: Boolean) {
- appName.text = item.first.label
- togglePermission.isChecked = item.second
- togglePermission.isEnabled = isEnabled
-
- itemView.findViewById<ImageView>(R.id.icon).setImageDrawable(item.first.icon)
- togglePermission.setOnClickListener { listener(item.first.packageName) }
- }
- }
-
- var dataSet: List<Pair<ApplicationDescription, Boolean>> = emptyList()
- set(value) {
- field = value
- notifyDataSetChanged()
- }
-
- var isEnabled: Boolean = true
-
- fun setData(list: List<Pair<ApplicationDescription, Boolean>>, isEnabled: Boolean = true) {
- this.isEnabled = isEnabled
- dataSet = list
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(itemsLayout, parent, false)
- return ViewHolder(view, listener)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val permission = dataSet[position]
- holder.bind(permission, isEnabled)
- }
-
- override fun getItemCount(): Int = dataSet.size
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/ToolbarFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/common/ToolbarFragment.kt
deleted file mode 100644
index 5c18548..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/ToolbarFragment.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.os.Bundle
-import android.view.View
-import androidx.annotation.LayoutRes
-import androidx.fragment.app.Fragment
-import com.google.android.material.appbar.MaterialToolbar
-import foundation.e.privacycentralapp.R
-
-abstract class ToolbarFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId) {
-
- /**
- * @return title to be used in toolbar
- */
- abstract fun getTitle(): String
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- setupToolbar(view.findViewById(R.id.toolbar))
- }
-
- open fun setupToolbar(toolbar: MaterialToolbar) {
- toolbar.title = getTitle()
- }
-
- fun getToolbar(): MaterialToolbar? = view?.findViewById(R.id.toolbar)
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/WarningDialog.kt b/app/src/main/java/foundation/e/privacycentralapp/common/WarningDialog.kt
deleted file mode 100644
index cbbeffa..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/WarningDialog.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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.privacycentralapp.common
-
-import android.app.Activity
-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.appcompat.app.AlertDialog
-import foundation.e.privacycentralapp.PrivacyCentralApplication
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.domain.entities.MainFeatures
-import foundation.e.privacycentralapp.domain.entities.MainFeatures.FAKE_LOCATION
-import foundation.e.privacycentralapp.domain.entities.MainFeatures.IP_SCRAMBLING
-import foundation.e.privacycentralapp.domain.entities.MainFeatures.TRACKERS_CONTROL
-import foundation.e.privacycentralapp.domain.usecases.ShowFeaturesWarningUseCase
-import foundation.e.privacycentralapp.main.MainActivity
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-
-class WarningDialog : Activity() {
- companion object {
- private const val PARAM_FEATURE = "feature"
-
- fun startListening(
- showFeaturesWarningUseCase: ShowFeaturesWarningUseCase,
- appScope: CoroutineScope,
- appContext: Context
- ) {
- showFeaturesWarningUseCase.showWarning().map { feature ->
- appContext.startActivity(
- createIntent(context = appContext, feature = feature)
- )
- }.launchIn(appScope)
- }
-
- private fun createIntent(
- context: Context,
- feature: MainFeatures,
- ): Intent {
- val intent = Intent(context, WarningDialog::class.java)
- intent.putExtra(PARAM_FEATURE, feature.name)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
- return intent
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- getWindow().setBackgroundDrawable(ColorDrawable(0))
-
- val feature = try {
- MainFeatures.valueOf(intent.getStringExtra(PARAM_FEATURE) ?: "")
- } catch (e: Exception) {
- Log.e("WarningDialog", "Missing mandatory activity parameter", e)
- finish()
- return
- }
-
- showWarningDialog(feature)
- }
-
- private fun showWarningDialog(feature: MainFeatures) {
- val builder = AlertDialog.Builder(this)
- builder.setOnDismissListener { finish() }
-
- val content: View = layoutInflater.inflate(R.layout.alertdialog_do_not_show_again, null)
- val checkbox = content.findViewById<CheckBox>(R.id.checkbox)
- builder.setView(content)
-
- 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
- }
- )
-
- 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
- }
- )
-
- builder.setPositiveButton(
- when (feature) {
- IP_SCRAMBLING -> R.string.warningdialog_ipscrambling_cta
- else -> R.string.ok
- }
- ) { _, _ ->
- if (checkbox.isChecked()) {
- (application as PrivacyCentralApplication)
- .dependencyContainer.showFeaturesWarningUseCase
- .doNotShowAgain(feature)
- }
- finish()
- }
-
- if (feature == TRACKERS_CONTROL) {
- builder.setNeutralButton(R.string.warningdialog_trackers_secondary_cta) { _, _ ->
- startActivity(MainActivity.createTrackersIntent(this))
- finish()
- }
- }
-
- builder.show()
- }
-}
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
deleted file mode 100644
index 71de99a..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/common/extensions/AnyExtension.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.privacycentralapp.common.extensions
-
-import android.content.Context
-
-fun Int.dpToPxF(context: Context): Float = this.toFloat() * context.resources.displayMetrics.density
diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
deleted file mode 100644
index a4f7487..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2022 E FOUNDATION, 2022 - 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.privacycentralapp.data.repositories
-
-import android.Manifest
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageInfo
-import foundation.e.privacycentralapp.R
-import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import foundation.e.privacymodules.permissions.data.ProfileType
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-
-class AppListsRepository(
- private val permissionsModule: PermissionsPrivacyModule,
- private val context: Context,
- private val coroutineScope: CoroutineScope
-) {
- companion object {
- private const val PNAME_SETTINGS = "com.android.settings"
- private const val PNAME_PWAPLAYER = "foundation.e.pwaplayer"
- private const val PNAME_INTENT_VERIFICATION = "com.android.statementservice"
- private const val PNAME_MICROG_SERVICES_CORE = "com.google.android.gms"
-
- val compatibiltyPNames = setOf(
- PNAME_PWAPLAYER, PNAME_INTENT_VERIFICATION, PNAME_MICROG_SERVICES_CORE
- )
- }
-
- val dummySystemApp = ApplicationDescription(
- packageName = "foundation.e.dummysystemapp",
- uid = -1,
- label = context.getString(R.string.dummy_system_app_label),
- icon = context.getDrawable(R.drawable.ic_e_app_logo),
- profileId = -1,
- profileType = ProfileType.MAIN
- )
-
- val dummyCompatibilityApp = ApplicationDescription(
- packageName = "foundation.e.dummyappscompatibilityapp",
- uid = -2,
- label = context.getString(R.string.dummy_apps_compatibility_app_label),
- icon = context.getDrawable(R.drawable.ic_apps_compatibility_components),
- profileId = -1,
- profileType = ProfileType.MAIN
- )
-
- private suspend fun fetchAppDescriptions(fetchMissingIcons: Boolean = false) {
- val launcherPackageNames = context.packageManager.queryIntentActivities(
- Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) },
- 0
- ).mapNotNull { it.activityInfo?.packageName }
-
- val visibleAppsFilter = { packageInfo: PackageInfo ->
- hasInternetPermission(packageInfo) &&
- isStandardApp(packageInfo.applicationInfo, launcherPackageNames)
- }
-
- val hiddenAppsFilter = { packageInfo: PackageInfo ->
- hasInternetPermission(packageInfo) &&
- isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames)
- }
-
- val compatibilityAppsFilter = { packageInfo: PackageInfo ->
- packageInfo.packageName in compatibiltyPNames
- }
-
- val visibleApps = recycleIcons(
- newApps = permissionsModule.getApplications(visibleAppsFilter),
- fetchMissingIcons = fetchMissingIcons
- )
- val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter)
- val compatibilityApps = permissionsModule.getApplications(compatibilityAppsFilter)
-
- updateMaps(visibleApps + hiddenApps + compatibilityApps)
-
- allProfilesAppDescriptions.emit(
- Triple(
- visibleApps + dummySystemApp + dummyCompatibilityApp,
- hiddenApps,
- compatibilityApps
- )
- )
- }
-
- private fun recycleIcons(
- newApps: List<ApplicationDescription>,
- fetchMissingIcons: Boolean
- ): List<ApplicationDescription> {
- val oldVisibleApps = allProfilesAppDescriptions.value.first
- return newApps.map { app ->
- app.copy(
- icon = oldVisibleApps.find { app.apId == it.apId }?.icon
- ?: if (fetchMissingIcons) permissionsModule.getApplicationIcon(app) else null
- )
- }
- }
-
- private fun updateMaps(apps: List<ApplicationDescription>) {
- val byUid = mutableMapOf<Int, ApplicationDescription>()
- val byApId = mutableMapOf<String, ApplicationDescription>()
- apps.forEach { app ->
- byUid[app.uid]?.run { packageName > app.packageName } == true
- if (byUid[app.uid].let { it == null || it.packageName > app.packageName }) {
- byUid[app.uid] = app
- }
-
- byApId[app.apId] = app
- }
- appsByUid = byUid
- appsByAPId = byApId
- }
-
- private var lastFetchApps = 0
- private var refreshAppJob: Job? = null
- private fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? {
- if (refreshAppJob == null) {
- refreshAppJob = coroutineScope.launch(Dispatchers.IO) {
- if (force || context.packageManager.getChangedPackages(lastFetchApps) != null) {
- fetchAppDescriptions(fetchMissingIcons = fetchMissingIcons)
- if (fetchMissingIcons) {
- lastFetchApps = context.packageManager.getChangedPackages(lastFetchApps)
- ?.sequenceNumber ?: lastFetchApps
- }
-
- refreshAppJob = null
- }
- }
- }
-
- return refreshAppJob
- }
-
- fun mainProfileApps(): Flow<List<ApplicationDescription>> {
- refreshAppDescriptions()
- return allProfilesAppDescriptions.map {
- it.first.filter { app -> app.profileType == ProfileType.MAIN }
- .sortedBy { app -> app.label.toString().lowercase() }
- }
- }
-
- fun getMainProfileHiddenSystemApps(): List<ApplicationDescription> {
- return allProfilesAppDescriptions.value.second.filter { it.profileType == ProfileType.MAIN }
- }
-
- fun apps(): Flow<List<ApplicationDescription>> {
- refreshAppDescriptions()
- return allProfilesAppDescriptions.map {
- it.first.sortedBy { app -> app.label.toString().lowercase() }
- }
- }
-
- fun allApps(): Flow<List<ApplicationDescription>> {
- return allProfilesAppDescriptions.map {
- it.first + it.second + it.third
- }
- }
-
- private fun getHiddenSystemApps(): List<ApplicationDescription> {
- return allProfilesAppDescriptions.value.second
- }
-
- private fun getCompatibilityApps(): List<ApplicationDescription> {
- return allProfilesAppDescriptions.value.third
- }
-
- fun anyForHiddenApps(app: ApplicationDescription, test: (ApplicationDescription) -> Boolean): Boolean {
- return if (app == dummySystemApp) {
- getHiddenSystemApps().any { test(it) }
- } else if (app == dummyCompatibilityApp) {
- getCompatibilityApps().any { test(it) }
- } else test(app)
- }
-
- fun applyForHiddenApps(app: ApplicationDescription, action: (ApplicationDescription) -> Unit) {
- mapReduceForHiddenApps(app = app, map = action, reduce = {})
- }
-
- fun <T, R> mapReduceForHiddenApps(
- app: ApplicationDescription,
- map: (ApplicationDescription) -> T,
- reduce: (List<T>) -> R
- ): R {
- return if (app == dummySystemApp) {
- reduce(getHiddenSystemApps().map(map))
- } else if (app == dummyCompatibilityApp) {
- reduce(getCompatibilityApps().map(map))
- } else reduce(listOf(map(app)))
- }
-
- private var appsByUid = mapOf<Int, ApplicationDescription>()
- private var appsByAPId = mapOf<String, ApplicationDescription>()
-
- fun getApp(appUid: Int): ApplicationDescription? {
- return appsByUid[appUid] ?: run {
- runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() }
- appsByUid[appUid]
- }
- }
-
- fun getApp(apId: String): ApplicationDescription? {
- if (apId.isBlank()) return null
-
- return appsByAPId[apId] ?: run {
- runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() }
- appsByAPId[apId]
- }
- }
-
- private val allProfilesAppDescriptions = MutableStateFlow(
- Triple(
- emptyList<ApplicationDescription>(),
- emptyList<ApplicationDescription>(),
- emptyList<ApplicationDescription>()
- )
- )
-
- private fun hasInternetPermission(packageInfo: PackageInfo): Boolean {
- return packageInfo.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
- }
-
- @Suppress("ReturnCount")
- private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
- if (app.packageName == PNAME_SETTINGS) {
- return false
- } else if (app.packageName == PNAME_PWAPLAYER) {
- return true
- } else if (app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) {
- return true
- } else if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) {
- return true
- } else if (launcherApps.contains(app.packageName)) {
- return true
- }
- return false
- }
-
- private fun isStandardApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
- return when {
- app.packageName == PNAME_SETTINGS -> false
- app.packageName in compatibiltyPNames -> false
- app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) -> true
- !app.hasFlag(ApplicationInfo.FLAG_SYSTEM) -> true
- launcherApps.contains(app.packageName) -> true
- else -> false
- }
- }
-
- private fun isHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
- return when {
- app.packageName in compatibiltyPNames -> false
- else -> !isNotHiddenSystemApp(app, launcherApps)
- }
- }
-
- private fun ApplicationInfo.hasFlag(flag: Int) = (flags and flag) == 1
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/CityDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/CityDataSource.kt
deleted file mode 100644
index d6a6a19..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/CityDataSource.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.dummy
-
-object CityDataSource {
- private val BARCELONA = Pair(41.3851f, 2.1734f)
- private val BUDAPEST = Pair(47.4979f, 19.0402f)
- private val ABU_DHABI = Pair(24.4539f, 54.3773f)
- private val HYDERABAD = Pair(17.3850f, 78.4867f)
- private val QUEZON_CITY = Pair(14.6760f, 121.0437f)
- private val PARIS = Pair(48.8566f, 2.3522f)
- private val LONDON = Pair(51.5074f, 0.1278f)
- private val SHANGHAI = Pair(31.2304f, 121.4737f)
- private val MADRID = Pair(40.4168f, -3.7038f)
- private val LAHORE = Pair(31.5204f, 74.3587f)
- private val CHICAGO = Pair(41.8781f, -87.6298f)
-
- val citiesLocationsList = listOf(
- BARCELONA,
- BUDAPEST,
- ABU_DHABI,
- HYDERABAD,
- QUEZON_CITY,
- PARIS,
- LONDON,
- SHANGHAI,
- MADRID,
- LAHORE,
- CHICAGO
- )
-}
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
deleted file mode 100644
index ed97c94..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.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
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.update
-
-class LocalStateRepository(context: Context) {
- companion object {
- private const val SHARED_PREFS_FILE = "localState"
- private const val KEY_BLOCK_TRACKERS = "blockTrackers"
- private const val KEY_IP_SCRAMBLING = "ipScrambling"
- private const val KEY_FAKE_LOCATION = "fakeLocation"
- private const val KEY_FAKE_LATITUDE = "fakeLatitude"
- private const val KEY_FAKE_LONGITUDE = "fakeLongitude"
- private const val KEY_FIRST_BOOT = "firstBoot"
- private const val KEY_HIDE_WARNING_TRACKERS = "hide_warning_trackers"
- private const val KEY_HIDE_WARNING_LOCATION = "hide_warning_location"
- private const val KEY_HIDE_WARNING_IPSCRAMBLING = "hide_warning_ipscrambling"
- }
-
- private val sharedPref = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE)
-
- private val _blockTrackers = MutableStateFlow(sharedPref.getBoolean(KEY_BLOCK_TRACKERS, true))
- val blockTrackers = _blockTrackers.asStateFlow()
-
- fun setBlockTrackers(enabled: Boolean) {
- set(KEY_BLOCK_TRACKERS, enabled)
- _blockTrackers.update { enabled }
- }
-
- val areAllTrackersBlocked: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
- private val _fakeLocationEnabled = MutableStateFlow(sharedPref.getBoolean(KEY_FAKE_LOCATION, false))
-
- val fakeLocationEnabled = _fakeLocationEnabled.asStateFlow()
-
- fun setFakeLocationEnabled(enabled: Boolean) {
- set(KEY_FAKE_LOCATION, enabled)
- _fakeLocationEnabled.update { enabled }
- }
-
- var fakeLocation: Pair<Float, Float>
- get() = Pair(
- // Initial default value is Quezon City
- sharedPref.getFloat(KEY_FAKE_LATITUDE, 14.6760f),
- sharedPref.getFloat(KEY_FAKE_LONGITUDE, 121.0437f)
- )
-
- set(value) {
- sharedPref.edit()
- .putFloat(KEY_FAKE_LATITUDE, value.first)
- .putFloat(KEY_FAKE_LONGITUDE, value.second)
- .apply()
- }
-
- val locationMode: MutableStateFlow<LocationMode> = MutableStateFlow(LocationMode.REAL_LOCATION)
-
- private val _ipScramblingSetting = MutableStateFlow(sharedPref.getBoolean(KEY_IP_SCRAMBLING, false))
- val ipScramblingSetting = _ipScramblingSetting.asStateFlow()
-
- fun setIpScramblingSetting(enabled: Boolean) {
- set(KEY_IP_SCRAMBLING, enabled)
- _ipScramblingSetting.update { enabled }
- }
-
- val internetPrivacyMode: MutableStateFlow<InternetPrivacyMode> = MutableStateFlow(InternetPrivacyMode.REAL_IP)
-
- private val _otherVpnRunning = MutableSharedFlow<ApplicationDescription>()
- suspend fun emitOtherVpnRunning(appDesc: ApplicationDescription) {
- _otherVpnRunning.emit(appDesc)
- }
- val otherVpnRunning: SharedFlow<ApplicationDescription> = _otherVpnRunning
-
- var firstBoot: Boolean
- get() = sharedPref.getBoolean(KEY_FIRST_BOOT, true)
- set(value) = set(KEY_FIRST_BOOT, value)
-
- var hideWarningTrackers: Boolean
- get() = sharedPref.getBoolean(KEY_HIDE_WARNING_TRACKERS, false)
- set(value) = set(KEY_HIDE_WARNING_TRACKERS, value)
-
- var hideWarningLocation: Boolean
- get() = sharedPref.getBoolean(KEY_HIDE_WARNING_LOCATION, false)
- set(value) = set(KEY_HIDE_WARNING_LOCATION, value)
-
- var hideWarningIpScrambling: Boolean
- get() = sharedPref.getBoolean(KEY_HIDE_WARNING_IPSCRAMBLING, false)
- set(value) = set(KEY_HIDE_WARNING_IPSCRAMBLING, value)
-
- private fun set(key: String, value: Boolean) {
- sharedPref.edit().putBoolean(key, value).apply()
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt
deleted file mode 100644
index b5310e1..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.privacycentralapp.data.repositories
-
-import android.content.Context
-import android.util.Log
-import com.google.gson.Gson
-import foundation.e.privacymodules.trackers.api.Tracker
-import retrofit2.Retrofit
-import retrofit2.converter.scalars.ScalarsConverterFactory
-import retrofit2.http.GET
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileWriter
-import java.io.IOException
-import java.io.InputStreamReader
-import java.io.PrintWriter
-
-class TrackersRepository(private val context: Context) {
-
- private val eTrackerFileName = "e_trackers.json"
- private val eTrackerFile = File(context.filesDir.absolutePath, eTrackerFileName)
-
- var trackers: List<Tracker> = emptyList()
- private set
-
- init {
- initTrackersFile()
- }
-
- suspend fun update() {
- val api = ETrackersApi.build()
- saveData(eTrackerFile, api.trackers())
- initTrackersFile()
- }
-
- private fun initTrackersFile() {
- try {
- var inputStream = context.assets.open(eTrackerFileName)
- if (eTrackerFile.exists()) {
- inputStream = FileInputStream(eTrackerFile)
- }
- val reader = InputStreamReader(inputStream, "UTF-8")
- val trackerResponse =
- Gson().fromJson(reader, ETrackersApi.ETrackersResponse::class.java)
-
- trackers = mapper(trackerResponse)
-
- reader.close()
- inputStream.close()
- } catch (e: Exception) {
- Log.e("TrackersRepository", "While parsing trackers in assets", e)
- }
- }
-
- private fun mapper(response: ETrackersApi.ETrackersResponse): List<Tracker> {
- return response.trackers.mapNotNull {
- try {
- it.toTracker()
- } catch (e: Exception) {
- null
- }
- }
- }
-
- private fun ETrackersApi.ETrackersResponse.ETracker.toTracker(): Tracker {
- return Tracker(
- id = id!!,
- hostnames = hostnames!!.toSet(),
- label = name!!,
- exodusId = exodusId
- )
- }
-
- private fun saveData(file: File, data: String): Boolean {
- try {
- val fos = FileWriter(file, false)
- val ps = PrintWriter(fos)
- ps.apply {
- print(data)
- flush()
- close()
- }
- return true
- } catch (e: IOException) {
- e.printStackTrace()
- }
- return false
- }
-}
-
-interface ETrackersApi {
- companion object {
- fun build(): ETrackersApi {
- val retrofit = Retrofit.Builder()
- .baseUrl("https://gitlab.e.foundation/e/os/tracker-list/-/raw/main/")
- .addConverterFactory(ScalarsConverterFactory.create())
- .build()
- return retrofit.create(ETrackersApi::class.java)
- }
- }
-
- @GET("list/e_trackers.json")
- suspend fun trackers(): String
-
- data class ETrackersResponse(val trackers: List<ETracker>) {
- data class ETracker(
- val id: String?,
- val hostnames: List<String>?,
- val name: String?,
- val exodusId: String?
- )
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt
deleted file mode 100644
index afdd2d5..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.privacycentralapp.domain.entities
-
-import android.graphics.drawable.Drawable
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-
-data class AppWithCounts(
- val appDesc: ApplicationDescription,
- val packageName: String,
- val uid: Int,
- var label: CharSequence?,
- var icon: Drawable?,
- val isWhitelisted: Boolean = false,
- val trackersCount: Int = 0,
- val whiteListedTrackersCount: Int = 0,
- val blockedLeaks: Int = 0,
- val leaks: Int = 0,
-) {
- constructor(
- app: ApplicationDescription,
- isWhitelisted: Boolean,
- trackersCount: Int,
- whiteListedTrackersCount: Int,
- blockedLeaks: Int,
- leaks: Int,
- ) :
- this(
- appDesc = app,
- packageName = app.packageName,
- uid = app.uid,
- label = app.label,
- icon = app.icon,
- isWhitelisted = isWhitelisted,
- trackersCount = trackersCount,
- whiteListedTrackersCount = whiteListedTrackersCount,
- blockedLeaks = blockedLeaks,
- leaks = leaks
- )
-
- val blockedTrackersCount get() = if (isWhitelisted) 0
- else Math.max(trackersCount - whiteListedTrackersCount, 0)
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt
deleted file mode 100644
index f849d57..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.domain.entities
-
-enum class InternetPrivacyMode {
- REAL_IP,
- HIDE_IP,
- HIDE_IP_LOADING,
- REAL_IP_LOADING;
-
- val isChecked get() = this == HIDE_IP || this == HIDE_IP_LOADING
-
- val isLoading get() = this == HIDE_IP_LOADING || this == REAL_IP_LOADING
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt
deleted file mode 100644
index 35a77b3..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.domain.entities
-
-enum class LocationMode {
- REAL_LOCATION, RANDOM_LOCATION, SPECIFIC_LOCATION
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/MainFeatures.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/MainFeatures.kt
deleted file mode 100644
index 0e7f99c..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/MainFeatures.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.privacycentralapp.domain.entities
-
-enum class MainFeatures {
- TRACKERS_CONTROL, FAKE_LOCATION, IP_SCRAMBLING
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt
deleted file mode 100644
index 3257402..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.privacycentralapp.domain.entities
-
-enum class QuickPrivacyState {
- DISABLED, ENABLED, FULL_ENABLED;
-
- fun isEnabled(): Boolean = this != DISABLED
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackerMode.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackerMode.kt
deleted file mode 100644
index 9f057be..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackerMode.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.privacycentralapp.domain.entities
-
-enum class TrackerMode {
- DENIED, CUSTOM, VULNERABLE
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt
deleted file mode 100644
index 8ce55dd..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.privacycentralapp.domain.entities
-
-data class TrackersPeriodicStatistics(
- val callsBlockedNLeaked: List<Pair<Int, Int>>,
- val periods: List<String>,
- val trackersCount: Int,
- val graduations: List<String?>? = null
-)
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt
deleted file mode 100644
index dd62839..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.domain.usecases
-
-import foundation.e.privacycentralapp.data.repositories.AppListsRepository
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import kotlinx.coroutines.flow.Flow
-
-class AppListUseCase(
- private val appListsRepository: AppListsRepository
-) {
- val dummySystemApp = appListsRepository.dummySystemApp
- fun getApp(uid: Int): ApplicationDescription {
- return when (uid) {
- dummySystemApp.uid -> dummySystemApp
- appListsRepository.dummyCompatibilityApp.uid ->
- appListsRepository.dummyCompatibilityApp
- else -> appListsRepository.getApp(uid) ?: dummySystemApp
- }
- }
- fun getAppsUsingInternet(): Flow<List<ApplicationDescription>> {
- return appListsRepository.mainProfileApps()
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
deleted file mode 100644
index 0ff2edb..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.domain.usecases
-
-import android.app.AppOpsManager
-import android.content.Context
-import android.content.pm.PackageManager
-import android.location.Location
-import android.location.LocationListener
-import android.location.LocationManager
-import android.os.Bundle
-import android.util.Log
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-import foundation.e.privacycentralapp.domain.entities.LocationMode
-import foundation.e.privacycentralapp.dummy.CityDataSource
-import foundation.e.privacymodules.fakelocation.IFakeLocationModule
-import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
-import foundation.e.privacymodules.permissions.data.AppOpModes
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlin.random.Random
-
-class FakeLocationStateUseCase(
- private val fakeLocationModule: IFakeLocationModule,
- private val permissionsModule: PermissionsPrivacyModule,
- private val localStateRepository: LocalStateRepository,
- private val citiesRepository: CityDataSource,
- private val appDesc: ApplicationDescription,
- private val appContext: Context,
- coroutineScope: CoroutineScope
-) {
- companion object {
- private const val TAG = "FakeLocationStateUseCase"
- }
-
- private val _configuredLocationMode = MutableStateFlow<Triple<LocationMode, Float?, Float?>>(Triple(LocationMode.REAL_LOCATION, null, null))
- val configuredLocationMode: StateFlow<Triple<LocationMode, Float?, Float?>> = _configuredLocationMode
-
- init {
- coroutineScope.launch {
- localStateRepository.fakeLocationEnabled.collect {
- applySettings(it, localStateRepository.fakeLocation)
- }
- }
- }
-
- private val locationManager: LocationManager
- get() = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
-
- private fun hasAcquireLocationPermission(): Boolean {
- return (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) ||
- permissionsModule.toggleDangerousPermission(appDesc, android.Manifest.permission.ACCESS_FINE_LOCATION, true)
- }
-
- private fun applySettings(isEnabled: Boolean, fakeLocation: Pair<Float, Float>, isSpecificLocation: Boolean = false) {
- _configuredLocationMode.value = computeLocationMode(isEnabled, fakeLocation, isSpecificLocation)
-
- if (isEnabled && hasAcquireMockLocationPermission()) {
- fakeLocationModule.startFakeLocation()
- fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble())
- localStateRepository.locationMode.value = configuredLocationMode.value.first
- } else {
- fakeLocationModule.stopFakeLocation()
- localStateRepository.locationMode.value = LocationMode.REAL_LOCATION
- }
- }
-
- private fun hasAcquireMockLocationPermission(): Boolean {
- return (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED) ||
- permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED)
- }
-
- fun setSpecificLocation(latitude: Float, longitude: Float) {
- setFakeLocation(latitude to longitude, true)
- }
-
- fun setRandomLocation() {
- val randomIndex = Random.nextInt(citiesRepository.citiesLocationsList.size)
- val location = citiesRepository.citiesLocationsList[randomIndex]
-
- setFakeLocation(location)
- }
-
- private fun setFakeLocation(location: Pair<Float, Float>, isSpecificLocation: Boolean = false) {
- localStateRepository.fakeLocation = location
- localStateRepository.setFakeLocationEnabled(true)
- applySettings(true, location, isSpecificLocation)
- }
-
- fun stopFakeLocation() {
- localStateRepository.setFakeLocationEnabled(false)
- applySettings(false, localStateRepository.fakeLocation)
- }
-
- private fun computeLocationMode(
- isFakeLocationEnabled: Boolean,
- fakeLocation: Pair<Float, Float>,
- isSpecificLocation: Boolean = false,
- ): Triple<LocationMode, Float?, Float?> {
- return Triple(
- when {
- !isFakeLocationEnabled -> LocationMode.REAL_LOCATION
- (fakeLocation in citiesRepository.citiesLocationsList && !isSpecificLocation) ->
- LocationMode.RANDOM_LOCATION
- else -> LocationMode.SPECIFIC_LOCATION
- },
- fakeLocation.first,
- fakeLocation.second
- )
- }
-
- val currentLocation = MutableStateFlow<Location?>(null)
-
- private var localListener = object : LocationListener {
-
- override fun onLocationChanged(location: Location) {
- currentLocation.update { previous ->
- if ((previous?.time ?: 0) + 1800 < location.time ||
- (previous?.accuracy ?: Float.MAX_VALUE) > location.accuracy
- ) {
- location
- } else {
- previous
- }
- }
- }
-
- // Deprecated since API 29, never called.
- override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
-
- override fun onProviderEnabled(provider: String) {
- reset()
- }
-
- override fun onProviderDisabled(provider: String) {
- reset()
- }
-
- private fun reset() {
- stopListeningLocation()
- currentLocation.value = null
- startListeningLocation()
- }
- }
-
- fun startListeningLocation(): Boolean {
- return if (hasAcquireLocationPermission()) {
- requestLocationUpdates()
- true
- } else false
- }
-
- fun stopListeningLocation() {
- locationManager.removeUpdates(localListener)
- }
-
- private fun requestLocationUpdates() {
- val networkProvider = LocationManager.NETWORK_PROVIDER
- .takeIf { it in locationManager.allProviders }
- val gpsProvider = LocationManager.GPS_PROVIDER
- .takeIf { it in locationManager.allProviders }
-
- try {
- networkProvider?.let {
- locationManager.requestLocationUpdates(
- it,
- 1000L,
- 0f,
- localListener
- )
- }
- gpsProvider?.let {
- locationManager.requestLocationUpdates(
- it,
- 1000L,
- 0f,
- localListener
- )
- }
-
- networkProvider?.let { locationManager.getLastKnownLocation(it) }
- ?: gpsProvider?.let { locationManager.getLastKnownLocation(it) }
- ?.let {
- localListener.onLocationChanged(it)
- }
- } catch (se: SecurityException) {
- Log.e(TAG, "Missing permission", se)
- }
- }
-}
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
deleted file mode 100644
index e2c0e7f..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.domain.usecases
-
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-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.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-
-class GetQuickPrivacyStateUseCase(
- private val localStateRepository: LocalStateRepository
-) {
- val quickPrivacyState: Flow<QuickPrivacyState> = combine(
- localStateRepository.blockTrackers,
- localStateRepository.areAllTrackersBlocked,
- localStateRepository.locationMode,
- localStateRepository.internetPrivacyMode
- ) { isBlockTrackers, isAllTrackersBlocked, locationMode, internetPrivacyMode ->
- when {
- !isBlockTrackers &&
- locationMode == LocationMode.REAL_LOCATION &&
- internetPrivacyMode == InternetPrivacyMode.REAL_IP -> QuickPrivacyState.DISABLED
-
- isAllTrackersBlocked &&
- locationMode != LocationMode.REAL_LOCATION &&
- internetPrivacyMode in listOf(
- InternetPrivacyMode.HIDE_IP,
- InternetPrivacyMode.HIDE_IP_LOADING
- ) -> QuickPrivacyState.FULL_ENABLED
-
- else -> QuickPrivacyState.ENABLED
- }
- }
-
- val trackerMode: Flow<TrackerMode> = combine(
- localStateRepository.blockTrackers,
- localStateRepository.areAllTrackersBlocked
- ) { isBlockTrackers, isAllTrackersBlocked ->
- when {
- isBlockTrackers && isAllTrackersBlocked -> TrackerMode.DENIED
- isBlockTrackers && !isAllTrackersBlocked -> TrackerMode.CUSTOM
- else -> TrackerMode.VULNERABLE
- }
- }
-
- val isLocationHidden: Flow<Boolean> = localStateRepository.locationMode.map { locationMode ->
- locationMode != LocationMode.REAL_LOCATION
- }
-
- val locationMode: StateFlow<LocationMode> = localStateRepository.locationMode
-
- val ipScramblingMode: Flow<InternetPrivacyMode> = localStateRepository.internetPrivacyMode
-
- fun toggleTrackers() {
- localStateRepository.setBlockTrackers(!localStateRepository.blockTrackers.value)
- }
-
- fun toggleLocation() {
- localStateRepository.setFakeLocationEnabled(!localStateRepository.fakeLocationEnabled.value)
- }
-
- fun toggleIpScrambling() {
- localStateRepository.setIpScramblingSetting(!localStateRepository.ipScramblingSetting.value)
- }
-
- 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
deleted file mode 100644
index dcb417b..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION, 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.privacycentralapp.domain.usecases
-
-import foundation.e.privacycentralapp.data.repositories.AppListsRepository
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode.HIDE_IP
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode.HIDE_IP_LOADING
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode.REAL_IP
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode.REAL_IP_LOADING
-import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
-import foundation.e.privacymodules.permissions.IPermissionsPrivacyModule
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-
-class IpScramblingStateUseCase(
- private val ipScramblerModule: IIpScramblerModule,
- private val permissionsPrivacyModule: IPermissionsPrivacyModule,
- private val appDesc: ApplicationDescription,
- private val localStateRepository: LocalStateRepository,
- private val appListsRepository: AppListsRepository,
- private val coroutineScope: CoroutineScope
-) {
- val internetPrivacyMode: StateFlow<InternetPrivacyMode> = callbackFlow {
- val listener = object : IIpScramblerModule.Listener {
- override fun onStatusChanged(newStatus: IIpScramblerModule.Status) {
- trySend(map(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) }
- }.stateIn(
- scope = coroutineScope,
- started = SharingStarted.Eagerly,
- initialValue = REAL_IP
- )
-
- init {
- coroutineScope.launch(Dispatchers.Default) {
- localStateRepository.ipScramblingSetting.collect {
- applySettings(it)
- }
- }
-
- coroutineScope.launch {
- internetPrivacyMode.collect { localStateRepository.internetPrivacyMode.value = it }
- }
- }
-
- fun toggle(hideIp: Boolean) {
- localStateRepository.setIpScramblingSetting(enabled = hideIp)
- }
-
- private fun getHiddenPackageNames(): List<String> {
- return appListsRepository.getMainProfileHiddenSystemApps().map { it.packageName }
- }
-
- val bypassTorApps: Set<String> get() {
- var whitelist = ipScramblerModule.appList
- if (getHiddenPackageNames().any { it in whitelist }) {
- val mutable = whitelist.toMutableSet()
- mutable.removeAll(getHiddenPackageNames())
- mutable.add(appListsRepository.dummySystemApp.packageName)
- whitelist = mutable
- }
- if (AppListsRepository.compatibiltyPNames.any { it in whitelist }) {
- val mutable = whitelist.toMutableSet()
- mutable.removeAll(AppListsRepository.compatibiltyPNames)
- mutable.add(appListsRepository.dummyCompatibilityApp.packageName)
- whitelist = mutable
- }
- return whitelist
- }
-
- fun toggleBypassTor(packageName: String) {
- val visibleList = bypassTorApps.toMutableSet()
- val rawList = ipScramblerModule.appList.toMutableSet()
-
- if (visibleList.contains(packageName)) {
- if (packageName == appListsRepository.dummySystemApp.packageName) {
- rawList.removeAll(getHiddenPackageNames())
- } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) {
- rawList.removeAll(AppListsRepository.compatibiltyPNames)
- } else {
- rawList.remove(packageName)
- }
- } else {
- if (packageName == appListsRepository.dummySystemApp.packageName) {
- rawList.addAll(getHiddenPackageNames())
- } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) {
- rawList.addAll(AppListsRepository.compatibiltyPNames)
- } else {
- rawList.add(packageName)
- }
- }
- ipScramblerModule.appList = rawList
- }
-
- private fun applySettings(isIpScramblingEnabled: Boolean) {
- val currentMode = localStateRepository.internetPrivacyMode.value
- when {
- isIpScramblingEnabled && currentMode in setOf(REAL_IP, REAL_IP_LOADING) ->
- applyStartIpScrambling()
-
- !isIpScramblingEnabled && currentMode in setOf(HIDE_IP, 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)
- }
- }
-
- private fun map(status: IIpScramblerModule.Status): InternetPrivacyMode {
- return when (status) {
- IIpScramblerModule.Status.OFF -> REAL_IP
- IIpScramblerModule.Status.ON -> HIDE_IP
- IIpScramblerModule.Status.STARTING -> HIDE_IP_LOADING
- IIpScramblerModule.Status.STOPPING,
- IIpScramblerModule.Status.START_DISABLED -> REAL_IP_LOADING
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/ShowFeaturesWarningUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/ShowFeaturesWarningUseCase.kt
deleted file mode 100644
index e347b34..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/ShowFeaturesWarningUseCase.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.privacycentralapp.domain.usecases
-
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-import foundation.e.privacycentralapp.domain.entities.MainFeatures
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.drop
-import kotlinx.coroutines.flow.dropWhile
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-
-class ShowFeaturesWarningUseCase(
- private val localStateRepository: LocalStateRepository
-) {
-
- fun showWarning(): Flow<MainFeatures> {
- return merge(
- localStateRepository.blockTrackers.drop(1).dropWhile { !it }
- .filter { it && !localStateRepository.hideWarningTrackers }
- .map { MainFeatures.TRACKERS_CONTROL },
- 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 }
- )
- }
-
- fun doNotShowAgain(feature: MainFeatures) {
- when (feature) {
- MainFeatures.TRACKERS_CONTROL -> localStateRepository.hideWarningTrackers = true
- MainFeatures.FAKE_LOCATION -> localStateRepository.hideWarningLocation = true
- MainFeatures.IP_SCRAMBLING -> localStateRepository.hideWarningIpScrambling = true
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
deleted file mode 100644
index afb6d1e..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION, 2022 - 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.privacycentralapp.domain.usecases
-
-import foundation.e.privacycentralapp.data.repositories.AppListsRepository
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-import foundation.e.privacycentralapp.data.repositories.TrackersRepository
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import foundation.e.privacymodules.trackers.api.IBlockTrackersPrivacyModule
-import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule
-import foundation.e.privacymodules.trackers.api.Tracker
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-class TrackersStateUseCase(
- private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule,
- private val trackersPrivacyModule: ITrackTrackersPrivacyModule,
- private val localStateRepository: LocalStateRepository,
- private val trackersRepository: TrackersRepository,
- private val appListsRepository: AppListsRepository,
- private val coroutineScope: CoroutineScope
-) {
- init {
- trackersPrivacyModule.start(
- trackers = trackersRepository.trackers,
- getAppByAPId = appListsRepository::getApp,
- getAppByUid = appListsRepository::getApp,
- enableNotification = false
- )
- coroutineScope.launch {
- localStateRepository.blockTrackers.collect { enabled ->
- if (enabled) {
- blockTrackersPrivacyModule.enableBlocking()
- } else {
- blockTrackersPrivacyModule.disableBlocking()
- }
- updateAllTrackersBlockedState()
- }
- }
- }
-
- private fun updateAllTrackersBlockedState() {
- localStateRepository.areAllTrackersBlocked.value = blockTrackersPrivacyModule.isBlockingEnabled() &&
- blockTrackersPrivacyModule.isWhiteListEmpty()
- }
-
- fun isWhitelisted(app: ApplicationDescription): Boolean {
- return isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule)
- }
-
- fun toggleAppWhitelist(app: ApplicationDescription, isWhitelisted: Boolean) {
- appListsRepository.applyForHiddenApps(app) {
- blockTrackersPrivacyModule.setWhiteListed(it, isWhitelisted)
- }
- updateAllTrackersBlockedState()
- }
-
- fun blockTracker(app: ApplicationDescription, tracker: Tracker, isBlocked: Boolean) {
- appListsRepository.applyForHiddenApps(app) {
- blockTrackersPrivacyModule.setWhiteListed(tracker, it, !isBlocked)
- }
- updateAllTrackersBlockedState()
- }
-
- fun clearWhitelist(app: ApplicationDescription) {
- appListsRepository.applyForHiddenApps(
- app,
- blockTrackersPrivacyModule::clearWhiteList
- )
- updateAllTrackersBlockedState()
- }
-
- fun updateTrackers() = coroutineScope.launch {
- trackersRepository.update()
- trackersPrivacyModule.start(
- trackers = trackersRepository.trackers,
- getAppByAPId = appListsRepository::getApp,
- getAppByUid = appListsRepository::getApp,
- enableNotification = false
- )
- }
-}
-
-fun isWhitelisted(
- app: ApplicationDescription,
- appListsRepository: AppListsRepository,
- blockTrackersPrivacyModule: IBlockTrackersPrivacyModule
-): Boolean {
- return appListsRepository.anyForHiddenApps(app, blockTrackersPrivacyModule::isWhitelisted)
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
deleted file mode 100644
index 5ca7039..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION, 2022 - 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.privacycentralapp.domain.usecases
-
-import android.content.res.Resources
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.common.throttleFirst
-import foundation.e.privacycentralapp.data.repositories.AppListsRepository
-import foundation.e.privacycentralapp.domain.entities.AppWithCounts
-import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import foundation.e.privacymodules.trackers.api.IBlockTrackersPrivacyModule
-import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule
-import foundation.e.privacymodules.trackers.api.Tracker
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-import java.time.temporal.ChronoUnit
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.seconds
-
-class TrackersStatisticsUseCase(
- private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule,
- private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule,
- private val appListsRepository: AppListsRepository,
- private val resources: Resources
-) {
- fun initAppList() {
- appListsRepository.apps()
- }
-
- private fun rawUpdates(): Flow<Unit> = callbackFlow {
- val listener = object : ITrackTrackersPrivacyModule.Listener {
- override fun onNewData() {
- trySend(Unit)
- }
- }
- trackTrackersPrivacyModule.addListener(listener)
- awaitClose { trackTrackersPrivacyModule.removeListener(listener) }
- }
-
- @OptIn(FlowPreview::class)
- fun listenUpdates(debounce: Duration = 1.seconds) = rawUpdates()
- .throttleFirst(windowDuration = debounce)
- .onStart { emit(Unit) }
-
- fun getDayStatistics(): Pair<TrackersPeriodicStatistics, Int> {
- return TrackersPeriodicStatistics(
- callsBlockedNLeaked = trackTrackersPrivacyModule.getPastDayTrackersCalls(),
- periods = buildDayLabels(),
- trackersCount = trackTrackersPrivacyModule.getPastDayTrackersCount(),
- graduations = buildDayGraduations(),
- ) to trackTrackersPrivacyModule.getTrackersCount()
- }
-
- fun getNonBlockedTrackersCount(): Flow<Int> {
- return if (blockTrackersPrivacyModule.isBlockingEnabled())
- appListsRepository.allApps().map { apps ->
- val whiteListedTrackers = mutableSetOf<Tracker>()
- val whiteListedApps = blockTrackersPrivacyModule.getWhiteListedApp()
- apps.forEach { app ->
- if (app in whiteListedApps) {
- whiteListedTrackers.addAll(trackTrackersPrivacyModule.getTrackersForApp(app))
- } else {
- whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app))
- }
- }
- whiteListedTrackers.size
- }
- else flowOf(trackTrackersPrivacyModule.getTrackersCount())
- }
-
- fun getMostLeakedApp(): ApplicationDescription? {
- return trackTrackersPrivacyModule.getPastDayMostLeakedApp()
- }
-
- fun getDayTrackersCalls() = trackTrackersPrivacyModule.getPastDayTrackersCalls()
-
- fun getDayTrackersCount() = trackTrackersPrivacyModule.getPastDayTrackersCount()
-
- private fun buildDayGraduations(): List<String?> {
- val formatter = DateTimeFormatter.ofPattern(
- resources.getString(R.string.trackers_graph_hours_period_format)
- )
-
- val periods = mutableListOf<String?>()
- var end = ZonedDateTime.now()
- for (i in 1..24) {
- val start = end.truncatedTo(ChronoUnit.HOURS)
- periods.add(if (start.hour % 6 == 0) formatter.format(start) else null)
- end = start.minus(1, ChronoUnit.MINUTES)
- }
- return periods.reversed()
- }
-
- private fun buildDayLabels(): List<String> {
- val formatter = DateTimeFormatter.ofPattern(
- resources.getString(R.string.trackers_graph_hours_period_format)
- )
- val periods = mutableListOf<String>()
- var end = ZonedDateTime.now()
- for (i in 1..24) {
- val start = end.truncatedTo(ChronoUnit.HOURS)
- periods.add("${formatter.format(start)} - ${formatter.format(end)}")
- end = start.minus(1, ChronoUnit.MINUTES)
- }
- return periods.reversed()
- }
-
- private fun buildMonthLabels(): List<String> {
- val formater = DateTimeFormatter.ofPattern(
- resources.getString(R.string.trackers_graph_days_period_format)
- )
- val periods = mutableListOf<String>()
- var day = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
- for (i in 1..30) {
- periods.add(formater.format(day))
- day = day.minus(1, ChronoUnit.DAYS)
- }
- return periods.reversed()
- }
-
- private fun buildYearLabels(): List<String> {
- val formater = DateTimeFormatter.ofPattern(
- resources.getString(R.string.trackers_graph_months_period_format)
- )
- val periods = mutableListOf<String>()
- var month = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1)
- for (i in 1..12) {
- periods.add(formater.format(month))
- month = month.minus(1, ChronoUnit.MONTHS)
- }
- return periods.reversed()
- }
-
- fun getDayMonthYearStatistics(): Triple<TrackersPeriodicStatistics, TrackersPeriodicStatistics, TrackersPeriodicStatistics> {
- return with(trackTrackersPrivacyModule) {
- Triple(
- TrackersPeriodicStatistics(
- callsBlockedNLeaked = getPastDayTrackersCalls(),
- periods = buildDayLabels(),
- trackersCount = getPastDayTrackersCount()
- ),
- TrackersPeriodicStatistics(
- callsBlockedNLeaked = getPastMonthTrackersCalls(),
- periods = buildMonthLabels(),
- trackersCount = getPastMonthTrackersCount()
- ),
- TrackersPeriodicStatistics(
- callsBlockedNLeaked = getPastYearTrackersCalls(),
- periods = buildYearLabels(),
- trackersCount = getPastYearTrackersCount()
- )
- )
- }
- }
-
- fun getTrackersWithWhiteList(app: ApplicationDescription): List<Pair<Tracker, Boolean>> {
- return appListsRepository.mapReduceForHiddenApps(
- app = app,
- map = { appDesc: ApplicationDescription ->
- (
- trackTrackersPrivacyModule.getTrackersForApp(appDesc) to
- blockTrackersPrivacyModule.getWhiteList(appDesc)
- )
- },
- reduce = { lists ->
- lists.unzip().let { (trackerLists, whiteListedIdLists) ->
- val whiteListedIds = whiteListedIdLists.flatten().map { it.id }.toSet()
-
- trackerLists.flatten().distinctBy { it.id }.sortedBy { it.label.lowercase() }
- .map { tracker -> tracker to (tracker.id in whiteListedIds) }
- }
- }
- )
- }
-
- fun isWhiteListEmpty(app: ApplicationDescription): Boolean {
- return appListsRepository.mapReduceForHiddenApps(
- app = app,
- map = { appDesc: ApplicationDescription ->
- blockTrackersPrivacyModule.getWhiteList(appDesc).isEmpty()
- },
- reduce = { areEmpty -> areEmpty.all { it } }
- )
- }
-
- fun getCalls(app: ApplicationDescription): Pair<Int, Int> {
- return appListsRepository.mapReduceForHiddenApps(
- app = app,
- map = trackTrackersPrivacyModule::getPastDayTrackersCallsForApp,
- reduce = { zip ->
- zip.unzip().let { (blocked, leaked) ->
- blocked.sum() to leaked.sum()
- }
- }
- )
- }
-
- fun getAppsWithCounts(): Flow<List<AppWithCounts>> {
- val trackersCounts = trackTrackersPrivacyModule.getTrackersCountByApp()
- val hiddenAppsTrackersWithWhiteList =
- getTrackersWithWhiteList(appListsRepository.dummySystemApp)
- val acAppsTrackersWithWhiteList =
- getTrackersWithWhiteList(appListsRepository.dummyCompatibilityApp)
-
- return appListsRepository.apps()
- .map { apps ->
- val callsByApp = trackTrackersPrivacyModule.getPastDayTrackersCallsByApps()
- apps.map { app ->
- val calls = appListsRepository.mapReduceForHiddenApps(
- app = app,
- map = { callsByApp.getOrDefault(app, 0 to 0) },
- reduce = {
- it.unzip().let { (blocked, leaked) ->
- blocked.sum() to leaked.sum()
- }
- }
- )
-
- AppWithCounts(
- app = app,
- isWhitelisted = !blockTrackersPrivacyModule.isBlockingEnabled() ||
- isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule),
- trackersCount = when (app) {
- appListsRepository.dummySystemApp ->
- hiddenAppsTrackersWithWhiteList.size
- appListsRepository.dummyCompatibilityApp ->
- acAppsTrackersWithWhiteList.size
- else -> trackersCounts.getOrDefault(app, 0)
- },
- whiteListedTrackersCount = when (app) {
- appListsRepository.dummySystemApp ->
- hiddenAppsTrackersWithWhiteList.count { it.second }
- appListsRepository.dummyCompatibilityApp ->
- acAppsTrackersWithWhiteList.count { it.second }
- else ->
- blockTrackersPrivacyModule.getWhiteList(app).size
- },
- blockedLeaks = calls.first,
- leaks = calls.second
- )
- }
- .sortedWith(mostLeakedAppsComparator)
- }
- }
-
- private val mostLeakedAppsComparator: Comparator<AppWithCounts> = Comparator { o1, o2 ->
- val leaks = o2.leaks - o1.leaks
- if (leaks != 0) leaks else {
- val whitelisted = o2.whiteListedTrackersCount - o1.whiteListedTrackersCount
- if (whitelisted != 0) whitelisted else {
- o2.trackersCount - o1.trackersCount
- }
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt
deleted file mode 100644
index f70065c..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.privacycentralapp.domain.usecases
-
-import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
-import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule
-
-class UpdateWidgetUseCase(
- private val localStateRepository: LocalStateRepository,
- private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule,
-) {
- init {
- trackTrackersPrivacyModule.addListener(object : ITrackTrackersPrivacyModule.Listener {
- override fun onNewData() {
- }
- })
- }
-}
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
deleted file mode 100644
index 0dc24e8..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * 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.features.dashboard
-
-import android.content.Intent
-import android.os.Bundle
-import android.text.Html
-import android.text.Html.FROM_HTML_MODE_LEGACY
-import android.view.View
-import android.widget.Toast
-import androidx.core.content.ContextCompat.getColor
-import androidx.core.os.bundleOf
-import androidx.core.view.isVisible
-import androidx.fragment.app.commit
-import androidx.fragment.app.replace
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import foundation.e.privacycentralapp.DependencyContainer
-import foundation.e.privacycentralapp.PrivacyCentralApplication
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.common.GraphHolder
-import foundation.e.privacycentralapp.common.NavToolbarFragment
-import foundation.e.privacycentralapp.databinding.FragmentDashboardBinding
-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.privacycentralapp.features.dashboard.DashboardViewModel.Action
-import foundation.e.privacycentralapp.features.dashboard.DashboardViewModel.SingleEvent
-import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyFragment
-import foundation.e.privacycentralapp.features.location.FakeLocationFragment
-import foundation.e.privacycentralapp.features.trackers.TrackersFragment
-import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment
-import kotlinx.coroutines.launch
-
-class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) {
- companion object {
- private const val PARAM_HIGHLIGHT_INDEX = "PARAM_HIGHLIGHT_INDEX"
- fun buildArgs(highlightIndex: Int): Bundle = bundleOf(
- PARAM_HIGHLIGHT_INDEX to highlightIndex
- )
- }
-
- private val dependencyContainer: DependencyContainer by lazy {
- (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
- }
-
- private val viewModel: DashboardViewModel by viewModels {
- dependencyContainer.viewModelsFactory
- }
-
- private var graphHolder: GraphHolder? = null
-
- private var _binding: FragmentDashboardBinding? = null
- private val binding get() = _binding!!
-
- private var highlightIndexOnStart: Int? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- highlightIndexOnStart = arguments?.getInt(PARAM_HIGHLIGHT_INDEX, -1)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- _binding = FragmentDashboardBinding.bind(view)
-
- graphHolder = GraphHolder(binding.graph, requireContext())
-
- binding.leakingAppButton.setOnClickListener {
- viewModel.submitAction(Action.ShowMostLeakedApp)
- }
- binding.toggleTrackers.setOnClickListener {
- viewModel.submitAction(Action.ToggleTrackers)
- }
- binding.toggleLocation.setOnClickListener {
- viewModel.submitAction(Action.ToggleLocation)
- }
- binding.toggleIpscrambling.setOnClickListener {
- viewModel.submitAction(Action.ToggleIpScrambling)
- }
- binding.myLocation.container.setOnClickListener {
- viewModel.submitAction(Action.ShowFakeMyLocationAction)
- }
- binding.internetActivityPrivacy.container.setOnClickListener {
- viewModel.submitAction(Action.ShowInternetActivityPrivacyAction)
- }
- binding.appsPermissions.container.setOnClickListener {
- viewModel.submitAction(Action.ShowAppsPermissions)
- }
-
- binding.amITracked.container.setOnClickListener {
- viewModel.submitAction(Action.ShowTrackers)
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- render(viewModel.state.value)
- viewModel.state.collect(::render)
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.singleEvents.collect { event ->
- when (event) {
- is SingleEvent.NavigateToLocationSingleEvent -> {
- requireActivity().supportFragmentManager.commit {
- replace<FakeLocationFragment>(R.id.container)
- setReorderingAllowed(true)
- addToBackStack("dashboard")
- }
- }
- is SingleEvent.NavigateToInternetActivityPrivacySingleEvent -> {
- requireActivity().supportFragmentManager.commit {
- replace<InternetPrivacyFragment>(R.id.container)
- setReorderingAllowed(true)
- addToBackStack("dashboard")
- }
- }
- is SingleEvent.NavigateToPermissionsSingleEvent -> {
- val intent = Intent("android.intent.action.MANAGE_PERMISSIONS")
- requireActivity().startActivity(intent)
- }
- SingleEvent.NavigateToTrackersSingleEvent -> {
- requireActivity().supportFragmentManager.commit {
- replace<TrackersFragment>(R.id.container)
- setReorderingAllowed(true)
- addToBackStack("dashboard")
- }
- }
- is SingleEvent.NavigateToAppDetailsEvent -> {
- requireActivity().supportFragmentManager.commit {
- replace<AppTrackersFragment>(
- R.id.container,
- args = AppTrackersFragment.buildArgs(
- event.appDesc.label.toString(),
- event.appDesc.packageName,
- event.appDesc.uid
- )
- )
- setReorderingAllowed(true)
- addToBackStack("dashboard")
- }
- }
- is SingleEvent.ToastMessageSingleEvent ->
- Toast.makeText(
- requireContext(),
- getString(event.message, *event.args.toTypedArray()),
- Toast.LENGTH_LONG
- ).show()
- }
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.doOnStartedState()
- }
- }
- }
-
- override fun getTitle(): String {
- return getString(R.string.dashboard_title)
- }
-
- private fun render(state: DashboardState) {
- binding.stateLabel.text = getString(
- when (state.quickPrivacyState) {
- QuickPrivacyState.DISABLED -> R.string.dashboard_state_title_off
- QuickPrivacyState.FULL_ENABLED -> R.string.dashboard_state_title_on
- QuickPrivacyState.ENABLED -> R.string.dashboard_state_title_custom
- }
- )
-
- binding.stateIcon.setImageResource(
- if (state.quickPrivacyState.isEnabled()) R.drawable.ic_shield_on
- else R.drawable.ic_shield_off
- )
-
- binding.toggleTrackers.isChecked = state.trackerMode != TrackerMode.VULNERABLE
-
- binding.stateTrackers.text = getString(
- when (state.trackerMode) {
- TrackerMode.DENIED -> R.string.dashboard_state_trackers_on
- TrackerMode.VULNERABLE -> R.string.dashboard_state_trackers_off
- TrackerMode.CUSTOM -> R.string.dashboard_state_trackers_custom
- }
- )
- binding.stateTrackers.setTextColor(
- getColor(
- requireContext(),
- if (state.trackerMode == TrackerMode.VULNERABLE) R.color.red_off
- else R.color.green_valid
- )
- )
-
- binding.toggleLocation.isChecked = state.isLocationHidden
-
- binding.stateGeolocation.text = getString(
- if (state.isLocationHidden) R.string.dashboard_state_geolocation_on
- else R.string.dashboard_state_geolocation_off
- )
- binding.stateGeolocation.setTextColor(
- getColor(
- requireContext(),
- if (state.isLocationHidden) R.color.green_valid
- else R.color.red_off
- )
- )
-
- binding.toggleIpscrambling.isChecked = state.ipScramblingMode.isChecked
- val isLoading = state.ipScramblingMode.isLoading
-
- binding.stateIpAddress.text = getString(
- if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.string.dashboard_state_ipaddress_on
- else R.string.dashboard_state_ipaddress_off
- )
-
- binding.stateIpAddressLoader.visibility = if (isLoading) View.VISIBLE else View.GONE
- binding.stateIpAddress.visibility = if (!isLoading) View.VISIBLE else View.GONE
-
- binding.stateIpAddress.setTextColor(
- getColor(
- requireContext(),
- if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.color.green_valid
- else R.color.red_off
- )
- )
-
- if (state.dayStatistics?.all { it.first == 0 && it.second == 0 } == true) {
- binding.graph.visibility = View.INVISIBLE
- binding.graphLegend.isVisible = false
- binding.leakingAppButton.isVisible = false
- binding.graphEmpty.isVisible = true
- } else {
- binding.graph.isVisible = true
- binding.graphLegend.isVisible = true
- binding.leakingAppButton.isVisible = true
- binding.graphEmpty.isVisible = false
- state.dayStatistics?.let { graphHolder?.data = it }
- state.dayLabels?.let { graphHolder?.labels = it }
- state.dayGraduations?.let { graphHolder?.graduations = it }
-
- binding.graphLegend.text = Html.fromHtml(
- getString(
- R.string.dashboard_graph_trackers_legend,
- state.leakedTrackersCount?.toString() ?: "No"
- ),
- FROM_HTML_MODE_LEGACY
- )
-
- highlightIndexOnStart?.let {
- binding.graph.post {
- graphHolder?.highlightIndex(it)
- }
- highlightIndexOnStart = null
- }
- }
-
- if (state.allowedTrackersCount != null && state.trackersCount != null) {
- binding.amITracked.subTitle = getString(R.string.dashboard_am_i_tracked_subtitle, state.trackersCount, state.allowedTrackersCount)
- } else {
- binding.amITracked.subTitle = ""
- }
-
- binding.myLocation.subTitle = getString(
- when (state.locationMode) {
- LocationMode.REAL_LOCATION -> R.string.dashboard_location_subtitle_off
- LocationMode.SPECIFIC_LOCATION -> R.string.dashboard_location_subtitle_specific
- LocationMode.RANDOM_LOCATION -> R.string.dashboard_location_subtitle_random
- }
- )
-
- binding.internetActivityPrivacy.subTitle = getString(
- if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.string.dashboard_internet_activity_privacy_subtitle_on
- else R.string.dashboard_internet_activity_privacy_subtitle_off
- )
-
- binding.executePendingBindings()
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- graphHolder = null
- _binding = null
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt
deleted file mode 100644
index 0e3521d..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.privacycentralapp.features.dashboard
-
-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
-
-data class DashboardState(
- val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED,
- val trackerMode: TrackerMode = TrackerMode.VULNERABLE,
- val isLocationHidden: Boolean = false,
- val ipScramblingMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP_LOADING,
- val locationMode: LocationMode = LocationMode.REAL_LOCATION,
- val leakedTrackersCount: Int? = null,
- val trackersCount: Int? = null,
- val allowedTrackersCount: Int? = null,
- val dayStatistics: List<Pair<Int, Int>>? = null,
- val dayLabels: List<String>? = null,
- val dayGraduations: List<String?>? = null,
-)
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
deleted file mode 100644
index f3a9774..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
-* 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
- * 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.features.dashboard
-
-import androidx.annotation.StringRes
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class DashboardViewModel(
- private val getPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
-) : ViewModel() {
-
- private val _state = MutableStateFlow(DashboardState())
- val state = _state.asStateFlow()
-
- private val _singleEvents = MutableSharedFlow<SingleEvent>()
- val singleEvents = _singleEvents.asSharedFlow()
-
- init {
- viewModelScope.launch(Dispatchers.IO) { trackersStatisticsUseCase.initAppList() }
- }
-
- suspend fun doOnStartedState() = withContext(Dispatchers.IO) {
- merge(
- getPrivacyStateUseCase.quickPrivacyState.map {
- _state.update { s -> s.copy(quickPrivacyState = it) }
- },
- getPrivacyStateUseCase.ipScramblingMode.map {
- _state.update { s -> s.copy(ipScramblingMode = it) }
- },
- trackersStatisticsUseCase.listenUpdates().flatMapLatest {
- fetchStatistics()
- },
- getPrivacyStateUseCase.trackerMode.map {
- _state.update { s -> s.copy(trackerMode = it) }
- },
- getPrivacyStateUseCase.isLocationHidden.map {
- _state.update { s -> s.copy(isLocationHidden = it) }
- },
- getPrivacyStateUseCase.locationMode.map {
- _state.update { s -> s.copy(locationMode = it) }
- },
- getPrivacyStateUseCase.otherVpnRunning.map {
- _singleEvents.emit(
- SingleEvent.ToastMessageSingleEvent(
- R.string.ipscrambling_error_always_on_vpn_already_running,
- listOf(it.label ?: "")
- )
- )
- }
- ).collect {}
- }
-
- fun submitAction(action: Action) = viewModelScope.launch {
- when (action) {
- is Action.ToggleTrackers -> {
- getPrivacyStateUseCase.toggleTrackers()
- // Add delay here to prevent race condition with trackers state.
- delay(200)
- fetchStatistics().first()
- }
- is Action.ToggleLocation -> getPrivacyStateUseCase.toggleLocation()
- is Action.ToggleIpScrambling -> getPrivacyStateUseCase.toggleIpScrambling()
- is Action.ShowFakeMyLocationAction ->
- _singleEvents.emit(SingleEvent.NavigateToLocationSingleEvent)
- is Action.ShowAppsPermissions ->
- _singleEvents.emit(SingleEvent.NavigateToPermissionsSingleEvent)
- is Action.ShowInternetActivityPrivacyAction ->
- _singleEvents.emit(SingleEvent.NavigateToInternetActivityPrivacySingleEvent)
- is Action.ShowTrackers ->
- _singleEvents.emit(SingleEvent.NavigateToTrackersSingleEvent)
- is Action.ShowMostLeakedApp -> actionShowMostLeakedApp()
- }
- }
-
- private suspend fun fetchStatistics(): Flow<Unit> = withContext(Dispatchers.IO) {
- trackersStatisticsUseCase.getNonBlockedTrackersCount().map { nonBlockedTrackersCount ->
- trackersStatisticsUseCase.getDayStatistics().let { (dayStatistics, trackersCount) ->
- _state.update { s ->
- s.copy(
- dayStatistics = dayStatistics.callsBlockedNLeaked,
- dayLabels = dayStatistics.periods,
- dayGraduations = dayStatistics.graduations,
- leakedTrackersCount = dayStatistics.trackersCount,
- trackersCount = trackersCount,
- allowedTrackersCount = nonBlockedTrackersCount
- )
- }
- }
- }
- }
-
- private suspend fun actionShowMostLeakedApp() = withContext(Dispatchers.IO) {
- _singleEvents.emit(
- trackersStatisticsUseCase.getMostLeakedApp()?.let {
- SingleEvent.NavigateToAppDetailsEvent(appDesc = it)
- } ?: SingleEvent.NavigateToTrackersSingleEvent
- )
- }
-
- sealed class SingleEvent {
- object NavigateToTrackersSingleEvent : SingleEvent()
- object NavigateToInternetActivityPrivacySingleEvent : SingleEvent()
- object NavigateToLocationSingleEvent : SingleEvent()
- object NavigateToPermissionsSingleEvent : SingleEvent()
- data class NavigateToAppDetailsEvent(val appDesc: ApplicationDescription) : SingleEvent()
- data class ToastMessageSingleEvent(
- @StringRes val message: Int,
- val args: List<Any> = emptyList()
- ) : SingleEvent()
- }
-
- sealed class Action {
- object ToggleTrackers : Action()
- object ToggleLocation : Action()
- object ToggleIpScrambling : Action()
- object ShowFakeMyLocationAction : Action()
- object ShowInternetActivityPrivacyAction : Action()
- object ShowAppsPermissions : Action()
- object ShowTrackers : Action()
- object ShowMostLeakedApp : 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
deleted file mode 100644
index afef986..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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.features.internetprivacy
-
-import android.os.Bundle
-import android.view.View
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.Toast
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.LinearLayoutManager
-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.common.ToggleAppsAdapter
-import foundation.e.privacycentralapp.common.setToolTipForAsterisk
-import foundation.e.privacycentralapp.databinding.FragmentInternetActivityPolicyBinding
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
-import kotlinx.coroutines.launch
-import java.util.Locale
-
-class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_activity_policy) {
-
- private val dependencyContainer: DependencyContainer by lazy {
- (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
- }
-
- private val viewModel: InternetPrivacyViewModel by viewModels {
- dependencyContainer.viewModelsFactory
- }
-
- private var _binding: FragmentInternetActivityPolicyBinding? = null
- private val binding get() = _binding!!
-
- private fun displayToast(message: String) {
- Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT)
- .show()
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- _binding = FragmentInternetActivityPolicyBinding.bind(view)
-
- binding.apps.apply {
- layoutManager = LinearLayoutManager(requireContext())
- setHasFixedSize(true)
- adapter = ToggleAppsAdapter(R.layout.ipscrambling_item_app_toggle) { packageName ->
- viewModel.submitAction(
- InternetPrivacyViewModel.Action.ToggleAppIpScrambled(packageName)
- )
- }
- }
-
- binding.radioUseRealIp.radiobutton.setOnClickListener {
- viewModel.submitAction(InternetPrivacyViewModel.Action.UseRealIPAction)
- }
-
- binding.radioUseHiddenIp.radiobutton.setOnClickListener {
- viewModel.submitAction(InternetPrivacyViewModel.Action.UseHiddenIPAction)
- }
-
- setToolTipForAsterisk(
- textView = binding.ipscramblingSelectApps,
- textId = R.string.ipscrambling_select_app,
- tooltipTextId = R.string.ipscrambling_app_list_infos
- )
-
- binding.ipscramblingSelectLocation.apply {
- adapter = ArrayAdapter(
- requireContext(), android.R.layout.simple_spinner_item,
- viewModel.availablesLocationsIds.map {
- if (it == "") {
- getString(R.string.ipscrambling_any_location)
- } else {
- Locale("", it).displayCountry
- }
- }
- ).apply {
- setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
- }
-
- onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
- override fun onItemSelected(
- parentView: AdapterView<*>,
- selectedItemView: View?,
- position: Int,
- id: Long
- ) {
- viewModel.submitAction(
- InternetPrivacyViewModel.Action.SelectLocationAction(
- position
- )
- )
- }
-
- override fun onNothingSelected(parentView: AdapterView<*>?) {}
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- render(viewModel.state.value)
- viewModel.state.collect(::render)
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.singleEvents.collect { event ->
- when (event) {
- is InternetPrivacyViewModel.SingleEvent.ErrorEvent -> {
- displayToast(getString(event.errorResId, *event.args.toTypedArray()))
- }
- }
- }
- }
- }
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.doOnStartedState()
- }
- }
- }
-
- override fun getTitle(): String = getString(R.string.ipscrambling_title)
-
- private fun render(state: InternetPrivacyState) {
- binding.radioUseHiddenIp.radiobutton.apply {
- isChecked = state.mode in listOf(
- InternetPrivacyMode.HIDE_IP,
- InternetPrivacyMode.HIDE_IP_LOADING
- )
- isEnabled = state.mode != InternetPrivacyMode.HIDE_IP_LOADING
- }
- binding.radioUseRealIp.radiobutton.apply {
- isChecked =
- state.mode in listOf(
- InternetPrivacyMode.REAL_IP,
- InternetPrivacyMode.REAL_IP_LOADING
- )
- isEnabled = state.mode != InternetPrivacyMode.REAL_IP_LOADING
- }
-
- binding.ipscramblingSelectLocation.setSelection(state.selectedLocationPosition)
-
- // TODO: this should not be mandatory.
- binding.apps.post {
- (binding.apps.adapter as ToggleAppsAdapter?)?.setData(
- list = state.getApps(),
- isEnabled = state.mode == InternetPrivacyMode.HIDE_IP
- )
- }
-
- val viewIdsToHide = listOf(
- binding.ipscramblingLocationLabel,
- binding.selectLocationContainer,
- binding.ipscramblingSelectLocation,
- binding.ipscramblingSelectApps,
- binding.apps
- )
-
- when {
- state.mode in listOf(
- InternetPrivacyMode.HIDE_IP_LOADING,
- InternetPrivacyMode.REAL_IP_LOADING
- )
- || state.availableApps.isEmpty() -> {
- binding.loader.visibility = View.VISIBLE
- viewIdsToHide.forEach { it.visibility = View.GONE }
- }
- else -> {
- binding.loader.visibility = View.GONE
- viewIdsToHide.forEach { it.visibility = View.VISIBLE }
- }
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt
deleted file mode 100644
index 54b7e01..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.privacycentralapp.features.internetprivacy
-
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-
-data class InternetPrivacyState(
- val mode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP,
- val availableApps: List<ApplicationDescription> = emptyList(),
- val bypassTorApps: Collection<String> = emptyList(),
- val selectedLocation: String = "",
- val availableLocationIds: List<String> = emptyList(),
- val forceRedraw: Boolean = false,
-) {
- fun getApps(): List<Pair<ApplicationDescription, Boolean>> {
- return availableApps.map { it to (it.packageName !in bypassTorApps) }
- }
-
- val selectedLocationPosition get() = availableLocationIds.indexOf(selectedLocation)
-}
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
deleted file mode 100644
index bbd6239..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * 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.features.internetprivacy
-
-import androidx.annotation.StringRes
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
-import foundation.e.privacycentralapp.domain.usecases.AppListUseCase
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase
-import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class InternetPrivacyViewModel(
- private val ipScramblerModule: IIpScramblerModule,
- private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- private val ipScramblingStateUseCase: IpScramblingStateUseCase,
- private val appListUseCase: AppListUseCase
-) : ViewModel() {
- companion object {
- private const val WARNING_LOADING_LONG_DELAY = 5 * 1000L
- }
-
- private val _state = MutableStateFlow(InternetPrivacyState())
- val state = _state.asStateFlow()
-
- private val _singleEvents = MutableSharedFlow<SingleEvent>()
- val singleEvents = _singleEvents.asSharedFlow()
-
- val availablesLocationsIds = listOf("", *ipScramblerModule.getAvailablesLocations().sorted().toTypedArray())
-
- init {
- viewModelScope.launch(Dispatchers.IO) {
- _state.update {
- it.copy(
- mode = ipScramblingStateUseCase.internetPrivacyMode.value,
- availableLocationIds = availablesLocationsIds,
- selectedLocation = ipScramblerModule.exitCountry
- )
- }
- }
- }
-
- @OptIn(FlowPreview::class)
- suspend fun doOnStartedState() = withContext(Dispatchers.IO) {
- launch {
- merge(
- appListUseCase.getAppsUsingInternet().map { apps ->
- _state.update { s ->
- s.copy(
- availableApps = apps,
- bypassTorApps = ipScramblingStateUseCase.bypassTorApps
- )
- }
- },
- ipScramblingStateUseCase.internetPrivacyMode.map {
- _state.update { s -> s.copy(mode = it) }
- }
- ).collect {}
- }
-
- launch {
- ipScramblingStateUseCase.internetPrivacyMode
- .map { it == InternetPrivacyMode.HIDE_IP_LOADING }
- .debounce(WARNING_LOADING_LONG_DELAY)
- .collect {
- if (it) _singleEvents.emit(
- SingleEvent.ErrorEvent(R.string.ipscrambling_warning_starting_long)
- )
- }
- }
-
- launch {
- getQuickPrivacyStateUseCase.otherVpnRunning.collect {
- _singleEvents.emit(
- SingleEvent.ErrorEvent(
- R.string.ipscrambling_error_always_on_vpn_already_running,
- listOf(it.label ?: "")
- )
- )
- _state.update { it.copy(forceRedraw = !it.forceRedraw) }
- }
- }
- }
-
- fun submitAction(action: Action) = viewModelScope.launch {
- when (action) {
- is Action.UseRealIPAction -> actionUseRealIP()
- is Action.UseHiddenIPAction -> actionUseHiddenIP()
- is Action.ToggleAppIpScrambled -> actionToggleAppIpScrambled(action)
- is Action.SelectLocationAction -> actionSelectLocation(action)
- }
- }
-
- private fun actionUseRealIP() {
- ipScramblingStateUseCase.toggle(hideIp = false)
- }
-
- private fun actionUseHiddenIP() {
- ipScramblingStateUseCase.toggle(hideIp = true)
- }
-
- private suspend fun actionToggleAppIpScrambled(action: Action.ToggleAppIpScrambled) = withContext(Dispatchers.IO) {
- ipScramblingStateUseCase.toggleBypassTor(action.packageName)
- _state.update { it.copy(bypassTorApps = ipScramblingStateUseCase.bypassTorApps) }
- }
-
- private suspend fun actionSelectLocation(action: Action.SelectLocationAction) = withContext(Dispatchers.IO) {
- val locationId = _state.value.availableLocationIds[action.position]
- if (locationId != ipScramblerModule.exitCountry) {
- ipScramblerModule.exitCountry = locationId
- _state.update { it.copy(selectedLocation = locationId) }
- }
- }
-
- sealed class SingleEvent {
- data class ErrorEvent(
- @StringRes val errorResId: Int,
- val args: List<Any> = emptyList()
- ) : SingleEvent()
- }
-
- sealed class Action {
- object UseRealIPAction : Action()
- object UseHiddenIPAction : Action()
- data class ToggleAppIpScrambled(val packageName: String) : Action()
- data class SelectLocationAction(val position: Int) : Action()
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
deleted file mode 100644
index 9e3f854..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * 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.features.location
-
-import android.Manifest
-import android.annotation.SuppressLint
-import android.content.Context
-import android.location.Location
-import android.os.Bundle
-import android.text.Editable
-import android.view.View
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.NonNull
-import androidx.core.view.isVisible
-import androidx.core.widget.addTextChangedListener
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.google.android.material.textfield.TextInputEditText
-import com.google.android.material.textfield.TextInputLayout
-import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
-import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
-import com.mapbox.mapboxsdk.Mapbox
-import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
-import com.mapbox.mapboxsdk.geometry.LatLng
-import com.mapbox.mapboxsdk.location.LocationComponent
-import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions
-import com.mapbox.mapboxsdk.location.LocationUpdate
-import com.mapbox.mapboxsdk.location.modes.CameraMode
-import com.mapbox.mapboxsdk.location.modes.RenderMode
-import com.mapbox.mapboxsdk.maps.MapboxMap
-import com.mapbox.mapboxsdk.maps.Style
-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.databinding.FragmentFakeLocationBinding
-import foundation.e.privacycentralapp.domain.entities.LocationMode
-import foundation.e.privacycentralapp.features.location.FakeLocationViewModel.Action
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.ensureActive
-import kotlinx.coroutines.launch
-
-class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) {
-
- private var isFirstLaunch: Boolean = true
-
- private val dependencyContainer: DependencyContainer by lazy {
- (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
- }
-
- private val viewModel: FakeLocationViewModel by viewModels {
- dependencyContainer.viewModelsFactory
- }
-
- private var _binding: FragmentFakeLocationBinding? = null
- private val binding get() = _binding!!
-
- private var mapboxMap: MapboxMap? = null
- private var locationComponent: LocationComponent? = null
-
- private var inputJob: Job? = null
-
- private val locationPermissionRequest = registerForActivityResult(
- ActivityResultContracts.RequestMultiplePermissions()
- ) { permissions ->
- if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) ||
- permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)
- ) {
- viewModel.submitAction(Action.StartListeningLocation)
- } // TODO: else.
- }
-
- companion object {
- private const val DEBOUNCE_PERIOD = 1000L
- }
-
- override fun onAttach(context: Context) {
- super.onAttach(context)
- Mapbox.getInstance(requireContext(), getString(R.string.mapbox_key))
- }
-
- override fun getTitle(): String = getString(R.string.location_title)
-
- private fun displayToast(message: String) {
- Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT)
- .show()
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- _binding = FragmentFakeLocationBinding.bind(view)
-
- binding.mapView.setup(savedInstanceState) { mapboxMap ->
- this.mapboxMap = mapboxMap
- mapboxMap.uiSettings.isRotateGesturesEnabled = false
- mapboxMap.setStyle(Style.MAPBOX_STREETS) { style ->
- enableLocationPlugin(style)
-
- mapboxMap.addOnCameraMoveListener {
- if (binding.mapView.isEnabled) {
- mapboxMap.cameraPosition.target.let {
- viewModel.submitAction(
- Action.SetSpecificLocationAction(
- it.latitude.toFloat(),
- it.longitude.toFloat()
- )
- )
- }
- }
- }
- // Bind click listeners once map is ready.
- bindClickListeners()
-
- render(viewModel.state.value)
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.singleEvents.collect { event ->
- if (event is FakeLocationViewModel.SingleEvent.LocationUpdatedEvent) {
- updateLocation(event.location, event.mode)
- }
- }
- }
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- render(viewModel.state.value)
- viewModel.state.collect(::render)
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.singleEvents.collect { event ->
- when (event) {
- is FakeLocationViewModel.SingleEvent.ErrorEvent -> {
- displayToast(event.error)
- }
- is FakeLocationViewModel.SingleEvent.RequestLocationPermission -> {
- // TODO for standalone: rationale dialog
- locationPermissionRequest.launch(
- arrayOf(
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION
- )
- )
- }
- is FakeLocationViewModel.SingleEvent.LocationUpdatedEvent -> {
- // Nothing here, another collect linked to mapbox view.
- }
- }
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.doOnStartedState()
- }
- }
- }
-
- private fun getCoordinatesAfterTextChanged(
- inputLayout: TextInputLayout,
- editText: TextInputEditText,
- isLat: Boolean
- ) = { editable: Editable? ->
- inputJob?.cancel()
- if (editable != null && editable.isNotEmpty() && editText.isEnabled) {
- inputJob = lifecycleScope.launch {
- delay(DEBOUNCE_PERIOD)
- ensureActive()
- try {
- val value = editable.toString().toFloat()
- val maxValue = if (isLat) 90f else 180f
-
- if (value > maxValue || value < -maxValue) {
- throw NumberFormatException("value $value is out of bounds")
- }
- inputLayout.error = null
-
- inputLayout.setEndIconDrawable(R.drawable.ic_valid)
- inputLayout.endIconMode = END_ICON_CUSTOM
-
- // Here, value is valid, try to send the values
- try {
- val lat = binding.edittextLatitude.text.toString().toFloat()
- val lon = binding.edittextLongitude.text.toString().toFloat()
- if (lat <= 90f && lat >= -90f && lon <= 180f && lon >= -180f) {
- mapboxMap?.moveCamera(
- CameraUpdateFactory.newLatLng(
- LatLng(lat.toDouble(), lon.toDouble())
- )
- )
- }
- } catch (e: NumberFormatException) {
- }
- } catch (e: NumberFormatException) {
- inputLayout.endIconMode = END_ICON_NONE
- inputLayout.error = getString(R.string.location_input_error)
- }
- }
- }
- }
-
- @SuppressLint("ClickableViewAccessibility")
- private fun bindClickListeners() {
- binding.radioUseRealLocation.setOnClickListener {
- viewModel.submitAction(Action.UseRealLocationAction)
- }
- binding.radioUseRandomLocation.setOnClickListener {
- viewModel.submitAction(Action.UseRandomLocationAction)
- }
- binding.radioUseSpecificLocation.setOnClickListener {
- mapboxMap?.cameraPosition?.target?.let {
- viewModel.submitAction(
- Action.SetSpecificLocationAction(it.latitude.toFloat(), it.longitude.toFloat())
- )
- }
- }
- binding.edittextLatitude.addTextChangedListener(
- afterTextChanged = getCoordinatesAfterTextChanged(
- binding.textlayoutLatitude,
- binding.edittextLatitude,
- true
- )
- )
-
- binding.edittextLongitude.addTextChangedListener(
- afterTextChanged = getCoordinatesAfterTextChanged(
- binding.textlayoutLongitude,
- binding.edittextLongitude,
- false
- )
- )
- }
-
- @SuppressLint("MissingPermission")
- private fun render(state: FakeLocationState) {
- binding.radioUseRandomLocation.isChecked = state.mode == LocationMode.RANDOM_LOCATION
-
- binding.radioUseSpecificLocation.isChecked = state.mode == LocationMode.SPECIFIC_LOCATION
-
- binding.radioUseRealLocation.isChecked = state.mode == LocationMode.REAL_LOCATION
-
- binding.mapView.isEnabled = (state.mode == LocationMode.SPECIFIC_LOCATION)
-
- if (state.mode == LocationMode.REAL_LOCATION) {
- binding.centeredMarker.isVisible = false
- } else {
- binding.mapLoader.isVisible = false
- binding.mapOverlay.isVisible = state.mode != LocationMode.SPECIFIC_LOCATION
- binding.centeredMarker.isVisible = true
-
- mapboxMap?.moveCamera(
- CameraUpdateFactory.newLatLng(
- LatLng(
- state.specificLatitude?.toDouble() ?: 0.0,
- state.specificLongitude?.toDouble() ?: 0.0
- )
- )
- )
- }
-
- binding.textlayoutLatitude.isVisible = (state.mode == LocationMode.SPECIFIC_LOCATION)
- binding.textlayoutLongitude.isVisible = (state.mode == LocationMode.SPECIFIC_LOCATION)
-
- binding.edittextLatitude.setText(state.specificLatitude?.toString())
- binding.edittextLongitude.setText(state.specificLongitude?.toString())
- }
-
- @SuppressLint("MissingPermission")
- private fun updateLocation(lastLocation: Location?, mode: LocationMode) {
- lastLocation?.let { location ->
- locationComponent?.isLocationComponentEnabled = true
- val locationUpdate = LocationUpdate.Builder()
- .location(location)
- .animationDuration(100)
- .build()
- locationComponent?.forceLocationUpdate(locationUpdate)
-
- if (mode == LocationMode.REAL_LOCATION) {
- binding.mapLoader.isVisible = false
- binding.mapOverlay.isVisible = false
-
- val update = CameraUpdateFactory.newLatLng(
- LatLng(location.latitude, location.longitude)
- )
-
- if (isFirstLaunch) {
- mapboxMap?.moveCamera(update)
- isFirstLaunch = false
- } else {
- mapboxMap?.animateCamera(update)
- }
- }
- } ?: run {
- locationComponent?.isLocationComponentEnabled = false
- if (mode == LocationMode.REAL_LOCATION) {
- binding.mapLoader.isVisible = true
- binding.mapOverlay.isVisible = true
- }
- }
- }
-
- @SuppressLint("MissingPermission")
- private fun enableLocationPlugin(@NonNull loadedMapStyle: Style) {
- // Check if permissions are enabled and if not request
- locationComponent = mapboxMap?.locationComponent
- locationComponent?.activateLocationComponent(
- LocationComponentActivationOptions.builder(
- requireContext(), loadedMapStyle
- ).useDefaultLocationEngine(false).build()
- )
- locationComponent?.isLocationComponentEnabled = true
- locationComponent?.cameraMode = CameraMode.NONE
- locationComponent?.renderMode = RenderMode.NORMAL
- }
-
- override fun onStart() {
- super.onStart()
- binding.mapView.onStart()
- }
-
- override fun onResume() {
- super.onResume()
- viewModel.submitAction(Action.StartListeningLocation)
- binding.mapView.onResume()
- }
-
- override fun onPause() {
- super.onPause()
- viewModel.submitAction(Action.StopListeningLocation)
- binding.mapView.onPause()
- }
-
- override fun onStop() {
- super.onStop()
- binding.mapView.onStop()
- }
-
- override fun onLowMemory() {
- super.onLowMemory()
- binding.mapView.onLowMemory()
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- binding.mapView.onDestroy()
- mapboxMap = null
- locationComponent = null
- inputJob = null
- _binding = null
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt
deleted file mode 100644
index e71bfcc..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationMapView.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.features.location
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.os.Bundle
-import android.util.AttributeSet
-import android.view.MotionEvent
-import com.mapbox.mapboxsdk.maps.MapView
-import com.mapbox.mapboxsdk.maps.OnMapReadyCallback
-
-class FakeLocationMapView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
-) : MapView(context, attrs, defStyleAttr) {
-
- /**
- * Overrides onTouchEvent because this MapView is part of a scroll view
- * and we want this map view to consume all touch events originating on this view.
- */
- @SuppressLint("ClickableViewAccessibility")
- override fun onTouchEvent(event: MotionEvent?): Boolean {
- when (event?.action) {
- MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)
- MotionEvent.ACTION_UP -> parent.requestDisallowInterceptTouchEvent(false)
- }
- super.onTouchEvent(event)
- return true
- }
-}
-
-fun FakeLocationMapView.setup(savedInstanceState: Bundle?, callback: OnMapReadyCallback) =
- this.apply {
- onCreate(savedInstanceState)
- getMapAsync(callback)
- }
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt
deleted file mode 100644
index 50d7a14..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.privacycentralapp.features.location
-
-import android.location.Location
-import foundation.e.privacycentralapp.domain.entities.LocationMode
-
-data class FakeLocationState(
- val mode: LocationMode = LocationMode.REAL_LOCATION,
- val currentLocation: Location? = null,
- val specificLatitude: Float? = null,
- val specificLongitude: Float? = null,
- val forceRefresh: Boolean = false,
-)
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
deleted file mode 100644
index 1cdf9f4..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.features.location
-
-import android.location.Location
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import foundation.e.privacycentralapp.domain.entities.LocationMode
-import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import kotlin.time.Duration.Companion.milliseconds
-
-class FakeLocationViewModel(
- private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- private val fakeLocationStateUseCase: FakeLocationStateUseCase
-) : ViewModel() {
- companion object {
- private val SET_SPECIFIC_LOCATION_DELAY = 200.milliseconds
- }
-
- private val _state = MutableStateFlow(FakeLocationState())
- val state = _state.asStateFlow()
-
- private val _singleEvents = MutableSharedFlow<SingleEvent>()
- val singleEvents = _singleEvents.asSharedFlow()
-
- private val specificLocationInputFlow = MutableSharedFlow<Action.SetSpecificLocationAction>()
-
- @OptIn(FlowPreview::class)
- suspend fun doOnStartedState() = withContext(Dispatchers.Main) {
- launch {
- merge(
- fakeLocationStateUseCase.configuredLocationMode.map { (mode, lat, lon) ->
- _state.update { s ->
- s.copy(
- mode = mode,
- specificLatitude = lat,
- specificLongitude = lon
- )
- }
- },
- specificLocationInputFlow
- .debounce(SET_SPECIFIC_LOCATION_DELAY).map { action ->
- fakeLocationStateUseCase.setSpecificLocation(action.latitude, action.longitude)
- }
- ).collect {}
- }
-
- launch {
- fakeLocationStateUseCase.currentLocation.collect { location ->
- _singleEvents.emit(
- SingleEvent.LocationUpdatedEvent(
- mode = _state.value.mode,
- location = location
- )
- )
- }
- }
- }
-
- fun submitAction(action: Action) = viewModelScope.launch {
- when (action) {
- is Action.StartListeningLocation -> actionStartListeningLocation()
- is Action.StopListeningLocation -> fakeLocationStateUseCase.stopListeningLocation()
- is Action.SetSpecificLocationAction -> setSpecificLocation(action)
- is Action.UseRandomLocationAction -> fakeLocationStateUseCase.setRandomLocation()
- is Action.UseRealLocationAction ->
- fakeLocationStateUseCase.stopFakeLocation()
- }
- }
-
- private suspend fun actionStartListeningLocation() {
- val started = fakeLocationStateUseCase.startListeningLocation()
- if (!started) {
- _singleEvents.emit(SingleEvent.RequestLocationPermission)
- }
- }
-
- private suspend fun setSpecificLocation(action: Action.SetSpecificLocationAction) {
- specificLocationInputFlow.emit(action)
- }
-
- sealed class SingleEvent {
- data class LocationUpdatedEvent(val mode: LocationMode, val location: Location?) : SingleEvent()
- object RequestLocationPermission : SingleEvent()
- data class ErrorEvent(val error: String) : SingleEvent()
- }
-
- sealed class Action {
- object StartListeningLocation : Action()
- object StopListeningLocation : Action()
- object UseRealLocationAction : Action()
- object UseRandomLocationAction : Action()
- data class SetSpecificLocationAction(
- val latitude: Float,
- val longitude: Float
- ) : Action()
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt
deleted file mode 100644
index cb32c2c..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION, 2022 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.privacycentralapp.features.trackers
-
-import android.content.ActivityNotFoundException
-import android.content.Intent
-import android.os.Bundle
-import android.text.Spannable
-import android.text.SpannableString
-import android.text.method.LinkMovementMethod
-import android.text.style.ClickableSpan
-import android.text.style.ForegroundColorSpan
-import android.text.style.UnderlineSpan
-import android.view.View
-import android.widget.Toast
-import androidx.core.content.ContextCompat
-import androidx.core.view.isVisible
-import androidx.fragment.app.commit
-import androidx.fragment.app.replace
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.LinearLayoutManager
-import foundation.e.privacycentralapp.DependencyContainer
-import foundation.e.privacycentralapp.PrivacyCentralApplication
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.common.AppsAdapter
-import foundation.e.privacycentralapp.common.GraphHolder
-import foundation.e.privacycentralapp.common.NavToolbarFragment
-import foundation.e.privacycentralapp.common.setToolTipForAsterisk
-import foundation.e.privacycentralapp.databinding.FragmentTrackersBinding
-import foundation.e.privacycentralapp.databinding.TrackersItemGraphBinding
-import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics
-import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment
-import kotlinx.coroutines.launch
-
-class TrackersFragment :
- NavToolbarFragment(R.layout.fragment_trackers) {
-
- private val dependencyContainer: DependencyContainer by lazy {
- (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
- }
-
- private val viewModel: TrackersViewModel by viewModels { dependencyContainer.viewModelsFactory }
-
- private var _binding: FragmentTrackersBinding? = null
- private val binding get() = _binding!!
-
- private var dayGraphHolder: GraphHolder? = null
- private var monthGraphHolder: GraphHolder? = null
- private var yearGraphHolder: GraphHolder? = null
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- _binding = FragmentTrackersBinding.bind(view)
-
- dayGraphHolder = GraphHolder(binding.graphDay.graph, requireContext(), false)
- monthGraphHolder = GraphHolder(binding.graphMonth.graph, requireContext(), false)
- yearGraphHolder = GraphHolder(binding.graphYear.graph, requireContext(), false)
-
- binding.apps.apply {
- layoutManager = LinearLayoutManager(requireContext())
- setHasFixedSize(true)
- adapter = AppsAdapter(R.layout.trackers_item_app) { appUid ->
- viewModel.submitAction(
- TrackersViewModel.Action.ClickAppAction(appUid)
- )
- }
- }
-
- val infoText = getString(R.string.trackers_info)
- val moreText = getString(R.string.trackers_info_more)
-
- val spannable = SpannableString("$infoText $moreText")
- val startIndex = infoText.length + 1
- val endIndex = spannable.length
- spannable.setSpan(
- ForegroundColorSpan(ContextCompat.getColor(requireContext(), R.color.accent)),
- startIndex,
- endIndex,
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE
- )
- spannable.setSpan(UnderlineSpan(), startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
- spannable.setSpan(
- object : ClickableSpan() {
- override fun onClick(p0: View) {
- viewModel.submitAction(TrackersViewModel.Action.ClickLearnMore)
- }
- },
- startIndex, endIndex, Spannable.SPAN_INCLUSIVE_EXCLUSIVE
- )
-
- with(binding.trackersInfo) {
- linksClickable = true
- isClickable = true
- movementMethod = LinkMovementMethod.getInstance()
- text = spannable
- }
-
- setToolTipForAsterisk(
- textView = binding.trackersAppsListTitle,
- textId = R.string.trackers_applist_title,
- tooltipTextId = R.string.trackers_applist_infos
- )
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- render(viewModel.state.value)
- viewModel.state.collect(::render)
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.singleEvents.collect { event ->
- when (event) {
- is TrackersViewModel.SingleEvent.ErrorEvent -> {
- displayToast(event.error)
- }
- is TrackersViewModel.SingleEvent.OpenAppDetailsEvent -> {
- requireActivity().supportFragmentManager.commit {
- replace<AppTrackersFragment>(
- R.id.container,
- args = AppTrackersFragment.buildArgs(
- event.appDesc.label.toString(),
- event.appDesc.packageName,
- event.appDesc.uid
- )
- )
- setReorderingAllowed(true)
- addToBackStack("apptrackers")
- }
- }
- is TrackersViewModel.SingleEvent.OpenUrl -> {
- try {
- startActivity(Intent(Intent.ACTION_VIEW, event.url))
- } catch (e: ActivityNotFoundException) {
- Toast.makeText(
- requireContext(),
- R.string.error_no_activity_view_url,
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- }
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.doOnStartedState()
- }
- }
- }
-
- private fun displayToast(message: String) {
- Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT)
- .show()
- }
-
- override fun getTitle() = getString(R.string.trackers_title)
-
- private fun render(state: TrackersState) {
- state.dayStatistics?.let { renderGraph(it, dayGraphHolder!!, binding.graphDay) }
- state.monthStatistics?.let { renderGraph(it, monthGraphHolder!!, binding.graphMonth) }
- state.yearStatistics?.let { renderGraph(it, yearGraphHolder!!, binding.graphYear) }
-
- state.apps?.let {
- binding.apps.post {
- (binding.apps.adapter as AppsAdapter?)?.dataSet = it
- }
- }
- }
-
- private fun renderGraph(
- statistics: TrackersPeriodicStatistics,
- graphHolder: GraphHolder,
- graphBinding: TrackersItemGraphBinding
- ) {
- if (statistics.callsBlockedNLeaked.all { it.first == 0 && it.second == 0 }) {
- graphBinding.graph.visibility = View.INVISIBLE
- graphBinding.graphEmpty.isVisible = true
- } else {
- graphBinding.graph.isVisible = true
- graphBinding.graphEmpty.isVisible = false
- graphHolder.data = statistics.callsBlockedNLeaked
- graphHolder.labels = statistics.periods
- graphBinding.trackersCountLabel.text =
- getString(R.string.trackers_count_label, statistics.trackersCount)
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- dayGraphHolder = null
- monthGraphHolder = null
- yearGraphHolder = null
- _binding = null
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt
deleted file mode 100644
index a3bb80a..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.privacycentralapp.features.trackers
-
-import foundation.e.privacycentralapp.domain.entities.AppWithCounts
-import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics
-
-data class TrackersState(
- val dayStatistics: TrackersPeriodicStatistics? = null,
- val monthStatistics: TrackersPeriodicStatistics? = null,
- val yearStatistics: TrackersPeriodicStatistics? = null,
- val apps: List<AppWithCounts>? = null,
-)
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
deleted file mode 100644
index 8b5cc32..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION, 2022 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.privacycentralapp.features.trackers
-
-import android.net.Uri
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import foundation.e.privacycentralapp.domain.entities.AppWithCounts
-import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class TrackersViewModel(
- private val trackersStatisticsUseCase: TrackersStatisticsUseCase
-) : ViewModel() {
-
- companion object {
- private const val URL_LEARN_MORE_ABOUT_TRACKERS =
- "https://doc.e.foundation/support-topics/advanced_privacy#trackers-blocker"
- }
-
- private val _state = MutableStateFlow(TrackersState())
- val state = _state.asStateFlow()
-
- private val _singleEvents = MutableSharedFlow<SingleEvent>()
- val singleEvents = _singleEvents.asSharedFlow()
-
- suspend fun doOnStartedState() = withContext(Dispatchers.IO) {
- merge(
- trackersStatisticsUseCase.listenUpdates().map {
- trackersStatisticsUseCase.getDayMonthYearStatistics()
- .let { (day, month, year) ->
- _state.update { s ->
- s.copy(
- dayStatistics = day,
- monthStatistics = month,
- yearStatistics = year
- )
- }
- }
- },
- trackersStatisticsUseCase.getAppsWithCounts().map {
- _state.update { s -> s.copy(apps = it) }
- }
- ).collect {}
- }
-
- fun submitAction(action: Action) = viewModelScope.launch {
- when (action) {
- is Action.ClickAppAction -> actionClickApp(action)
- is Action.ClickLearnMore ->
- _singleEvents.emit(SingleEvent.OpenUrl(Uri.parse(URL_LEARN_MORE_ABOUT_TRACKERS)))
- }
- }
-
- private suspend fun actionClickApp(action: Action.ClickAppAction) {
- state.value.apps?.find { it.uid == action.appUid }?.let {
- _singleEvents.emit(SingleEvent.OpenAppDetailsEvent(it))
- }
- }
-
- sealed class SingleEvent {
- data class ErrorEvent(val error: String) : SingleEvent()
- data class OpenAppDetailsEvent(val appDesc: AppWithCounts) : SingleEvent()
- data class OpenUrl(val url: Uri) : SingleEvent()
- }
-
- sealed class Action {
- data class ClickAppAction(val appUid: Int) : Action()
- object ClickLearnMore : 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
deleted file mode 100644
index 888c140..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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
- * 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.features.trackers.apptrackers
-
-import android.content.ActivityNotFoundException
-import android.content.Intent
-import android.os.Bundle
-import android.view.View
-import android.widget.Toast
-import androidx.core.os.bundleOf
-import androidx.core.view.isVisible
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.LinearLayoutManager
-import com.google.android.material.snackbar.Snackbar
-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.databinding.ApptrackersFragmentBinding
-import kotlinx.coroutines.launch
-
-class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) {
- companion object {
- private val PARAM_LABEL = "PARAM_LABEL"
- private val PARAM_PACKAGE_NAME = "PARAM_PACKAGE_NAME"
-
- const val PARAM_APP_UID = "PARAM_APP_UID"
-
- fun buildArgs(label: String, packageName: String, appUid: Int): Bundle = bundleOf(
- PARAM_LABEL to label,
- PARAM_PACKAGE_NAME to packageName,
- PARAM_APP_UID to appUid
- )
- }
-
- private val dependencyContainer: DependencyContainer by lazy {
- (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
- }
-
- private val viewModel: AppTrackersViewModel by viewModels {
- dependencyContainer.viewModelsFactory
- }
-
- private var _binding: ApptrackersFragmentBinding? = null
- private val binding get() = _binding!!
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- if (arguments == null ||
- requireArguments().getInt(PARAM_APP_UID, Int.MIN_VALUE) == Int.MIN_VALUE
- ) {
- activity?.supportFragmentManager?.popBackStack()
- }
- }
-
- private fun displayToast(message: String) {
- Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT)
- .show()
- }
-
- override fun getTitle(): String = requireArguments().getString(PARAM_LABEL) ?: ""
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- _binding = ApptrackersFragmentBinding.bind(view)
-
- binding.blockAllToggle.setOnClickListener {
- viewModel.submitAction(AppTrackersViewModel.Action.BlockAllToggleAction(binding.blockAllToggle.isChecked))
- }
- binding.btnReset.setOnClickListener {
- viewModel.submitAction(AppTrackersViewModel.Action.ResetAllTrackers)
- }
-
- binding.trackers.apply {
- layoutManager = LinearLayoutManager(requireContext())
- setHasFixedSize(true)
- adapter = ToggleTrackersAdapter(
- R.layout.apptrackers_item_tracker_toggle,
- onToggleSwitch = { tracker, isBlocked ->
- viewModel.submitAction(AppTrackersViewModel.Action.ToggleTrackerAction(tracker, isBlocked))
- },
- onClickTitle = { viewModel.submitAction(AppTrackersViewModel.Action.ClickTracker(it)) },
- )
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.singleEvents.collect { event ->
- when (event) {
- is AppTrackersViewModel.SingleEvent.ErrorEvent ->
- displayToast(getString(event.errorResId))
- is AppTrackersViewModel.SingleEvent.OpenUrl ->
- try {
- startActivity(Intent(Intent.ACTION_VIEW, event.url))
- } catch (e: ActivityNotFoundException) {
- Toast.makeText(
- requireContext(),
- R.string.error_no_activity_view_url,
- Toast.LENGTH_SHORT
- ).show()
- }
- is AppTrackersViewModel.SingleEvent.ToastTrackersControlDisabled ->
- Snackbar.make(
- binding.root,
- R.string.apptrackers_tracker_control_disabled_message,
- Snackbar.LENGTH_LONG
- ).show()
- }
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.doOnStartedState()
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- render(viewModel.state.value)
- viewModel.state.collect(::render)
- }
- }
- }
-
- private fun render(state: AppTrackersState) {
- binding.trackersCountSummary.text = if (state.getTrackersCount() == 0) ""
- else getString(
- R.string.apptrackers_trackers_count_summary,
- state.getBlockedTrackersCount(),
- state.getTrackersCount(),
- state.blocked,
- state.leaked
- )
-
- binding.blockAllToggle.isChecked = state.isBlockingActivated
-
- val trackersStatus = state.getTrackersStatus()
- if (!trackersStatus.isNullOrEmpty()) {
- binding.trackersListTitle.isVisible = state.isBlockingActivated
- binding.trackers.isVisible = true
- binding.trackers.post {
- (binding.trackers.adapter as ToggleTrackersAdapter?)?.updateDataSet(
- trackersStatus,
- state.isBlockingActivated
- )
- }
- binding.noTrackersYet.isVisible = false
- binding.btnReset.isVisible = true
- } else {
- binding.trackersListTitle.isVisible = false
- binding.trackers.isVisible = false
- binding.noTrackersYet.isVisible = true
- binding.noTrackersYet.text = getString(
- when {
- !state.isBlockingActivated -> R.string.apptrackers_no_trackers_yet_block_off
- state.isWhitelistEmpty -> R.string.apptrackers_no_trackers_yet_block_on
- else -> R.string.app_trackers_no_trackers_yet_remaining_whitelist
- }
- )
- binding.btnReset.isVisible = state.isBlockingActivated && !state.isWhitelistEmpty
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt
deleted file mode 100644
index a190a74..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.privacycentralapp.features.trackers.apptrackers
-
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import foundation.e.privacymodules.trackers.api.Tracker
-
-data class AppTrackersState(
- val appDesc: ApplicationDescription? = null,
- val isBlockingActivated: Boolean = false,
- val trackersWithWhiteList: List<Pair<Tracker, Boolean>>? = null,
- val leaked: Int = 0,
- val blocked: Int = 0,
- val isTrackersBlockingEnabled: Boolean = false,
- val isWhitelistEmpty: Boolean = true,
- val showQuickPrivacyDisabledMessage: Boolean = false,
-) {
- fun getTrackersStatus(): List<Pair<Tracker, Boolean>>? {
- return trackersWithWhiteList?.map { it.first to !it.second }
- }
-
- fun getTrackersCount() = trackersWithWhiteList?.size ?: 0
- fun getBlockedTrackersCount(): Int = if (isTrackersBlockingEnabled && isBlockingActivated)
- trackersWithWhiteList?.count { !it.second } ?: 0
- else 0
-}
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
deleted file mode 100644
index e5a94f9..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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
- * 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.features.trackers.apptrackers
-
-import android.net.Uri
-import androidx.annotation.StringRes
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import foundation.e.privacycentralapp.domain.entities.TrackerMode
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import foundation.e.privacymodules.trackers.api.Tracker
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class AppTrackersViewModel(
- private val app: ApplicationDescription,
- private val trackersStateUseCase: TrackersStateUseCase,
- private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
- private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase
-) : ViewModel() {
- companion object {
- private const val exodusBaseUrl = "https://reports.exodus-privacy.eu.org/trackers/"
- }
-
- private val _state = MutableStateFlow(AppTrackersState())
- val state = _state.asStateFlow()
-
- private val _singleEvents = MutableSharedFlow<SingleEvent>()
- val singleEvents = _singleEvents.asSharedFlow()
-
- init {
- viewModelScope.launch(Dispatchers.IO) {
- _state.update {
- it.copy(
- appDesc = app,
- isBlockingActivated = !trackersStateUseCase.isWhitelisted(app),
- trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(
- app
- ),
- isWhitelistEmpty = trackersStatisticsUseCase.isWhiteListEmpty(app)
- )
- }
- }
- }
-
- suspend fun doOnStartedState() = withContext(Dispatchers.IO) {
- merge(
- getQuickPrivacyStateUseCase.trackerMode.map {
- _state.update { s -> s.copy(isTrackersBlockingEnabled = it != TrackerMode.VULNERABLE) }
- },
- trackersStatisticsUseCase.listenUpdates().map { fetchStatistics() }
- ).collect { }
- }
-
- fun submitAction(action: Action) = viewModelScope.launch {
- when (action) {
- is Action.BlockAllToggleAction -> blockAllToggleAction(action)
- is Action.ToggleTrackerAction -> toggleTrackerAction(action)
- is Action.ClickTracker -> actionClickTracker(action)
- is Action.ResetAllTrackers -> resetAllTrackers()
- }
- }
-
- private suspend fun blockAllToggleAction(action: Action.BlockAllToggleAction) {
- withContext(Dispatchers.IO) {
- if (!state.value.isTrackersBlockingEnabled) {
- _singleEvents.emit(SingleEvent.ToastTrackersControlDisabled)
- }
- trackersStateUseCase.toggleAppWhitelist(app, !action.isBlocked)
- _state.update {
- it.copy(
- isBlockingActivated = !trackersStateUseCase.isWhitelisted(app)
- )
- }
- }
- }
-
- private suspend fun toggleTrackerAction(action: Action.ToggleTrackerAction) {
- withContext(Dispatchers.IO) {
- if (!state.value.isTrackersBlockingEnabled) {
- _singleEvents.emit(SingleEvent.ToastTrackersControlDisabled)
- }
-
- if (state.value.isBlockingActivated) {
- trackersStateUseCase.blockTracker(app, action.tracker, action.isBlocked)
- updateWhitelist()
- }
- }
- }
-
- private suspend fun actionClickTracker(action: Action.ClickTracker) {
- withContext(Dispatchers.IO) {
- action.tracker.exodusId?.let {
- try {
- _singleEvents.emit(
- SingleEvent.OpenUrl(
- Uri.parse(exodusBaseUrl + it)
- )
- )
- } catch (e: Exception) {
- }
- }
- }
- }
-
- private suspend fun resetAllTrackers() {
- withContext(Dispatchers.IO) {
- trackersStateUseCase.clearWhitelist(app)
- updateWhitelist()
- }
- }
- private fun fetchStatistics() {
- val (blocked, leaked) = trackersStatisticsUseCase.getCalls(app)
- return _state.update { s ->
- s.copy(
- trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(app),
- leaked = leaked,
- blocked = blocked,
- isWhitelistEmpty = trackersStatisticsUseCase.isWhiteListEmpty(app)
- )
- }
- }
-
- private fun updateWhitelist() {
- _state.update { s ->
- s.copy(
- trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(app),
- isWhitelistEmpty = trackersStatisticsUseCase.isWhiteListEmpty(app)
- )
- }
- }
-
- sealed class SingleEvent {
- data class ErrorEvent(@StringRes val errorResId: Int) : SingleEvent()
- data class OpenUrl(val url: Uri) : SingleEvent()
- object ToastTrackersControlDisabled : SingleEvent()
- }
-
- sealed class Action {
- data class BlockAllToggleAction(val isBlocked: Boolean) : Action()
- data class ToggleTrackerAction(val tracker: Tracker, val isBlocked: Boolean) : Action()
- data class ClickTracker(val tracker: Tracker) : Action()
- object ResetAllTrackers : Action()
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
deleted file mode 100644
index 197f13f..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.features.trackers.apptrackers
-
-import android.text.SpannableString
-import android.text.style.UnderlineSpan
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Switch
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.recyclerview.widget.RecyclerView
-import foundation.e.privacycentralapp.R
-import foundation.e.privacymodules.trackers.api.Tracker
-
-class ToggleTrackersAdapter(
- private val itemsLayout: Int,
- private val onToggleSwitch: (Tracker, Boolean) -> Unit,
- private val onClickTitle: (Tracker) -> Unit
-) : RecyclerView.Adapter<ToggleTrackersAdapter.ViewHolder>() {
-
- var isEnabled = true
-
- class ViewHolder(
- view: View,
- private val onToggleSwitch: (Tracker, Boolean) -> Unit,
- private val onClickTitle: (Tracker) -> Unit
- ) : RecyclerView.ViewHolder(view) {
- val title: TextView = view.findViewById(R.id.title)
-
- val toggle: Switch = view.findViewById(R.id.toggle)
-
- fun bind(item: Pair<Tracker, Boolean>, isEnabled: Boolean) {
- val text = item.first.label
- if (item.first.exodusId != null) {
- title.setTextColor(ContextCompat.getColor(title.context, R.color.accent))
- val spannable = SpannableString(text)
- spannable.setSpan(UnderlineSpan(), 0, spannable.length, 0)
- title.text = spannable
- } else {
- title.setTextColor(ContextCompat.getColor(title.context, R.color.primary_text))
- title.text = text
- }
-
- toggle.isChecked = item.second
- toggle.isEnabled = isEnabled
-
- toggle.setOnClickListener {
- onToggleSwitch(item.first, toggle.isChecked)
- }
-
- title.setOnClickListener { onClickTitle(item.first) }
- }
- }
-
- private var dataSet: List<Pair<Tracker, Boolean>> = emptyList()
-
- fun updateDataSet(new: List<Pair<Tracker, Boolean>>, isEnabled: Boolean) {
- this.isEnabled = isEnabled
- dataSet = new
- notifyDataSetChanged()
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(itemsLayout, parent, false)
- return ViewHolder(view, onToggleSwitch, onClickTitle)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val permission = dataSet[position]
- holder.bind(permission, isEnabled)
- }
-
- override fun getItemCount(): Int = dataSet.size
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt
deleted file mode 100644
index 92dc326..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.main
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import androidx.fragment.app.FragmentActivity
-import androidx.fragment.app.add
-import androidx.fragment.app.commit
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.features.dashboard.DashboardFragment
-import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyFragment
-import foundation.e.privacycentralapp.features.location.FakeLocationFragment
-import foundation.e.privacycentralapp.features.trackers.TrackersFragment
-
-open class MainActivity : FragmentActivity(R.layout.activity_main) {
- override fun onPostCreate(savedInstanceState: Bundle?) {
- super.onPostCreate(savedInstanceState)
- handleIntent(intent)
- }
-
- override fun onNewIntent(intent: Intent) {
- super.onNewIntent(intent)
- handleIntent(intent)
- }
-
- open fun handleIntent(intent: Intent) {
- supportFragmentManager.commit {
- setReorderingAllowed(true)
- when (intent.action) {
- ACTION_HIGHLIGHT_LEAKS -> add<DashboardFragment>(
- containerViewId = R.id.container,
- args = intent.extras
- )
- ACTION_VIEW_TRACKERS -> {
- add<TrackersFragment>(R.id.container)
- }
- ACTION_VIEW_FAKE_LOCATION -> {
- add<FakeLocationFragment>(R.id.container)
- }
- ACTION_VIEW_IPSCRAMBLING -> {
- add<InternetPrivacyFragment>(R.id.container)
- }
- else -> add<DashboardFragment>(R.id.container)
- }
- disallowAddToBackStack()
- }
- }
-
- override fun finishAfterTransition() {
- val resultData = Intent()
- val result = onPopulateResultIntent(resultData)
- setResult(result, resultData)
-
- super.finishAfterTransition()
- }
-
- open fun onPopulateResultIntent(intent: Intent): Int = Activity.RESULT_OK
-
- companion object {
- private const val ACTION_HIGHLIGHT_LEAKS = "ACTION_HIGHLIGHT_LEAKS"
- private const val ACTION_VIEW_TRACKERS = "ACTION_VIEW_TRACKERS"
- private const val ACTION_VIEW_FAKE_LOCATION = "ACTION_VIEW_FAKE_LOCATION"
- private const val ACTION_VIEW_IPSCRAMBLING = "ACTION_VIEW_IPSCRAMBLING"
-
- fun createHighlightLeaksIntent(context: Context, highlightIndex: Int) =
- Intent(context, MainActivity::class.java).apply {
- action = ACTION_HIGHLIGHT_LEAKS
- putExtras(DashboardFragment.buildArgs(highlightIndex))
- }
-
- fun createTrackersIntent(context: Context) =
- Intent(context, MainActivity::class.java).apply {
- action = ACTION_VIEW_TRACKERS
- }
-
- fun createFakeLocationIntent(context: Context): Intent {
- return Intent(context, MainActivity::class.java).apply {
- action = ACTION_VIEW_FAKE_LOCATION
- }
- }
-
- fun createIpScramblingIntent(context: Context): Intent {
- return Intent(context, MainActivity::class.java).apply {
- action = ACTION_VIEW_IPSCRAMBLING
- }
- }
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt
deleted file mode 100644
index 3abe21b..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.privacycentralapp
-
-import android.appwidget.AppWidgetManager
-import android.appwidget.AppWidgetProvider
-import android.content.Context
-import android.os.Bundle
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
-import foundation.e.privacycentralapp.widget.State
-import foundation.e.privacycentralapp.widget.render
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.sample
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import java.time.temporal.ChronoUnit
-
-/**
- * Implementation of App Widget functionality.
- */
-class Widget : AppWidgetProvider() {
-
- override fun onUpdate(
- context: Context,
- appWidgetManager: AppWidgetManager,
- appWidgetIds: IntArray
- ) {
- render(context, state.value, appWidgetManager)
- }
-
- override fun onEnabled(context: Context) {
- // Enter relevant functionality for when the first widget is created
- }
-
- override fun onDisabled(context: Context) {
- // Enter relevant functionality for when the last widget is disabled
- }
-
- companion object {
- private var updateWidgetJob: Job? = null
-
- private var state: StateFlow<State> = MutableStateFlow(State())
-
- private const val DARK_TEXT_KEY = "foundation.e.blisslauncher.WIDGET_OPTION_DARK_TEXT"
- var isDarkText = false
-
- @OptIn(FlowPreview::class)
- private fun initState(
- getPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- trackersStatisticsUseCase: TrackersStatisticsUseCase,
- coroutineScope: CoroutineScope
- ): StateFlow<State> {
-
- return combine(
- getPrivacyStateUseCase.quickPrivacyState,
- getPrivacyStateUseCase.trackerMode,
- getPrivacyStateUseCase.isLocationHidden,
- getPrivacyStateUseCase.ipScramblingMode,
- ) { quickPrivacyState, trackerMode, isLocationHidden, ipScramblingMode ->
-
- State(
- quickPrivacyState = quickPrivacyState,
- trackerMode = trackerMode,
- isLocationHidden = isLocationHidden,
- ipScramblingMode = ipScramblingMode
- )
- }.sample(50)
- .combine(
- merge(
- trackersStatisticsUseCase.listenUpdates()
- .onStart { emit(Unit) }
- .debounce(5000),
- flow {
- while (true) {
- emit(Unit)
- delay(ChronoUnit.HOURS.duration.toMillis())
- }
- }
-
- )
- ) { state, _ ->
- state.copy(
- dayStatistics = trackersStatisticsUseCase.getDayTrackersCalls(),
- activeTrackersCount = trackersStatisticsUseCase.getDayTrackersCount()
- )
- }.stateIn(
- scope = coroutineScope,
- started = SharingStarted.Eagerly,
- initialValue = State()
- )
- }
-
- @OptIn(DelicateCoroutinesApi::class)
- fun startListening(
- appContext: Context,
- getPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- trackersStatisticsUseCase: TrackersStatisticsUseCase,
- ) {
- state = initState(
- getPrivacyStateUseCase,
- trackersStatisticsUseCase,
- GlobalScope
- )
-
- updateWidgetJob?.cancel()
- updateWidgetJob = GlobalScope.launch(Dispatchers.Main) {
- state.collect {
- render(appContext, it, AppWidgetManager.getInstance(appContext))
- }
- }
- }
- }
-
- override fun onAppWidgetOptionsChanged(
- context: Context,
- appWidgetManager: AppWidgetManager,
- appWidgetId: Int,
- newOptions: Bundle?
- ) {
- super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
- if (newOptions != null) {
- isDarkText = newOptions.getBoolean(DARK_TEXT_KEY)
- }
- render(context, state.value, appWidgetManager)
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt
deleted file mode 100644
index e01f47f..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.privacycentralapp.widget
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import foundation.e.privacycentralapp.PrivacyCentralApplication
-
-class WidgetCommandReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- val getQuickPrivacyStateUseCase = (context?.applicationContext as? PrivacyCentralApplication)?.dependencyContainer?.getQuickPrivacyStateUseCase
-
- when (intent?.action) {
- ACTION_TOGGLE_TRACKERS -> getQuickPrivacyStateUseCase?.toggleTrackers()
- ACTION_TOGGLE_LOCATION -> getQuickPrivacyStateUseCase?.toggleLocation()
- ACTION_TOGGLE_IPSCRAMBLING -> getQuickPrivacyStateUseCase?.toggleIpScrambling()
- else -> {}
- }
- }
-
- companion object {
- const val ACTION_TOGGLE_TRACKERS = "toggle_trackers"
- const val ACTION_TOGGLE_LOCATION = "toggle_location"
- const val ACTION_TOGGLE_IPSCRAMBLING = "toggle_ipscrambling"
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt
deleted file mode 100644
index fccfd48..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * 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.privacycentralapp.widget
-
-import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_IMMUTABLE
-import android.app.PendingIntent.FLAG_UPDATE_CURRENT
-import android.appwidget.AppWidgetManager
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import android.widget.RemoteViews
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.Widget
-import foundation.e.privacycentralapp.Widget.Companion.isDarkText
-import foundation.e.privacycentralapp.common.extensions.dpToPxF
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
-import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState
-import foundation.e.privacycentralapp.domain.entities.TrackerMode
-import foundation.e.privacycentralapp.main.MainActivity
-import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_IPSCRAMBLING
-import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_LOCATION
-import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_TRACKERS
-
-data class State(
- val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED,
- val trackerMode: TrackerMode = TrackerMode.VULNERABLE,
- val isLocationHidden: Boolean = false,
- val ipScramblingMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP_LOADING,
- val dayStatistics: List<Pair<Int, Int>> = emptyList(),
- val activeTrackersCount: Int = 0,
-)
-
-fun render(
- context: Context,
- state: State,
- appWidgetManager: AppWidgetManager,
-) {
- val views = RemoteViews(context.packageName, R.layout.widget)
- applyDarkText(context, state, views)
- views.apply {
- val openPIntent = PendingIntent.getActivity(
- context,
- REQUEST_CODE_DASHBOARD,
- Intent(context, MainActivity::class.java),
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- setOnClickPendingIntent(R.id.settings_btn, openPIntent)
- setOnClickPendingIntent(R.id.widget_container, openPIntent)
-
- setTextViewText(
- R.id.state_label,
- context.getString(
- when (state.quickPrivacyState) {
- QuickPrivacyState.DISABLED -> R.string.widget_state_title_off
- QuickPrivacyState.FULL_ENABLED -> R.string.widget_state_title_on
- QuickPrivacyState.ENABLED -> R.string.widget_state_title_custom
- }
- )
- )
-
- setImageViewResource(
- R.id.toggle_trackers,
- if (state.trackerMode == TrackerMode.VULNERABLE)
- R.drawable.ic_switch_disabled
- else R.drawable.ic_switch_enabled
- )
-
- setOnClickPendingIntent(
- R.id.toggle_trackers,
- PendingIntent.getBroadcast(
- context,
- REQUEST_CODE_TOGGLE_TRACKERS,
- Intent(context, WidgetCommandReceiver::class.java).apply {
- action = ACTION_TOGGLE_TRACKERS
- },
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- )
-
- setTextViewText(
- R.id.state_trackers,
- context.getString(
- when (state.trackerMode) {
- TrackerMode.DENIED -> R.string.widget_state_trackers_on
- TrackerMode.VULNERABLE -> R.string.widget_state_trackers_off
- TrackerMode.CUSTOM -> R.string.widget_state_trackers_custom
- }
- )
- )
-
- setImageViewResource(
- R.id.toggle_location,
- if (state.isLocationHidden) R.drawable.ic_switch_enabled
- else R.drawable.ic_switch_disabled
- )
-
- setOnClickPendingIntent(
- R.id.toggle_location,
- PendingIntent.getBroadcast(
- context,
- REQUEST_CODE_TOGGLE_LOCATION,
- Intent(context, WidgetCommandReceiver::class.java).apply {
- action = ACTION_TOGGLE_LOCATION
- },
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- )
-
- setTextViewText(
- R.id.state_geolocation,
- context.getString(
- if (state.isLocationHidden) R.string.widget_state_geolocation_on
- else R.string.widget_state_geolocation_off
- )
- )
-
- setImageViewResource(
- R.id.toggle_ipscrambling,
- if (state.ipScramblingMode.isChecked) R.drawable.ic_switch_enabled
- else R.drawable.ic_switch_disabled
- )
-
- setOnClickPendingIntent(
- R.id.toggle_ipscrambling,
- PendingIntent.getBroadcast(
- context,
- REQUEST_CODE_TOGGLE_IPSCRAMBLING,
- Intent(context, WidgetCommandReceiver::class.java).apply {
- action = ACTION_TOGGLE_IPSCRAMBLING
- },
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- )
-
- setTextViewText(
- R.id.state_ip_address,
- context.getString(
- if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.string.widget_state_ipaddress_on
- else R.string.widget_state_ipaddress_off
- )
- )
-
- val loading = state.ipScramblingMode.isLoading
-
- setViewVisibility(R.id.state_ip_address, if (loading) View.GONE else View.VISIBLE)
-
- setViewVisibility(R.id.state_ip_address_loader, if (loading) View.VISIBLE else View.GONE)
-
- if (state.dayStatistics.all { it.first == 0 && it.second == 0 }) {
- setViewVisibility(R.id.graph, View.GONE)
- setViewVisibility(R.id.graph_legend, View.GONE)
- setViewVisibility(R.id.graph_empty, View.VISIBLE)
- setViewVisibility(R.id.graph_legend_values, View.GONE)
- setViewVisibility(R.id.graph_view_trackers_btn, View.GONE)
- } else {
- setViewVisibility(R.id.graph, View.VISIBLE)
- setViewVisibility(R.id.graph_legend, View.VISIBLE)
- setViewVisibility(R.id.graph_empty, View.GONE)
- setViewVisibility(R.id.graph_legend_values, View.VISIBLE)
- setViewVisibility(R.id.graph_view_trackers_btn, View.VISIBLE)
-
- val pIntent = PendingIntent.getActivity(
- context,
- REQUEST_CODE_TRACKERS,
- MainActivity.createTrackersIntent(context),
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
-
- setOnClickPendingIntent(R.id.graph_view_trackers_btn, pIntent)
-
- val graphHeightPx = 26.dpToPxF(context)
- val maxValue =
- state.dayStatistics
- .map { it.first + it.second }
- .maxOrNull()
- .let { if (it == null || it == 0) 1 else it }
- val ratio = graphHeightPx / maxValue
-
- state.dayStatistics.forEachIndexed { index, (blocked, leaked) ->
- // blocked (the bar below)
- val middlePadding = graphHeightPx - blocked * ratio
- setViewPadding(blockedBarIds[index], 0, middlePadding.toInt(), 0, 0)
-
- // leaked (the bar above)
- val topPadding = graphHeightPx - (blocked + leaked) * ratio
- setViewPadding(leakedBarIds[index], 0, topPadding.toInt(), 0, 0)
-
- val highlightPIntent = PendingIntent.getActivity(
- context, REQUEST_CODE_HIGHLIGHT + index,
- MainActivity.createHighlightLeaksIntent(context, index),
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- setOnClickPendingIntent(containerBarIds[index], highlightPIntent)
- }
-
- setTextViewText(
- R.id.graph_legend,
- context.getString(
- R.string.widget_graph_trackers_legend,
- state.activeTrackersCount.toString()
- )
- )
- }
- }
-
- appWidgetManager.updateAppWidget(ComponentName(context, Widget::class.java), views)
-}
-
-private val containerBarIds = listOf(
- R.id.widget_graph_bar_container_0,
- R.id.widget_graph_bar_container_1,
- R.id.widget_graph_bar_container_2,
- R.id.widget_graph_bar_container_3,
- R.id.widget_graph_bar_container_4,
- R.id.widget_graph_bar_container_5,
- R.id.widget_graph_bar_container_6,
- R.id.widget_graph_bar_container_7,
- R.id.widget_graph_bar_container_8,
- R.id.widget_graph_bar_container_9,
- R.id.widget_graph_bar_container_10,
- R.id.widget_graph_bar_container_11,
- R.id.widget_graph_bar_container_12,
- R.id.widget_graph_bar_container_13,
- R.id.widget_graph_bar_container_14,
- R.id.widget_graph_bar_container_15,
- R.id.widget_graph_bar_container_16,
- R.id.widget_graph_bar_container_17,
- R.id.widget_graph_bar_container_18,
- R.id.widget_graph_bar_container_19,
- R.id.widget_graph_bar_container_20,
- R.id.widget_graph_bar_container_21,
- R.id.widget_graph_bar_container_22,
- R.id.widget_graph_bar_container_23,
-)
-
-private val blockedBarIds = listOf(
- R.id.widget_graph_bar_0,
- R.id.widget_graph_bar_1,
- R.id.widget_graph_bar_2,
- R.id.widget_graph_bar_3,
- R.id.widget_graph_bar_4,
- R.id.widget_graph_bar_5,
- R.id.widget_graph_bar_6,
- R.id.widget_graph_bar_7,
- R.id.widget_graph_bar_8,
- R.id.widget_graph_bar_9,
- R.id.widget_graph_bar_10,
- R.id.widget_graph_bar_11,
- R.id.widget_graph_bar_12,
- R.id.widget_graph_bar_13,
- R.id.widget_graph_bar_14,
- R.id.widget_graph_bar_15,
- R.id.widget_graph_bar_16,
- R.id.widget_graph_bar_17,
- R.id.widget_graph_bar_18,
- R.id.widget_graph_bar_19,
- R.id.widget_graph_bar_20,
- R.id.widget_graph_bar_21,
- R.id.widget_graph_bar_22,
- R.id.widget_graph_bar_23
-)
-
-private val leakedBarIds = listOf(
- R.id.widget_leaked_graph_bar_0,
- R.id.widget_leaked_graph_bar_1,
- R.id.widget_leaked_graph_bar_2,
- R.id.widget_leaked_graph_bar_3,
- R.id.widget_leaked_graph_bar_4,
- R.id.widget_leaked_graph_bar_5,
- R.id.widget_leaked_graph_bar_6,
- R.id.widget_leaked_graph_bar_7,
- R.id.widget_leaked_graph_bar_8,
- R.id.widget_leaked_graph_bar_9,
- R.id.widget_leaked_graph_bar_10,
- R.id.widget_leaked_graph_bar_11,
- R.id.widget_leaked_graph_bar_12,
- R.id.widget_leaked_graph_bar_13,
- R.id.widget_leaked_graph_bar_14,
- R.id.widget_leaked_graph_bar_15,
- R.id.widget_leaked_graph_bar_16,
- R.id.widget_leaked_graph_bar_17,
- R.id.widget_leaked_graph_bar_18,
- R.id.widget_leaked_graph_bar_19,
- R.id.widget_leaked_graph_bar_20,
- R.id.widget_leaked_graph_bar_21,
- R.id.widget_leaked_graph_bar_22,
- R.id.widget_leaked_graph_bar_23
-)
-
-private const val REQUEST_CODE_DASHBOARD = 1
-private const val REQUEST_CODE_TRACKERS = 3
-private const val REQUEST_CODE_TOGGLE_TRACKERS = 4
-private const val REQUEST_CODE_TOGGLE_LOCATION = 5
-private const val REQUEST_CODE_TOGGLE_IPSCRAMBLING = 6
-private const val REQUEST_CODE_HIGHLIGHT = 100
-
-fun applyDarkText(context: Context, state: State, views: RemoteViews) {
- views.apply {
- listOf(
- R.id.state_label,
- R.id.graph_legend_blocked,
- R.id.graph_legend_allowed,
-
- )
- .forEach {
- setTextColor(
- it,
- context.getColor(if (isDarkText) R.color.on_surface_disabled_light else R.color.on_primary_medium_emphasis)
- )
- }
- setTextColor(
- R.id.widget_title,
- context.getColor(if (isDarkText) R.color.on_surface_medium_emphasis_light else R.color.on_surface_high_emphasis)
- )
- listOf(
- R.id.state_trackers,
- R.id.state_geolocation,
- R.id.state_ip_address,
- R.id.graph_legend,
- R.id.graph_view_trackers_btn
- )
- .forEach {
- setTextColor(
- it,
- context.getColor(if (isDarkText) R.color.on_surface_medium_emphasis_light else R.color.on_primary_high_emphasis)
- )
- }
-
- listOf(
- R.id.trackers_label,
- R.id.geolocation_label,
- R.id.ip_address_label,
- R.id.graph_empty
-
- )
- .forEach {
- setTextColor(
- it,
- context.getColor(if (isDarkText) R.color.on_surface_disabled_light else R.color.on_primary_disabled)
- )
- }
- setTextViewCompoundDrawables(
- R.id.graph_view_trackers_btn,
- 0,
- 0,
- if (isDarkText) R.drawable.ic_chevron_right_24dp_light else R.drawable.ic_chevron_right_24dp,
- 0
- )
- setImageViewResource(
- R.id.settings_btn,
- if (isDarkText) R.drawable.ic_settings_light else R.drawable.ic_settings
- )
- setImageViewResource(
- R.id.state_icon,
- if (isDarkText) {
- if (state.quickPrivacyState.isEnabled()) R.drawable.ic_shield_on_light
- else R.drawable.ic_shield_off_light
- } else {
- if (state.quickPrivacyState.isEnabled()) R.drawable.ic_shield_on_white
- else R.drawable.ic_shield_off_white
- }
- )
- }
-}