Last active
March 11, 2025 12:34
-
-
Save kibotu/dbc2b6321816652169cc21786b4c2397 to your computer and use it in GitHub Desktop.
CoreStrictService
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class App : Application() { | |
var strictMode: CoreStrictMode? = null | |
val applicationScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) | |
} | |
class StrictModeInitializer : Initializer<Unit> { | |
private val corePreferences: CoreSharedPreferencesRepository | |
get() = CoreServices.services.sharedPreferencesRepository | |
override fun create(context: Context) { | |
if (!R.bool.core_enable_development_mode.resBoolean) return | |
var strictMode = c24CoreApplication?.strictMode | |
if (strictMode == null) { | |
strictMode = CoreStrictMode() | |
c24CoreApplication?.strictMode = strictMode | |
} | |
strictMode.start() | |
// Start listening to violations immediately | |
c24CoreApplication?.applicationScope?.launch(Dispatchers.IO) { | |
strictMode | |
.strictModeViolations | |
.filter { it.stackTrace.contains("my.bundle.identifier", ignoreCase = true) } | |
.collect { violation -> | |
corePreferences.addLatestStrictModeViolation(violation) | |
if (violation.stackTrace !in CoreStrictMode.sentryBlackList) { | |
val throwable = violation.stackTrace.parseStackTrace { message, stacktrace -> | |
StrictModeViolation(message.ifEmpty { violation.description }, stacktrace) | |
} | |
if (throwable != null) { | |
Logger.w("StrictMode", throwable = throwable) | |
} else { | |
Logger.w(message = violation.stackTrace) | |
} | |
} | |
} | |
} | |
} | |
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList() | |
} | |
class CoreStrictService { | |
@Suppress("KotlinConstantConditions") | |
var enableStrictMode: Boolean | |
get() = application | |
?.getSharedPreferences("strict-mode", MODE_PRIVATE) | |
?.getBoolean("enable-strict-mode", R.bool.core_enable_development_mode.resBoolean) == true | |
set(value) { | |
application | |
?.getSharedPreferences("strict-mode", MODE_PRIVATE) | |
?.edit { | |
putBoolean("enable-strict-mode", value) | |
} | |
toggleStrictMode(value) | |
} | |
val strictModeViolations: SharedFlow<StrictModeViolation> | |
field = MutableSharedFlow<StrictModeViolation>(replay = 100) | |
private val isDevelopment by lazy { R.bool.core_enable_development_mode.resBoolean } | |
fun start() { | |
toggleStrictMode(enableStrictMode) | |
} | |
private fun toggleStrictMode(enable: Boolean) { | |
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return | |
if (!isDevelopment) return | |
if (enable) { | |
enableStrictMode() | |
} else { | |
disableStrictMode() | |
} | |
} | |
@RequiresApi(Build.VERSION_CODES.P) | |
private fun enableStrictMode() { | |
configureStrictModeThreadPolicy() | |
CoreLogger.d(TAG, "StrictMode enabled") | |
configureStrictModeVmPolicy() | |
} | |
private fun disableStrictMode() { | |
// Ensure policy is cleared on the main thread | |
Handler(Looper.getMainLooper()).post { | |
StrictMode.setThreadPolicy( | |
StrictMode.ThreadPolicy.Builder() | |
.permitAll() // Allow all operations | |
.build() | |
) | |
CoreLogger.d(TAG, "StrictMode disabled") | |
} | |
StrictMode.setVmPolicy( | |
StrictMode.VmPolicy.Builder() | |
.build() // No VM checks | |
) | |
} | |
@RequiresApi(Build.VERSION_CODES.P) | |
private fun configureStrictModeThreadPolicy() { | |
val context = c24CoreApplication?.applicationContext ?: return | |
// Configure thread policy for the main thread only | |
Handler(Looper.getMainLooper()).post { | |
StrictMode.setThreadPolicy( | |
StrictMode.ThreadPolicy.Builder().apply { | |
// ANR potential | |
detectCustomSlowCalls() | |
// io | |
detectNetwork() | |
detectDiskReads() | |
detectDiskWrites() | |
detectUnbufferedIo() | |
detectResourceMismatches() | |
// visual feedback during violation | |
penaltyFlashScreen() | |
penaltyListener(context.mainExecutor) { violation -> | |
c24CoreApplication?.applicationScope?.launch { | |
strictModeViolations.emit( | |
StrictModeViolation( | |
type = "Thread Policy Violation", | |
timestamp = now, | |
stackTrace = violation.stackTraceToString() | |
) | |
) | |
} | |
} | |
}.build() | |
) | |
} | |
} | |
@RequiresApi(Build.VERSION_CODES.P) | |
private fun configureStrictModeVmPolicy() { | |
StrictMode.setVmPolicy( | |
StrictMode.VmPolicy.Builder().apply { | |
// leaks | |
detectLeakedClosableObjects() | |
detectLeakedRegistrationObjects() | |
detectActivityLeaks() | |
detectLeakedSqlLiteObjects() | |
// permissions | |
detectContentUriWithoutPermission() | |
// context | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | |
detectIncorrectContextUse() | |
detectUnsafeIntentLaunch() | |
} | |
val context = c24CoreApplication ?: return | |
penaltyListener(context.mainExecutor) { violation -> | |
context.applicationScope.launch { | |
strictModeViolations.emit( | |
StrictModeViolation( | |
type = "VM Policy Violation", | |
timestamp = now, | |
stackTrace = violation.stackTraceToString() | |
) | |
) | |
} | |
} | |
}.build() | |
) | |
} | |
companion object { | |
val sentryBlackList by lazy { | |
listOf( | |
"DiskReadViolation" | |
) | |
} | |
val knownViolations by lazy { | |
listOf( | |
"DiskReadViolation", | |
"LeakedClosableViolation" | |
) | |
} | |
} | |
} | |
@Serializable | |
data class StrictModeViolation( | |
/** | |
* "Thread Policy Violation", "VM Policy Violation", "ANR Detected" | |
*/ | |
val type: String, | |
@Contextual val timestamp: Instant, | |
val stackTrace: String | |
) | |
val StrictModeViolation.title: String | |
get() { | |
val firstLine = stackTrace.lineSequence().firstOrNull()?.trim() ?: return "" | |
val colonIndex = firstLine.indexOf(":") | |
return if (colonIndex != -1) { | |
firstLine.substring(0, colonIndex).substringAfterLast(".") | |
} else { | |
"^.*\\.(\\w+)$".toRegex().find(firstLine)?.groupValues?.get(1) ?: "" | |
} | |
} | |
val StrictModeViolation.description: String | |
get() { | |
val firstLine = stackTrace.lineSequence().firstOrNull()?.trim() ?: return "" | |
val colonIndex = firstLine.indexOf(":") | |
var description = if (colonIndex != -1) { | |
firstLine.substring(colonIndex + 1).trim() | |
} else { | |
"" | |
} | |
if (description.isEmpty()) { | |
description = stackTrace | |
.lineSequence() | |
.filter { it.contains("de.check24.profis.partner.") } | |
.map { | |
it.trim() | |
.replace("at ", "") | |
.replace("de.check24.profis.partner.", "") | |
} | |
.firstOrNull() | |
.orEmpty() | |
} | |
return description | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment