Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ plugins {
id("kotlin-parcelize")
id("androidx.navigation.safeargs.kotlin")
id("com.itsaky.androidide.desugaring")
alias(libs.plugins.sentry)
alias(libs.plugins.google.services)
alias(libs.plugins.google.services)
}

fun propOrEnv(name: String): String =
Expand Down Expand Up @@ -79,12 +78,8 @@ android {
buildTypes {
debug {
signingConfig = signingConfigs.getByName("debug")
manifestPlaceholders["sentryDsn"] =
props.getProperty("sentryDsnDebug") ?: propOrEnv("SENTRY_DSN_DEBUG")
}
release {
manifestPlaceholders["sentryDsn"] =
props.getProperty("sentryDsnRelease") ?: propOrEnv("SENTRY_DSN_RELEASE")
}
}

Expand Down Expand Up @@ -147,10 +142,6 @@ android {
}
}

sentry {
includeProguardMapping = false
}

kapt { arguments { arg("eventBusIndex", "${BuildConfig.PACKAGE_NAME}.events.AppEventsIndex") } }

desugaring {
Expand Down Expand Up @@ -321,11 +312,6 @@ dependencies {
implementation(libs.koin.android)
implementation(libs.androidx.security.crypto)

// Sentry Android SDK (core + replay for quality configuration)
implementation(libs.sentry.core)
implementation(libs.sentry.android.core)
implementation(libs.sentry.android.replay)

// Firebase Analytics
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics)
Expand Down
43 changes: 0 additions & 43 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,49 +124,6 @@
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
android:windowSoftInputMode="adjustResize" />

<!-- Required: set your sentry.io project identifier (DSN) -->
<meta-data
android:name="io.sentry.dsn"
android:value="${sentryDsn}" />
<!-- enable automatic breadcrumbs for user interactions (clicks, swipes, scrolls) -->
<meta-data
android:name="io.sentry.traces.user-interaction.enable"
android:value="true" />
<!-- enable screenshot for crashes -->
<meta-data
android:name="io.sentry.attach-screenshot"
android:value="true" />
<!-- enable view hierarchy for crashes -->
<meta-data
android:name="io.sentry.attach-view-hierarchy"
android:value="true" />
<!-- enable the performance API by setting a sample-rate, adjust in production env -->
<meta-data
android:name="io.sentry.traces.sample-rate"
android:value="1.0" />
<!-- enable profiling when starting transactions, adjust in production env -->
<meta-data
android:name="io.sentry.traces.profiling.sample-rate"
android:value="1.0" />
<meta-data
android:name="io.sentry.send-default-pii"
android:value="true" />
<!-- Enable profiling on app start -->
<meta-data
android:name="io.sentry.traces.profiling.start-on-app-start"
android:value="true" />
<!-- Enable logs to be sent to Sentry -->
<meta-data
android:name="io.sentry.logs.enabled"
android:value="true" />
<!-- Record session replays for 100% of errors and 10% of sessions -->
<meta-data
android:name="io.sentry.session-replay.on-error-sample-rate"
android:value="1.0" />
<meta-data
android:name="io.sentry.session-replay.session-sample-rate"
android:value="0.1" />

<provider
android:name=".provider.IDEDocumentsProvider"
android:authorities="com.itsaky.androidide.documents"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ import com.itsaky.androidide.viewmodel.BuildVariantsViewModel
import com.itsaky.androidide.viewmodel.BuildViewModel
import io.github.rosemoe.sora.text.ICUUtils
import io.github.rosemoe.sora.util.IntPair
import io.sentry.Sentry
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -689,10 +688,6 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {

val isExpectedError = error.isFileNotFound

if (error != null && !isExpectedError) {
Sentry.captureException(error)
}

withContext(Dispatchers.Main) { postProjectInit(false, CACHE_READ_ERROR) }
return@launch
}
Expand Down Expand Up @@ -985,7 +980,6 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() {
throw e
}

Sentry.captureException(e)
logger.error("Unable to connect LSP servers with debug client", e)
listOf(DebugClientConnectionResult.Failure(cause = e))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.itsaky.androidide.utils.FeatureFlags
import com.itsaky.androidide.utils.FileUtil
import com.itsaky.androidide.utils.VMUtils
import com.itsaky.androidide.eventbus.events.plugin.PluginCrashedEvent
import io.sentry.Sentry
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
Expand Down Expand Up @@ -143,7 +142,6 @@ internal object CredentialProtectedApplicationLoader : ApplicationLoader {
WorkManager.getInstance(app)
}.onFailure { error ->
logger.error("Failed to get WorkManager instance after storage validation", error)
Sentry.captureException(error)
}
}

