-
Notifications
You must be signed in to change notification settings - Fork 0
Add consent-gated Android Sentry runtime init #96
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
Changes from all commits
db8b439
ac01ae0
202d5f8
b7b4ded
13c7a4d
7ca0aee
d75197a
7600411
4ca4f22
45febdd
ca64f54
c15d1e5
9da61c1
97fc618
70799b6
eff75c5
848cd56
17374c6
9d3edad
130ac5e
ee3bf68
31b24db
6069453
45fea54
d2c5330
d937b63
146723c
84b4100
0a80490
444a8f9
b554c71
7d5b77f
9f3968b
2659ee0
3266113
d7c11e7
67d5ddc
2b14c87
b1550ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| package social.cloudhub.charm | ||
|
|
||
| import android.app.Application | ||
| import android.os.FileObserver | ||
| import io.sentry.SentryOptions.BeforeBreadcrumbCallback | ||
| import io.sentry.SentryOptions.BeforeSendCallback | ||
| import io.sentry.android.core.SentryAndroid | ||
| import org.json.JSONObject | ||
| import java.io.File | ||
|
|
||
| class CharmApplication : Application() { | ||
| @Volatile | ||
| private var sentryConsentEnabled: Boolean = false | ||
| private val sentryConsentObservers = mutableListOf<FileObserver>() | ||
| private val observabilityStoreFilename = "observability.json" | ||
|
|
||
| override fun onCreate() { | ||
| super.onCreate() | ||
| initializeSentryIfConsented() | ||
| } | ||
|
|
||
| private fun initializeSentryIfConsented() { | ||
| val dsn = BuildConfig.SENTRY_DSN.takeIf { it.isNotBlank() } ?: return | ||
| sentryConsentEnabled = readSentryEnabledFromStore() | ||
| if (!sentryConsentEnabled) return | ||
|
Copilot marked this conversation as resolved.
|
||
| startSentryConsentObservers() | ||
|
|
||
| SentryAndroid.init(this) { options -> | ||
| options.setDsn(dsn) | ||
| BuildConfig.SENTRY_ENVIRONMENT.takeIf { it.isNotBlank() }?.let { options.setEnvironment(it) } | ||
| BuildConfig.SENTRY_RELEASE.takeIf { it.isNotBlank() }?.let { options.setRelease(it) } | ||
| options.setSendDefaultPii(false) | ||
| options.setSendClientReports(false) | ||
| options.setEnableNdk(false) | ||
| // Leave tracesSampleRate unset; 0.0 still enables tracing instrumentation overhead. | ||
| options.setEnableAutoSessionTracking(false) | ||
| options.setBeforeBreadcrumb(BeforeBreadcrumbCallback { breadcrumb, _ -> | ||
| if (sentryConsentEnabled) breadcrumb else null | ||
| }) | ||
| options.setBeforeSend(BeforeSendCallback { event, _ -> | ||
| if (sentryConsentEnabled) event else null | ||
| }) | ||
|
Just-Insane marked this conversation as resolved.
Just-Insane marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| private fun startSentryConsentObservers() { | ||
| val mask = FileObserver.CLOSE_WRITE or | ||
| FileObserver.DELETE or | ||
| FileObserver.MOVED_FROM or | ||
| FileObserver.MOVED_TO | ||
|
Copilot marked this conversation as resolved.
|
||
| listOf(File(applicationInfo.dataDir), filesDir) | ||
| .distinctBy { it.absolutePath } | ||
| .forEach { directory -> | ||
| @Suppress("DEPRECATION") | ||
| val observer = object : FileObserver(directory.absolutePath, mask) { | ||
| override fun onEvent(event: Int, path: String?) { | ||
| if (path == null) { | ||
| sentryConsentEnabled = false | ||
| return | ||
|
Comment on lines
+57
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because this observer is attached to the whole app-data/filesDir directory, Android's documented null-path callbacks for the watched directory itself are not evidence that Useful? React with 👍 / 👎. |
||
| } | ||
| if (isObservabilityStoreEvent(path)) { | ||
| sentryConsentEnabled = readSentryEnabledFromStore() | ||
| } | ||
| } | ||
| } | ||
| observer.startWatching() | ||
| sentryConsentObservers.add(observer) | ||
| } | ||
| } | ||
|
|
||
| private fun isObservabilityStoreEvent(path: String): Boolean = | ||
| path == observabilityStoreFilename || | ||
| path.endsWith("/$observabilityStoreFilename") | ||
|
|
||
| private fun readSentryEnabledFromStore(): Boolean { | ||
| val appDataFile = File(applicationInfo.dataDir, observabilityStoreFilename) | ||
| val file = if (appDataFile.isFile) { | ||
| appDataFile | ||
| } else { | ||
| File(filesDir, observabilityStoreFilename).takeIf { it.isFile } | ||
| } | ||
| if (file == null) return false | ||
|
|
||
| return runCatching { | ||
| val root = JSONObject(file.readText(Charsets.UTF_8)) | ||
| val state = root.optJSONObject("observability")?.optJSONObject("state") | ||
| ?: root.optJSONObject("state") | ||
| ?: root.optJSONObject("observability") | ||
|
|
||
| state?.optBoolean("sentryEnabled", false) == true | ||
| }.getOrDefault(false) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.