diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 708b0c3c21..4eb1528111 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 = @@ -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") } } @@ -147,10 +142,6 @@ android { } } -sentry { - includeProguardMapping = false -} - kapt { arguments { arg("eventBusIndex", "${BuildConfig.PACKAGE_NAME}.events.AppEventsIndex") } } desugaring { @@ -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) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1aa4f452d0..ebb87bb8cb 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -124,49 +124,6 @@ android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" android:windowSoftInputMode="adjustResize" /> - - - - - - - - - - - - - - - - - - - - - logger.error("Failed to get WorkManager instance after storage validation", error) - Sentry.captureException(error) } } @@ -167,7 +165,6 @@ internal object CredentialProtectedApplicationLoader : ApplicationLoader { } writeException(exception) - Sentry.captureException(exception) runCatching { val intent = Intent() @@ -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) } @@ -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) @@ -338,7 +328,6 @@ internal object CredentialProtectedApplicationLoader : ApplicationLoader { } } } catch (e: Exception) { - Sentry.captureException(e) logger.error("Failed to initialize plugin system", e) } } diff --git a/app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt b/app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt index deb40e1786..7629ba41b2 100644 --- a/app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt +++ b/app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt @@ -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 @@ -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 @@ -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) } diff --git a/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt b/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt index b673341529..1c86f6f8eb 100755 --- a/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt +++ b/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt @@ -29,7 +29,6 @@ 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 @@ -37,7 +36,6 @@ 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 @@ -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) @@ -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 @@ -131,7 +123,6 @@ class IDEApplication : TreeSitter.loadLibrary() } catch (e: UnsatisfiedLinkError) { - Sentry.captureException(e) logger.warn("Failed to load native libraries", e) } } @@ -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() @@ -223,7 +213,6 @@ class IDEApplication : if (isFinalizerWatchdogTimeout(thread, exception)) { logger.warn("Non-fatal: FinalizerWatchdogDaemon timeout (suppressed crash)", exception) - Sentry.captureException(exception) return } diff --git a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt index 3efc221669..77e1fa5565 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt @@ -52,7 +52,6 @@ object AssetsInstallationHelper { data class Failure( val cause: Throwable?, val errorMessage: String? = cause?.message, - val shouldReportToSentry: Boolean = true ) : Result } @@ -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 diff --git a/app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt index 129fa28462..a64b9a58d1 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/RecentProjectsFragment.kt @@ -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 @@ -324,8 +323,7 @@ class RecentProjectsFragment : BaseFragment() { if (validProjects.isEmpty()) return@launch loadProjectsIntoViewModel(validProjects) - } catch (e: Throwable) { - Sentry.captureException(e) + } catch (_: Throwable) { } } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/debug/DebuggerFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/debug/DebuggerFragment.kt index 4df5333c01..8e7c037180 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/debug/DebuggerFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/debug/DebuggerFragment.kt @@ -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 @@ -423,8 +422,7 @@ class ThreadSelectorListAdapter( if (view.isAttachedToWindow) { onItemLongClick.invoke(item, position, view) } - } catch (e: Exception) { - Sentry.captureException(e) + } catch (_: Exception) { } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt index 750b6ac2fb..9ed7d6a911 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt @@ -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 @@ -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) { } } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/CrashEventSubscriber.kt b/app/src/main/java/com/itsaky/androidide/handlers/CrashEventSubscriber.kt index 158ebf4f93..0343554d7c 100644 --- a/app/src/main/java/com/itsaky/androidide/handlers/CrashEventSubscriber.kt +++ b/app/src/main/java/com/itsaky/androidide/handlers/CrashEventSubscriber.kt @@ -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 @@ -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) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/itsaky/androidide/handlers/SentryDiagnosticsContext.kt b/app/src/main/java/com/itsaky/androidide/handlers/SentryDiagnosticsContext.kt deleted file mode 100644 index 640b797624..0000000000 --- a/app/src/main/java/com/itsaky/androidide/handlers/SentryDiagnosticsContext.kt +++ /dev/null @@ -1,203 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - * - */ - -package com.itsaky.androidide.handlers - -import android.content.pm.ApplicationInfo -import android.os.SystemClock -import com.blankj.utilcode.util.AppUtils -import com.blankj.utilcode.util.DeviceUtils -import com.itsaky.androidide.app.IDEApplication -import com.itsaky.androidide.app.configuration.IDEBuildConfigProvider -import com.itsaky.androidide.buildinfo.BuildInfo -import com.itsaky.androidide.plugins.manager.core.PluginManager -import com.itsaky.androidide.utils.BuildInfoUtils -import com.termux.shared.android.PackageUtils -import com.termux.shared.android.SELinuxUtils -import io.sentry.EventProcessor -import io.sentry.Hint -import io.sentry.SentryEvent -import io.sentry.SentryOptions -import org.slf4j.LoggerFactory - -/** - * Enriches every Sentry event with app-specific diagnostic context (SELinux - * labels, boot mode, install location, signing certificate, active plugins, - * release/ABI/device posture). - * - * The context is attached through a single [EventProcessor] registered in - * [io.sentry.android.core.SentryAndroid.init]. Running at capture time means a - * single processor sees the live boot state and the currently loaded plugins - * for fatal crashes, caught exceptions and plugin crashes alike, with no - * per-call-site wiring. - * - * Every field is collected inside its own [runCatching] so that a single - * failing collector (e.g. an SELinux read denied by policy, or credential - * protected storage being inaccessible in direct boot) drops only that one - * field — the event is still reported with everything else intact. - * - * No source code or project file paths are ever read. The SELinux file-context - * collector keeps only the returned security label, not the directory path. - * - * @author Hal Eisen - */ -object SentryDiagnosticsContext { - - private val log = LoggerFactory.getLogger(SentryDiagnosticsContext::class.java) - - /** Process/boot start stamp, used to compute the direct-boot locked duration. */ - private val bootElapsedStartMs = SystemClock.elapsedRealtime() - - /** Elapsed-time stamp of when the user credential-unlocked the device, if observed. */ - @Volatile - private var userUnlockedElapsedMs: Long? = null - - /** Whether the app process started while the device was still credential-locked. */ - @Volatile - private var startedInDirectBoot = false - - // --- Static per-process values, computed once and cached lazily. --- - - private val appInfo: ApplicationInfo? by lazy { - val app = IDEApplication.instance - PackageUtils.getApplicationInfoForPackage(app, app.packageName) - } - - private val seInfo: String? by lazy { - appInfo?.let { PackageUtils.getApplicationInfoSeInfoForPackage(it) } - } - - private val signingDigest: String? by lazy { - val app = IDEApplication.instance - PackageUtils.getSigningCertificateSHA256DigestForPackage(app, app.packageName) - } - - private val installLocation: String? by lazy { - appInfo?.let { - if (PackageUtils.isAppInstalledOnExternalStorage(it)) "external" else "internal" - } - } - - /** - * Registers the diagnostics [EventProcessor] on the given Sentry [options]. - * Call once from within `SentryAndroid.init`. - */ - fun install(options: SentryOptions) { - // Capture whether we started locked at install time; install() runs from - // DeviceProtectedApplicationLoader, which is reachable in direct boot. - startedInDirectBoot = runCatching { !IDEApplication.instance.isUserUnlocked }.getOrDefault(false) - - options.addEventProcessor(object : EventProcessor { - override fun process(event: SentryEvent, hint: Hint): SentryEvent { - runCatching { enrich(event) }.onFailure { log.warn("Failed to enrich Sentry event", it) } - return event - } - }) - } - - /** - * Stamps the moment the device transitioned to credential-unlocked, so the - * direct-boot locked duration can be reported. Idempotent — only the first - * unlock is recorded. - */ - fun onUserUnlocked() { - if (userUnlockedElapsedMs == null) { - userUnlockedElapsedMs = SystemClock.elapsedRealtime() - } - } - - private fun enrich(event: SentryEvent) { - val app = runCatching { IDEApplication.instance }.getOrNull() ?: return - - // ① SELinux contexts (process, private-data-dir file label, seinfo). - context(event, "selinux") { - buildMap { - runCatching { SELinuxUtils.getContext() }.getOrNull()?.let { put("process_context", it) } - runCatching { SELinuxUtils.getFileContext(app.filesDir.absolutePath) } - .getOrNull()?.let { put("file_context", it) } - runCatching { seInfo }.getOrNull()?.let { put("seinfo", it) } - } - } - - // ② Boot mode, queried live, plus the locked duration if we started locked. - tag(event, "boot_mode") { - if (app.isUserUnlocked) "credential_unlocked" else "direct_boot" - } - if (startedInDirectBoot) { - tag(event, "boot_locked_duration_ms") { - val end = userUnlockedElapsedMs ?: SystemClock.elapsedRealtime() - (end - bootElapsedStartMs).toString() - } - } - - // ③ Install location (internal vs external/SD). - tag(event, "install_location") { installLocation } - - // ④ Signing certificate digest + official/unofficial build flag. - tag(event, "signing_sha256") { signingDigest } - tag(event, "signing_official") { BuildInfoUtils.isOfficialBuild(app).toString() } - - // ⑤ Active plugins (enabled + loaded) with version and recent crash count. - context(event, "active_plugins") { - val pm = PluginManager.getInstance() ?: return@context null - val plugins = pm.getAllPlugins() - .filter { it.isEnabled && it.isLoaded } - .map { info -> - mapOf( - "id" to info.metadata.id, - "version" to info.metadata.version, - "min_ide_version" to info.metadata.minIdeVersion, - "crash_count" to runCatching { - pm.crashTracker.getCrashCount(info.metadata.id) - }.getOrDefault(0), - ) - } - mapOf("count" to plugins.size, "plugins" to plugins) - } - - // A — release identifier + version code. - tag(event, "app_version_name") { BuildInfo.VERSION_NAME_SIMPLE } - tag(event, "app_version_code") { AppUtils.getAppVersionCode().toString() } - tag(event, "app_git_commit") { BuildInfo.CI_GIT_COMMIT_HASH } - tag(event, "app_ci_build") { BuildInfo.CI_BUILD.toString() } - - // B — process ABI / variant. - val buildConfig = IDEBuildConfigProvider.getInstance() - tag(event, "abi") { buildConfig.cpuAbiName } - tag(event, "cpu_arch") { buildConfig.cpuArch.name } - - // H — device posture (emulator vs physical, rooted). - tag(event, "device_emulator") { DeviceUtils.isEmulator().toString() } - tag(event, "device_rooted") { DeviceUtils.isDeviceRooted().toString() } - } - - /** Sets a single tag, guarded so a failing collector drops only that tag. */ - private inline fun tag(event: SentryEvent, key: String, value: () -> String?) { - runCatching { value()?.let { event.setTag(key, it) } } - .onFailure { log.debug("Sentry diagnostics: dropped tag '{}'", key, it) } - } - - /** - * Attaches a structured context group, guarded so a failing collector drops - * only that group. Empty/null maps are skipped. - */ - private inline fun context(event: SentryEvent, key: String, value: () -> Map?) { - runCatching { value()?.takeIf { it.isNotEmpty() }?.let { event.contexts.put(key, it) } } - .onFailure { log.debug("Sentry diagnostics: dropped context '{}'", key, it) } - } -} diff --git a/app/src/main/java/com/itsaky/androidide/repositories/BreakpointRepository.kt b/app/src/main/java/com/itsaky/androidide/repositories/BreakpointRepository.kt index e361d9f799..82a3d96819 100644 --- a/app/src/main/java/com/itsaky/androidide/repositories/BreakpointRepository.kt +++ b/app/src/main/java/com/itsaky/androidide/repositories/BreakpointRepository.kt @@ -9,7 +9,6 @@ import com.itsaky.androidide.lsp.debug.model.PositionalBreakpoint import com.itsaky.androidide.lsp.debug.model.MethodBreakpoint import com.itsaky.androidide.tooling.api.util.RuntimeTypeAdapterFactory import com.itsaky.androidide.utils.Environment -import io.sentry.Sentry import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File @@ -105,7 +104,6 @@ object BreakpointRepository { "Failed to save breakpoints to file: ${file.absolutePath}", e ) - Sentry.captureException(e) } } } diff --git a/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt b/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt index 06f2e0d0b8..dba78c4d31 100644 --- a/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt +++ b/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt @@ -71,7 +71,6 @@ import com.itsaky.androidide.tooling.events.ProgressEvent import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.FeatureFlags import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment -import io.sentry.Sentry import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -284,7 +283,6 @@ class GradleBuildService : } else { // log if the error is not due to the stream being closed log.error("Failed to shutdown Tooling API server", err) - Sentry.captureException(err) } } } @@ -388,7 +386,6 @@ class GradleBuildService : } }.onFailure { err -> log.error("Failed to auto-tune Gradle build", err) - Sentry.captureException(err) }.getOrDefault(null) } else { null diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt index 2da418b61e..1acf8ab2c3 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt @@ -20,7 +20,6 @@ import com.itsaky.androidide.viewmodel.InstallationState.InstallationError import com.itsaky.androidide.viewmodel.InstallationState.InstallationGranted import com.itsaky.androidide.viewmodel.InstallationState.InstallationPending import com.itsaky.androidide.viewmodel.InstallationState.Installing -import io.sentry.Sentry import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -91,9 +90,6 @@ class InstallationViewModel : ViewModel() { _state.update { InstallationComplete } } is AssetsInstallationHelper.Result.Failure -> { - if (result.shouldReportToSentry) { - result.cause?.let { Sentry.captureException(it) } - } val errorMsg = result.errorMessage ?: context.getString(R.string.title_installation_failed) _events.emit(InstallationEvent.ShowError(errorMsg)) @@ -108,7 +104,6 @@ class InstallationViewModel : ViewModel() { _state.update { InstallationPending } throw e } - Sentry.captureException(e) log.error("IDE setup installation failed", e) val errorMsg = e.message ?: context.getString(R.string.unknown_error) _events.emit(InstallationEvent.ShowError(errorMsg)) diff --git a/app/src/test/java/com/itsaky/androidide/assets/AssetsInstallationHelperTest.kt b/app/src/test/java/com/itsaky/androidide/assets/AssetsInstallationHelperTest.kt index 81660f0ca9..035cd2194d 100644 --- a/app/src/test/java/com/itsaky/androidide/assets/AssetsInstallationHelperTest.kt +++ b/app/src/test/java/com/itsaky/androidide/assets/AssetsInstallationHelperTest.kt @@ -7,7 +7,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -23,7 +22,7 @@ class AssetsInstallationHelperTest { } @Test - fun `install with missing asset skips sentry`() = runBlocking { + fun `install with missing asset wraps in MissingAssetsEntryException`() = runBlocking { val helper = AssetsInstallationHelper every { @@ -38,7 +37,6 @@ class AssetsInstallationHelperTest { assertTrue("Expected Result.Failure", result is Failure) val failure = result as Failure - assertFalse("Should skip Sentry report", failure.shouldReportToSentry) assertTrue( "Expected MissingAssetsEntryException as cause", failure.cause is MissingAssetsEntryException diff --git a/app/src/test/java/com/itsaky/androidide/handlers/SentryDiagnosticsContextTest.kt b/app/src/test/java/com/itsaky/androidide/handlers/SentryDiagnosticsContextTest.kt deleted file mode 100644 index fc590e6589..0000000000 --- a/app/src/test/java/com/itsaky/androidide/handlers/SentryDiagnosticsContextTest.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.androidide.handlers - -import com.google.common.truth.Truth.assertThat -import com.itsaky.androidide.app.IDEApplication -import com.itsaky.androidide.buildinfo.BuildInfo -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.unmockkAll -import io.sentry.Hint -import io.sentry.SentryEvent -import io.sentry.SentryOptions -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.File - -/** - * Verifies that [SentryDiagnosticsContext] enriches events and, crucially, that - * a single failing field collector never prevents the rest of the event from - * being reported. - * - * @author Hal Eisen - */ -@RunWith(RobolectricTestRunner::class) -class SentryDiagnosticsContextTest { - - private lateinit var app: IDEApplication - - @Before - fun setUp() { - // Skip the native-library block in IDEApplication's static initializer. - System.setProperty("androidide.test.mode", "true") - - app = mockk(relaxed = true) - every { app.packageName } returns "com.itsaky.androidide" - every { app.filesDir } returns File("/tmp/androidide-test") - every { app.isUserUnlocked } returns true - - mockkObject(IDEApplication.Companion) - every { IDEApplication.instance } returns app - } - - @After - fun tearDown() { - unmockkAll() - } - - /** Installs the processor on a fresh options instance and enriches a new event. */ - private fun enrichNewEvent(): SentryEvent { - val options = SentryOptions() - SentryDiagnosticsContext.install(options) - val processor = options.eventProcessors - .first { it.javaClass.name.contains("SentryDiagnosticsContext") } - return processor.process(SentryEvent(), Hint())!! - } - - @Test - fun `enrich populates diagnostic context on the event`() { - val event = enrichNewEvent() - - // Live boot state and a compile-time release constant are both attached. - assertThat(event.getTag("boot_mode")).isEqualTo("credential_unlocked") - assertThat(event.getTag("app_version_name")).isEqualTo(BuildInfo.VERSION_NAME_SIMPLE) - } - - @Test - fun `a single failing collector never breaks the rest of the event`() { - // Make the boot-mode collector blow up (simulating e.g. an SELinux denial). - every { app.isUserUnlocked } throws RuntimeException("read denied") - - val event = enrichNewEvent() - - // The failing field is simply dropped... - assertThat(event.getTag("boot_mode")).isNull() - // ...the event is still returned, with every other field intact. - assertThat(event.getTag("app_version_name")).isEqualTo(BuildInfo.VERSION_NAME_SIMPLE) - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 714f8f7941..0a509a32ec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,8 +49,6 @@ viewpager2 = "1.1.0-beta02" zoomage = "1.3.1" monitor = "1.6.1" monitorVersion = "1.7.2" -sentry = "8.29.0" -sentry-gradle-plugin = "5.12.2" mockk = "1.14.5" firebase-bom = "33.7.0" google-services = "4.4.2" @@ -144,11 +142,6 @@ compose-activity = { module = "androidx.activity:activity-compose", version = "1 firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" } -# Sentry -sentry-core = { module = "io.sentry:sentry", version.ref = "sentry" } -sentry-android-core = { module = "io.sentry:sentry-android-core", version.ref = "sentry" } -sentry-android-replay = { module = "io.sentry:sentry-android-replay", version.ref = "sentry" } - # AndroidIDE androidide-ts = { module = "com.itsaky.androidide.treesitter:android-tree-sitter", version.ref = "tree-sitter" } androidide-ts-java = { module = "com.itsaky.androidide.treesitter:tree-sitter-java", version.ref = "tree-sitter" } @@ -323,7 +316,6 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } maven-publish = { id = "com.vanniktech.maven.publish.base", version.ref = "maven-publish-plugin" } gradle-publish = { id = "com.gradle.plugin-publish", version = "1.2.1" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -sentry = { id = "io.sentry.android.gradle", version.ref = "sentry-gradle-plugin" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } google-protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" } rikka-autoresconfig = { id = "dev.rikka.tools.autoresconfig", version = "1.2.2" } diff --git a/lsp/kotlin/build.gradle.kts b/lsp/kotlin/build.gradle.kts index 9b16f87796..7ebd5c36a2 100644 --- a/lsp/kotlin/build.gradle.kts +++ b/lsp/kotlin/build.gradle.kts @@ -55,8 +55,6 @@ dependencies { implementation(libs.common.kotlin) implementation(libs.common.kotlin.coroutines.core) implementation(libs.common.kotlin.coroutines.android) - implementation(libs.sentry.android.core) - compileOnly(projects.common) testImplementation(projects.testing.lsp) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt index 7ed6946e47..b13027d696 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt @@ -56,7 +56,6 @@ import com.itsaky.androidide.tasks.createJobCancelChecker import com.itsaky.androidide.utils.DocumentUtils import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.ifNotEmpty -import io.sentry.Sentry import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -335,7 +334,6 @@ class KotlinLanguageServer : ILanguageServer { @Subscribe @Suppress("unused") fun onBuildCompleted(event: BuildCompletedEvent) { - Sentry.addBreadcrumb("onBuildCompleted: result=${event.result}") compiler?.refreshSources() } diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt index e30d462929..2a128ace46 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt @@ -15,7 +15,6 @@ import com.itsaky.androidide.lsp.kotlin.utils.toVirtualFileOrNull import com.itsaky.androidide.projects.FileManager import com.itsaky.androidide.projects.api.Workspace import com.itsaky.androidide.utils.KeyedDebouncingAction -import io.sentry.Sentry import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineName @@ -203,9 +202,7 @@ internal class CompilationEnvironment( } fun refreshSources() { - Sentry.addBreadcrumb("refreshSources (env=$name, modules=${modules.size})") project.write { - Sentry.addBreadcrumb("refreshSources(env=$name): in-progress") ResolutionScopeProvider.getInstance(project).invalidateAll() modules .asFlatSequence() diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt index a89a19e7e8..fb70655041 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt @@ -10,7 +10,6 @@ import com.itsaky.androidide.lsp.kotlin.compiler.write import com.itsaky.androidide.lsp.kotlin.utils.toVirtualFileOrNull import com.itsaky.androidide.projects.FileManager import com.itsaky.androidide.utils.DocumentUtils -import io.sentry.Sentry import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -151,7 +150,6 @@ internal class KtSymbolIndex( } fun refreshSources() { - Sentry.addBreadcrumb("KtSymbolIndex.refreshSources()") indexingJob ?: startIndexing() scanningJob?.cancel() diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt index b03c205a92..c237066773 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt @@ -6,8 +6,6 @@ import com.itsaky.androidide.lsp.kotlin.compiler.read import com.itsaky.androidide.lsp.kotlin.utils.toNioPathOrNull import com.itsaky.androidide.progress.ICancelChecker import com.itsaky.androidide.projects.FileManager -import io.sentry.Attachment -import io.sentry.Sentry import org.appdevforall.codeonthego.indexing.jvm.JvmClassInfo import org.appdevforall.codeonthego.indexing.jvm.JvmFieldInfo import org.appdevforall.codeonthego.indexing.jvm.JvmFunctionInfo @@ -134,23 +132,6 @@ private fun KaSession.analyzeDeclaration(filePath: String, dcl: KtDeclaration): is KtTypeAlias -> analyzeTypeAlias(filePath, dcl) else -> null } - }.onFailure { err -> - Sentry.captureException(err) { scope -> - scope.apply { - setExtra("fpth", filePath) - setExtra("dcl", dcl.name) - setExtra("dcl.dbg", dcl.getDebugText()) - setExtra( - "par.dbg", - (dcl.parent as? KtElement)?.getDebugText() ?: dcl.parent?.toString() ?: "none" - ) - if (err is KotlinExceptionWithAttachments) { - err.attachments.forEach { attachment -> - scope.addAttachment(Attachment(attachment.bytes, attachment.path)) - } - } - } - } }.getOrNull() } diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt index 9c1f603ef0..4b2acea21e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt @@ -1,6 +1,5 @@ package com.itsaky.androidide.lsp.kotlin.compiler.modules -import io.sentry.Sentry import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import org.jetbrains.kotlin.analysis.api.KaPlatformInterface import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KaContentScopeProvider @@ -26,7 +25,6 @@ internal abstract class AbstractKtModule( _baseSearchScope = computeBaseContentScope() _contentScope = KaContentScopeProvider.getInstance(project).getRefinedContentScope(this) - Sentry.addBreadcrumb("createSearchScopes(mod=$this, base=${_baseSearchScope?.hashCode()}, content=${_contentScope?.hashCode()})") } /** @@ -44,7 +42,6 @@ internal abstract class AbstractKtModule( fun invalidateSearchScope() { synchronized(searchScopeLock) { - Sentry.addBreadcrumb("invalidateSearchScope(mod=$this)") _baseSearchScope = null _contentScope = null } diff --git a/resources/src/main/res/values-in-rID/strings.xml b/resources/src/main/res/values-in-rID/strings.xml index 64c60e7ef1..6f74561290 100644 --- a/resources/src/main/res/values-in-rID/strings.xml +++ b/resources/src/main/res/values-in-rID/strings.xml @@ -585,7 +585,7 @@ Privasi Privasi & analitik - Code on the Go menggunakan Firebase Analytics dan Sentry untuk membantu kami meningkatkan aplikasi.\n\nFirebase Analytics mengumpulkan data penggunaan anonim untuk membantu kami memahami bagaimana aplikasi digunakan.\n\nSentry membantu kami melacak dan memperbaiki masalah.\n\nTidak ada informasi pribadi yang dikumpulkan atau dibagikan. Semua data diproses sesuai dengan kebijakan privasi kami. + Code on the Go menggunakan Firebase Analytics untuk membantu kami meningkatkan aplikasi.\n\nFirebase Analytics mengumpulkan data penggunaan anonim untuk membantu kami memahami bagaimana aplikasi digunakan.\n\nTidak ada informasi pribadi yang dikumpulkan atau dibagikan. Semua data diproses sesuai dengan kebijakan privasi kami. Saya mengerti Pelajari lebih lanjut diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index 32a90dfd87..28a05a486f 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -657,7 +657,7 @@ Privacy Privacy & analytics - Code on the Go uses Firebase Analytics and Sentry to help us improve the app.\n\nFirebase Analytics collects anonymous usage data to help us understand how the app is used. \n\nSentry helps us track and fix errors.\n\nNo personal information is collected or shared. All data is processed in accordance with our privacy policy. + Code on the Go uses Firebase Analytics to help us improve the app.\n\nFirebase Analytics collects anonymous usage data to help us understand how the app is used.\n\nNo personal information is collected or shared. All data is processed in accordance with our privacy policy. I understand Learn more