Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to opt out of Analytics and Crashlytics #1237

Merged
merged 16 commits into from
Oct 12, 2024
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.SecurityPreferences
import kotlinx.coroutines.CoroutineScope

object Firebase {
fun setup(context: Context, preference: SecurityPreferences, 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,15 +36,21 @@ 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.SecurityPreferences
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.injectLazy

internal class PermissionStep : OnboardingStep {

private val securityPreferences: SecurityPreferences by injectLazy()

private var notificationGranted by mutableStateOf(false)
private var batteryGranted by mutableStateOf(false)
private var allowCrashLogs by mutableStateOf(true)
private var allowAnalytics by mutableStateOf(true)
Animeboynz marked this conversation as resolved.
Show resolved Hide resolved

override val isComplete: Boolean = true

Expand Down Expand Up @@ -73,7 +81,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 +97,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 +117,31 @@ internal class PermissionStep : OnboardingStep {
context.startActivity(intent)
},
)

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

PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
granted = allowCrashLogs,
onToggleChange = {
allowCrashLogs = it
securityPreferences.crashlytics().set(allowCrashLogs)
},
)

PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
granted = allowAnalytics,
onToggleChange = {
allowAnalytics = it
securityPreferences.analytics().set(allowAnalytics)
},
)
Animeboynz marked this conversation as resolved.
Show resolved Hide resolved
}
}

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

@Composable
private fun PermissionItem(
private fun PermissionCheckbox(
title: String,
subtitle: String,
granted: Boolean,
Expand Down Expand Up @@ -157,4 +190,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),
)
Animeboynz marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,26 @@ object SettingsSecurityScreen : SearchableSettings {

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

val useAuthPref = securityPreferences.useAuthenticator()
val useAuth by useAuthPref.collectAsState()
@Composable
private fun getSecurityGroup(
securityPreferences: SecurityPreferences,
): Preference.PreferenceGroup {
Animeboynz marked this conversation as resolved.
Show resolved Hide resolved
val context = LocalContext.current
val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator()
val useAuth by useAuthPref.collectAsState()

return listOf(
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_security),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = useAuthPref,
title = stringResource(MR.strings.lock_with_biometrics),
Expand Down Expand Up @@ -65,6 +77,7 @@ object SettingsSecurityScreen : SearchableSettings {
)
},
),

Preference.PreferenceItem.SwitchPreference(
pref = securityPreferences.hideNotificationContent(),
title = stringResource(MR.strings.hide_notification_content),
Expand All @@ -77,8 +90,30 @@ object SettingsSecurityScreen : SearchableSettings {
.toImmutableMap(),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
)
}
),
)
}

@Composable
private fun getFirebaseGroup(
securityPreferences: SecurityPreferences,
): Preference.PreferenceGroup {
Animeboynz marked this conversation as resolved.
Show resolved Hide resolved
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_firebase),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = securityPreferences.crashlytics(),
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
),
Preference.PreferenceItem.SwitchPreference(
pref = securityPreferences.analytics(),
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.firebase_summary)),
),
)
}

private val LockAfterValues = persistentListOf(
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.SecurityPreferences
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 securityPreferences: SecurityPreferences 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, securityPreferences, ProcessLifecycleOwner.get().lifecycleScope)

setupNotificationChannels()

ProcessLifecycleOwner.get().lifecycle.addObserver(this)
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.SecurityPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

object Firebase {
fun setup(context: Context, preference: SecurityPreferences, 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
Expand Up @@ -18,6 +18,10 @@ class SecurityPreferences(

fun hideNotificationContent() = preferenceStore.getBoolean("hide_notification_content", false)

fun crashlytics() = preferenceStore.getBoolean("crashlytics", false)

fun analytics() = preferenceStore.getBoolean("analytics", false)
Animeboynz marked this conversation as resolved.
Show resolved Hide resolved

/**
* For app lock. Will be set when there is a pending timed lock.
* Otherwise this pref should be deleted.
Expand Down
8 changes: 8 additions & 0 deletions i18n/src/commonMain/moko-resources/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@
<string name="onboarding_permission_notifications_description">Get notified for library updates and more.</string>
<string name="onboarding_permission_ignore_battery_opts">Background battery usage</string>
<string name="onboarding_permission_ignore_battery_opts_description">Avoid interruptions to long-running library updates, downloads, and backup restores.</string>
<string name="onboarding_permission_crashlytics">Send crash logs</string>
<string name="onboarding_permission_crashlytics_description">Send anonymised crash logs to the developers.</string>
Animeboynz marked this conversation as resolved.
Show resolved Hide resolved
<string name="onboarding_permission_analytics">Allow analytics</string>
<string name="onboarding_permission_analytics_description">Send anonymized usage data to improve app features.</string>
<string name="onboarding_permission_action_grant">Grant</string>
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
<string name="onboarding_guides_returning_user">Reinstalling %s?</string>
Expand Down Expand Up @@ -242,13 +246,17 @@
<string name="pref_app_language">App language</string>

<string name="pref_category_security">Security and privacy</string>
<string name="pref_security">Security</string>
<string name="pref_firebase">Analytics and Crash logs</string>

<string name="lock_with_biometrics">Require unlock</string>
<string name="lock_when_idle">Lock when idle</string>
<string name="lock_always">Always</string>
<string name="lock_never">Never</string>
<string name="hide_notification_content">Hide notification content</string>
<string name="secure_screen">Secure screen</string>
<string name="secure_screen_summary">Secure screen hides app contents when switching apps and block screenshots</string>
<string name="firebase_summary">Sending crash logs and analytics will allow us to identify and fix issues, improve performance, and make future updates more relevant to your needs</string>

<string name="pref_category_nsfw_content">NSFW (18+) sources</string>
<string name="pref_show_nsfw_source">Show in sources and extensions lists</string>
Expand Down