Expand All @@ -167,7 +165,6 @@ internal object CredentialProtectedApplicationLoader : ApplicationLoader {
}

writeException(exception)
Sentry.captureException(exception)

runCatching {
val intent = Intent()
Expand All @@ -179,7 +176,6 @@ internal object CredentialProtectedApplicationLoader : ApplicationLoader {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
IDEApplication.instance.startActivity(intent)
}.onFailure { error ->
Sentry.captureException(error)
logger.error("Unable to start crash handler activity", error)
}

Expand All @@ -192,12 +188,6 @@ internal object CredentialProtectedApplicationLoader : ApplicationLoader {
runCatching {
writeException(exception)

Sentry.withScope { scope ->
scope.setTag("plugin_crash", "true")
scope.setTag("plugin_id", pluginId)
Sentry.captureException(exception)
}

val pluginManager = PluginManager.getInstance() ?: return
val result = pluginManager.recordPluginCrash(pluginId)

Expand Down Expand Up @@ -338,7 +328,6 @@ internal object CredentialProtectedApplicationLoader : ApplicationLoader {
}
}
} catch (e: Exception) {
Sentry.captureException(e)
logger.error("Failed to initialize plugin system", e)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@ import com.itsaky.androidide.events.LspApiEventsIndex
import com.itsaky.androidide.events.LspJavaEventsIndex
import com.itsaky.androidide.events.ProjectsApiEventsIndex
import com.itsaky.androidide.handlers.CrashEventSubscriber
import com.itsaky.androidide.handlers.SentryDiagnosticsContext
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
import com.itsaky.androidide.ui.themes.IThemeManager
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.FeatureFlags
import com.termux.shared.reflection.ReflectionUtils
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme
import io.sentry.Sentry
import io.sentry.SentryReplayOptions.SentryReplayQuality
import io.sentry.android.core.SentryAndroid
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -67,18 +63,6 @@ internal object DeviceProtectedApplicationLoader :
),
)

SentryAndroid.init(app) { options ->
// Reduce replay quality to LOW to prevent OOM
// This reduces screenshot compression to 10 and bitrate to 50kbps
// (defaults to MEDIUM quality)
options.sessionReplay.quality = SentryReplayQuality.LOW
options.environment =
if (BuildConfig.DEBUG) IDEApplication.SENTRY_ENV_DEV else IDEApplication.SENTRY_ENV_PROD

// Enrich every Sentry event with app-specific diagnostic context.
SentryDiagnosticsContext.install(options)
}

ShizukuSettings.initialize()

