diff options
| author | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-05-02 21:25:17 +0200 | 
|---|---|---|
| committer | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-05-02 22:00:35 +0200 | 
| commit | a8874167f663885f2d3371801cf03681576ac817 (patch) | |
| tree | 5be07b8768142efeade536d4135f2250c1ac9071 /app/src/main/java/foundation/e/privacycentralapp | |
| parent | a0ee04ea9dbc0802c828afdf660eb37dc6fa350f (diff) | |
| download | advanced-privacy-a8874167f663885f2d3371801cf03681576ac817.tar.gz | |
1200: rename everything to AdvancedPrivacy
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp')
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 -            } -        ) -    } -} | 
