Skip to content

Commit

Permalink
Add option to opt out of Analytics and Crashlytics (#1237)
Browse files Browse the repository at this point in the history
  • Loading branch information
Animeboynz authored Oct 12, 2024
1 parent c8bb78d commit 7c7af72
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 45 deletions.
9 changes: 9 additions & 0 deletions app/src/dev/java/mihon/core/firebase/Firebase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package mihon.core.firebase

import android.content.Context
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import kotlinx.coroutines.CoroutineScope

object Firebase {
fun setup(context: Context, preference: PrivacyPreferences, scope: CoroutineScope) = Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
Expand All @@ -34,13 +36,18 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.injectLazy

internal class PermissionStep : OnboardingStep {

private val privacyPreferences: PrivacyPreferences by injectLazy()

private var notificationGranted by mutableStateOf(false)
private var batteryGranted by mutableStateOf(false)

Expand Down Expand Up @@ -73,7 +80,7 @@ internal class PermissionStep : OnboardingStep {
}

Column {
PermissionItem(
PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_install_apps),
subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
granted = installGranted,
Expand All @@ -89,15 +96,15 @@ internal class PermissionStep : OnboardingStep {
// no-op. resulting checks is being done on resume
},
)
PermissionItem(
PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_notifications),
subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
granted = notificationGranted,
onButtonClick = { permissionRequester.launch(Manifest.permission.POST_NOTIFICATIONS) },
)
}

PermissionItem(
PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
granted = batteryGranted,
Expand All @@ -109,6 +116,29 @@ internal class PermissionStep : OnboardingStep {
context.startActivity(intent)
},
)

HorizontalDivider(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
color = MaterialTheme.colorScheme.onPrimaryContainer,
)

val crashlyticsPref = privacyPreferences.crashlytics()
val crashlytics by crashlyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
granted = crashlytics,
onToggleChange = crashlyticsPref::set,
)

val analyticsPref = privacyPreferences.analytics()
val analytics by analyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
granted = analytics,
onToggleChange = analyticsPref::set,
)
}
}

Expand All @@ -127,7 +157,7 @@ internal class PermissionStep : OnboardingStep {
}

@Composable
private fun PermissionItem(
private fun PermissionCheckbox(
title: String,
subtitle: String,
granted: Boolean,
Expand Down Expand Up @@ -157,4 +187,26 @@ internal class PermissionStep : OnboardingStep {
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}

@Composable
private fun PermissionSwitch(
title: String,
subtitle: String,
granted: Boolean,
modifier: Modifier = Modifier,
onToggleChange: (Boolean) -> Unit,
) {
ListItem(
modifier = modifier,
headlineContent = { Text(text = title) },
supportingContent = { Text(text = subtitle) },
trailingContent = {
Switch(
checked = granted,
onCheckedChange = onToggleChange,
)
},
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.fragment.app.FragmentActivity
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
Expand All @@ -28,55 +29,91 @@ object SettingsSecurityScreen : SearchableSettings {

@Composable
override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
val authSupported = remember { context.isAuthenticationSupported() }
val privacyPreferences = remember { Injekt.get<PrivacyPreferences>() }
return listOf(
getSecurityGroup(securityPreferences),
getFirebaseGroup(privacyPreferences),
)
}

@Composable
private fun getSecurityGroup(
securityPreferences: SecurityPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator()
val useAuth by useAuthPref.collectAsState()

return listOf(
Preference.PreferenceItem.SwitchPreference(
pref = useAuthPref,
title = stringResource(MR.strings.lock_with_biometrics),
enabled = authSupported,
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_with_biometrics),
)
},
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.lockAppAfter(),
title = stringResource(MR.strings.lock_when_idle),
enabled = authSupported && useAuth,
entries = LockAfterValues
.associateWith {
when (it) {
-1 -> stringResource(MR.strings.lock_never)
0 -> stringResource(MR.strings.lock_always)
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_security),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = useAuthPref,
title = stringResource(MR.strings.lock_with_biometrics),
enabled = authSupported,
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_with_biometrics),
)
},
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.lockAppAfter(),
title = stringResource(MR.strings.lock_when_idle),
enabled = authSupported && useAuth,
entries = LockAfterValues
.associateWith {
when (it) {
-1 -> stringResource(MR.strings.lock_never)
0 -> stringResource(MR.strings.lock_always)
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
}
}
}
.toImmutableMap(),
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_when_idle),
)
},
),
Preference.PreferenceItem.SwitchPreference(
pref = securityPreferences.hideNotificationContent(),
title = stringResource(MR.strings.hide_notification_content),
.toImmutableMap(),
onValueChanged = {
(context as FragmentActivity).authenticate(
title = context.stringResource(MR.strings.lock_when_idle),
)
},
),