EventBus
Expand Down Expand Up @@ -120,10 +104,6 @@ internal object DeviceProtectedApplicationLoader :
thread: Thread,
exception: Throwable,
) {
// we can't write logs to files, nor we can show the crash handler
// activity to the user. Just report to Sentry and exit.

Sentry.captureException(exception)
IDEApplication.instance.uncaughtExceptionHandler?.uncaughtException(thread, exception)
exitProcess(EXIT_CODE_CRASH)
}
Expand Down
11 changes: 0 additions & 11 deletions app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@ import androidx.work.Configuration
import com.itsaky.androidide.BuildConfig
import com.itsaky.androidide.di.coreModule
import com.itsaky.androidide.di.pluginModule
import com.itsaky.androidide.handlers.SentryDiagnosticsContext
import com.itsaky.androidide.plugins.manager.core.PluginManager
import com.itsaky.androidide.treesitter.TreeSitter
import com.itsaky.androidide.utils.RecyclableObjectPool
import com.itsaky.androidide.utils.VMUtils
import com.itsaky.androidide.utils.isAtLeastR
import com.itsaky.androidide.utils.isTestMode
import com.topjohnwu.superuser.Shell
import io.sentry.Sentry
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -85,9 +83,6 @@ class IDEApplication :
) {
if (intent?.action == Intent.ACTION_USER_UNLOCKED) {
runCatching { unregisterReceiver(this) }
// Stamp the unlock time so Sentry's boot_mode context reflects the
// live state and can report the direct-boot locked duration.
SentryDiagnosticsContext.onUserUnlocked()
coroutineScope.launch(Dispatchers.Default) {
logger.info("Device unlocked! Loading all components...")
CredentialProtectedApplicationLoader.load(this@IDEApplication)
Expand All @@ -99,9 +94,6 @@ class IDEApplication :
companion object {
private val logger = LoggerFactory.getLogger(IDEApplication::class.java)

const val SENTRY_ENV_DEV = "development"
const val SENTRY_ENV_PROD = "production"

@JvmStatic
@SuppressLint("StaticFieldLeak")
lateinit var instance: IDEApplication
Expand Down Expand Up @@ -131,7 +123,6 @@ class IDEApplication :

TreeSitter.loadLibrary()
} catch (e: UnsatisfiedLinkError) {
Sentry.captureException(e)
logger.warn("Failed to load native libraries", e)
}
}
Expand Down Expand Up @@ -180,7 +171,6 @@ class IDEApplication :
// In case any of the components fail to initialize there, it may lead
// to ANRs when the IDE is launched after device reboot.
// https://appdevforall.atlassian.net/browse/ADFA-2026
// https://appdevforall-inc-9p.sentry.io/issues/6860179170/events/7177c576e7b3491c9e9746c76f806d37/

ensureKoinStarted()

Expand Down Expand Up @@ -223,7 +213,6 @@ class IDEApplication :

if (isFinalizerWatchdogTimeout(thread, exception)) {
logger.warn("Non-fatal: FinalizerWatchdogDaemon timeout (suppressed crash)", exception)
Sentry.captureException(exception)
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ object AssetsInstallationHelper {
data class Failure(
val cause: Throwable?,
val errorMessage: String? = cause?.message,
val shouldReportToSentry: Boolean = true
) : Result
}

Expand Down Expand Up @@ -90,7 +89,7 @@ object AssetsInstallationHelper {
}
logger.error("Failed to install assets", e)
onProgress(Progress(msg))
return@withContext Result.Failure(cause, errorMessage = msg, shouldReportToSentry = !isMissingAsset)
return@withContext Result.Failure(cause, errorMessage = msg)
}

return@withContext Result.Success
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import com.itsaky.androidide.viewmodel.FilterState
import com.itsaky.androidide.viewmodel.RecentProjectsViewModel
import com.itsaky.androidide.viewmodel.SortCriteria
import com.itsaky.androidide.ui.ProjectInfoBottomSheet
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.debounce
Expand Down Expand Up @@ -324,8 +323,7 @@ class RecentProjectsFragment : BaseFragment() {
if (validProjects.isEmpty()) return@launch

loadProjectsIntoViewModel(validProjects)
} catch (e: Throwable) {
Sentry.captureException(e)
} catch (_: Throwable) {
}
Comment on lines +326 to 327

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Do not silently swallow exceptions with broad catch-alls.

When removing Sentry error reporting, the catch blocks were changed to silently swallow Exception or Throwable. As per coding guidelines and learnings, you must log handled exceptions, avoid broad catch-all blocks, and rethrow coroutine CancellationException where applicable.

  • app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt#L326-L327: Catch CancellationException and rethrow it, then catch Exception and log it using LoggerFactory, rather than silently swallowing Throwable.
  • app/src/main/java/com/itsaky/androidide/fragments/debug/DebuggerFragment.kt#L425-L426: Log the exception using LoggerFactory instead of silently ignoring it, and prefer catching narrower exceptions if known.
  • app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt#L406-L407: Catch ActivityNotFoundException specifically (since startActivity might fail if no browser is installed) and log it using logger, instead of using a silent catch-all.
📍 Affects 3 files
  • app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt#L326-L327 (this comment)
  • app/src/main/java/com/itsaky/androidide/fragments/debug/DebuggerFragment.kt#L425-L426
  • app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt#L406-L407
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt`
around lines 326 - 327, Replace the silent broad catch in
RecentProjectsFragment.kt lines 326-327 with CancellationException rethrowing
followed by an Exception catch logged through LoggerFactory; in
DebuggerFragment.kt lines 425-426, log the handled exception with LoggerFactory
and narrow the caught type where known; in PermissionsFragment.kt lines 406-407,
catch ActivityNotFoundException specifically and log it through the existing
logger instead of swallowing Throwable.

Sources: Coding guidelines, Learnings

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import com.itsaky.androidide.utils.viewLifecycleScope
import com.itsaky.androidide.viewmodel.DebuggerConnectionState
import com.itsaky.androidide.viewmodel.DebuggerViewModel
import com.itsaky.androidide.viewmodel.WADBConnectionViewModel
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
Expand Down Expand Up @@ -423,8 +422,7 @@ class ThreadSelectorListAdapter(
if (view.isAttachedToWindow) {
onItemLongClick.invoke(item, position, view)
}
} catch (e: Exception) {
Sentry.captureException(e)
} catch (_: Exception) {
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import com.itsaky.androidide.utils.isAtLeastR
import com.itsaky.androidide.utils.viewLifecycleScope
import com.itsaky.androidide.viewmodel.InstallationState
import com.itsaky.androidide.viewmodel.InstallationViewModel
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -404,8 +403,7 @@ class PermissionsFragment :
val privacyPolicyUrl = getString(R.string.privacy_policy_url)
val intent = Intent(Intent.ACTION_VIEW, privacyPolicyUrl.toUri())
startActivity(intent)
} catch (e: Exception) {
Sentry.captureException(e)
} catch (_: Exception) {
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.blankj.utilcode.util.ActivityUtils.startActivity
import com.blankj.utilcode.util.ThrowableUtils.getFullStackTrace
import com.itsaky.androidide.activities.CrashHandlerActivity
import com.itsaky.androidide.eventbus.events.editor.ReportCaughtExceptionEvent
import io.sentry.Sentry
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.slf4j.LoggerFactory
Expand All @@ -19,25 +18,15 @@ class CrashEventSubscriber {
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onReportCaughtException(ev: ReportCaughtExceptionEvent) {
try {
Sentry.configureScope { scope ->
ev.extras.forEach { (k, v) -> scope.setTag(k, v) }
ev.message?.let { scope.setExtra("message", it) }
}
Sentry.captureException(ev.throwable)
val intent = Intent()
intent.action = CrashHandlerActivity.REPORT_ACTION
intent.putExtra(CrashHandlerActivity.TRACE_KEY, getFullStackTrace(ev.throwable))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)

try {
val intent = Intent()
intent.action = CrashHandlerActivity.REPORT_ACTION
intent.putExtra(CrashHandlerActivity.TRACE_KEY, getFullStackTrace(ev.throwable))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)

exitProcess(EXIT_CODE_CRASH)
} catch (error: Throwable) {
log.error("Unable to show crash handler activity", error)
}
} catch (t: Throwable) {
log.error("Failed to forward exception to Sentry", t)
exitProcess(EXIT_CODE_CRASH)
} catch (error: Throwable) {
log.error("Unable to show crash handler activity", error)
}
}
}
}
Loading
Loading