diff options
Diffstat (limited to 'app/src/main')
7 files changed, 281 insertions, 39 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index f241e67..e6d4c42 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -143,9 +143,17 @@ class DependencyContainer(val app: Application) { getQuickPrivacyStateUseCase, trackersStatisticsUseCase, ) + + Notifications.startListening( + context, + getQuickPrivacyStateUseCase, + permissionsModule, + GlobalScope + ) } } +@Suppress("LongParameterList") class ViewModelsFactory( private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, private val trackersStatisticsUseCase: TrackersStatisticsUseCase, diff --git a/app/src/main/java/foundation/e/privacycentralapp/Notifications.kt b/app/src/main/java/foundation/e/privacycentralapp/Notifications.kt new file mode 100644 index 0000000..0df3e18 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/Notifications.kt @@ -0,0 +1,210 @@ +/* + * 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/common/BootCompletedReceiver.kt b/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt index f43c2cc..d7902ee 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt @@ -17,57 +17,20 @@ package foundation.e.privacycentralapp.common -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import androidx.core.app.NotificationCompat -import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.Notifications import foundation.e.privacycentralapp.data.repositories.LocalStateRepository class BootCompletedReceiver : BroadcastReceiver() { - companion object { - const val FIRST_BOOT_NOTIFICATION_ID = 10 - } - override fun onReceive(context: Context, intent: Intent?) { if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { val localStateRepository = LocalStateRepository(context) if (localStateRepository.firstBoot) { - showNotification(context) + Notifications.showFirstBootNotification(context) localStateRepository.firstBoot = false } } } - - private fun showNotification(context: Context) { - val channelId = "first_boot_notification" - val pendingIntent = - PendingIntent.getActivity( - context, - 0, - context.packageManager.getLaunchIntentForPackage(context.packageName), - PendingIntent.FLAG_IMMUTABLE - ) - val notificationBuilder: NotificationCompat.Builder = - NotificationCompat.Builder(context, channelId) - .setSmallIcon(R.drawable.ic_notification_logo) - .setContentTitle(context.getString(R.string.first_notification_title)) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - .setStyle( - NotificationCompat.BigTextStyle() - .bigText(context.getString(R.string.first_notification_summary)) - ) - val notificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val name: CharSequence = "First Boot" - val importance = NotificationManager.IMPORTANCE_HIGH - val mChannel = NotificationChannel(channelId, name, importance) - notificationManager.createNotificationChannel(mChannel) - notificationManager.notify(FIRST_BOOT_NOTIFICATION_ID, notificationBuilder.build()) - } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt index a81f5b5..92dc326 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt @@ -26,6 +26,8 @@ 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) { @@ -50,6 +52,12 @@ open class MainActivity : FragmentActivity(R.layout.activity_main) { 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() @@ -69,6 +77,8 @@ open class MainActivity : FragmentActivity(R.layout.activity_main) { 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 { @@ -80,5 +90,17 @@ open class MainActivity : FragmentActivity(R.layout.activity_main) { 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/res/drawable/ic_fmd_bad.xml b/app/src/main/res/drawable/ic_fmd_bad.xml new file mode 100644 index 0000000..beeff35 --- /dev/null +++ b/app/src/main/res/drawable/ic_fmd_bad.xml @@ -0,0 +1,15 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12,2C7.8,2 4,5.22 4,10.2C4,13.52 6.67,17.45 12,22C17.33,17.45 20,13.52 20,10.2C20,5.22 16.2,2 12,2ZM12,19.33C7.95,15.63 6,12.54 6,10.19C6,6.57 8.65,4 12,4C15.35,4 18,6.57 18,10.2C18,12.54 16.05,15.64 12,19.33Z" + android:fillColor="#000000"/> + <path + android:pathData="M13,6H11V11H13V6Z" + android:fillColor="#000000"/> + <path + android:pathData="M13,13H11V15H13V13Z" + android:fillColor="#000000"/> +</vector> diff --git a/app/src/main/res/drawable/ic_language.xml b/app/src/main/res/drawable/ic_language.xml new file mode 100644 index 0000000..ae37ec0 --- /dev/null +++ b/app/src/main/res/drawable/ic_language.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M11.99,2C6.47,2 2,6.48 2,12C2,17.52 6.47,22 11.99,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 11.99,2ZM18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.35 18.92,8ZM12,4.04C12.83,5.24 13.48,6.57 13.91,8H10.09C10.52,6.57 11.17,5.24 12,4.04ZM4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14H4.26ZM5.08,16H8.03C8.35,17.25 8.81,18.45 9.41,19.56C7.57,18.93 6.04,17.66 5.08,16ZM8.03,8H5.08C6.04,6.34 7.57,5.07 9.41,4.44C8.81,5.55 8.35,6.75 8.03,8ZM12,19.96C11.17,18.76 10.52,17.43 10.09,16H13.91C13.48,17.43 12.83,18.76 12,19.96ZM14.34,14H9.66C9.57,13.34 9.5,12.68 9.5,12C9.5,11.32 9.57,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14ZM14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56ZM16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14H16.36Z" + android:fillColor="#000000"/> +</vector> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d6e1f9..b29bbca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,6 +102,7 @@ <string name="warningdialog_ipscrambling_message">This option uses the TOR network to hide your real IP address. Only use it for specific applications. While your IP is faked, your Internet speed is likely to be reduced.\n<b>Important:</b> we advise disabling this feature for email services as your address may end into permanent ban from your provider.</string> <string name="warningdialog_ipscrambling_cta">I understand</string> + <!-- Widget --> <string name="widget_title" translatable="false">@string/app_name</string> <string name="widget_state_title_on">Your online privacy is protected</string> @@ -119,6 +120,20 @@ <string name="widget_state_ipaddress_on" translatable="false">@string/dashboard_state_ipaddress_on</string> <string name="widget_graph_trackers_legend" translatable="false">@string/dashboard_graph_trackers_legend</string> <string name="widget_graph_view_trackers">View</string> + + <!-- Notifications --> + <string name="notifications_first_boot_channel_name">First Boot</string> <string name="first_notification_title">Discover Advanced Privacy</string> <string name="first_notification_summary">Tap to find out how to easily block trackers, fake your location & hide your IP address.</string> + <string name="notifications_fake_location_channel_name">Fake location flag</string> + <string name="notifications_fake_location_channel_description">Highlight that location is currently faked by Advanced Privacy</string> + + <string name="notifications_fake_location_title">Fake location is on</string> + <string name="notifications_fake_location_content">All apps using your location including your navigation app are now using this fake location.</string> + + <string name="notifications_ipscrambling_channel_name">Hide my IP flag</string> + <string name="notifications_ipscrambling_channel_description">Highlight that IP is currently hidden by Advanced Privacy</string> + <string name="notifications_ipscrambling_title">Real IP hidden</string> + <string name="notifications_ipscrambling_content">This could impact the functioning of some applications.</string> + </resources> |