Preference.PreferenceItem.SwitchPreference(
pref = securityPreferences.hideNotificationContent(),
title = stringResource(MR.strings.hide_notification_content),
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(),
title = stringResource(MR.strings.secure_screen),
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(),
title = stringResource(MR.strings.secure_screen),
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
)
}

@Composable
private fun getFirebaseGroup(
privacyPreferences: PrivacyPreferences,
): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_firebase),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = privacyPreferences.crashlytics(),
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
),
Preference.PreferenceItem.SwitchPreference(
pref = privacyPreferences.analytics(),
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.firebase_summary)),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
)
}
}
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/eu/kanade/tachiyomi/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import eu.kanade.domain.DomainModule
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
Expand All @@ -50,6 +51,7 @@ import kotlinx.coroutines.flow.onEach
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import logcat.LogcatLogger
import mihon.core.firebase.Firebase
import mihon.core.migration.Migrator
import mihon.core.migration.migrations.migrations
import org.conscrypt.Conscrypt
Expand All @@ -67,6 +69,7 @@ import java.security.Security
class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {

private val basePreferences: BasePreferences by injectLazy()
private val privacyPreferences: PrivacyPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy()

private val disableIncognitoReceiver = DisableIncognitoReceiver()
Expand All @@ -93,6 +96,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
Injekt.importModule(AppModule(this))
Injekt.importModule(DomainModule())

Firebase.setup(applicationContext, privacyPreferences, ProcessLifecycleOwner.get().lifecycleScope)

setupNotificationChannels()

ProcessLifecycleOwner.get().lifecycle.addObserver(this)
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
Expand Down Expand Up @@ -39,6 +40,9 @@ class PreferenceModule(val app: Application) : InjektModule {
addSingletonFactory {
SecurityPreferences(get())
}
addSingletonFactory {
PrivacyPreferences(get())
}
addSingletonFactory {
LibraryPreferences(get())
}
Expand Down
9 changes: 9 additions & 0 deletions app/src/standard/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
tools:node="remove" />

<application>
<!-- Disable for manual opt-in -->
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />

<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />

<!-- Disable unnecessary stuff from Firebase -->
<meta-data
android:name="google_analytics_adid_collection_enabled"
Expand Down
20 changes: 20 additions & 0 deletions app/src/standard/java/mihon/core/firebase/Firebase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package mihon.core.firebase

import android.content.Context
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

object Firebase {
fun setup(context: Context, preference: PrivacyPreferences, scope: CoroutineScope) {
preference.analytics().changes().onEach { enabled ->
FirebaseAnalytics.getInstance(context).setAnalyticsCollectionEnabled(enabled)
}.launchIn(scope)
preference.crashlytics().changes().onEach { enabled ->
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enabled)
}.launchIn(scope)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.core.security

import tachiyomi.core.common.preference.PreferenceStore

class PrivacyPreferences(
private val preferenceStore: PreferenceStore,
) {
fun crashlytics() = preferenceStore.getBoolean("crashlytics", true)

fun analytics() = preferenceStore.getBoolean("analytics", true)
}
Loading

0 comments on commit 7c7af72

Please sign in to comment.