From 92bf34fe115f8f26fbd1b1484e337f29530943bf Mon Sep 17 00:00:00 2001 From: Hal Eisen Date: Fri, 24 Apr 2026 19:18:45 -0700 Subject: [PATCH 1/2] Fix 3 StrictMode violations --- .../onboarding/PermissionsFragment.kt | 18 +-- .../androidide/localWebServer/WebServer.kt | 3 + .../viewmodel/InstallationViewModel.kt | 106 +++++++++--------- 3 files changed, 62 insertions(+), 65 deletions(-) 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 271c6c14a1..435f27e2fd 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 @@ -233,17 +233,17 @@ class PermissionsFragment : } private fun startIdeSetup() { - val shouldProceed = viewModel.checkStorageAndNotify(requireContext()) - if (!shouldProceed) { - return - } + viewLifecycleScope.launch { + val shouldProceed = viewModel.checkStorageAndNotify(requireContext()) + if (!shouldProceed) { + return@launch + } - if (viewModel.isSetupComplete()) { - (activity as? OnboardingActivity)?.tryNavigateToMainIfSetupIsCompleted() - return - } + if (viewModel.isSetupComplete()) { + (activity as? OnboardingActivity)?.tryNavigateToMainIfSetupIsCompleted() + return@launch + } - viewLifecycleScope.launch { doAsyncWithProgress( Dispatchers.IO, configureFlashbar = { builder, _ -> diff --git a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt index 4743433ed7..7303fa3fbc 100644 --- a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt +++ b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt @@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream import java.io.File import java.io.InputStream import java.io.PrintWriter +import android.net.TrafficStats import java.net.InetSocketAddress import java.net.ServerSocket import java.net.Socket @@ -134,8 +135,10 @@ FROM LastChange // NEW FEATURE: Log database metadata when debug is enabled if (debugEnabled) logDatabaseLastChanged() + TrafficStats.setThreadStatsTag(0xC0DE) serverSocket = ServerSocket().apply { reuseAddress = true } serverSocket.bind(InetSocketAddress(config.bindName, config.port)) + TrafficStats.clearThreadStatsTag() log.info("WebServer started successfully on '{}', port {}.", config.bindName, config.port) while (true) { 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 95c1f7e714..2a4c1868e6 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt @@ -60,66 +60,62 @@ class InstallationViewModel : ViewModel() { return } - if (!checkStorageAndNotify(context)) { - return - } - if (!checkToolsIsInstalled()) { - viewModelScope.launch { - try { - _state.update { Installing() } - - withContext(Dispatchers.IO) { - val result = - withStopWatch("Assets installation") { - AssetsInstallationHelper.install(context) { progress -> - log.debug("Assets installation progress: {}", progress.message) - _installationProgress.value = progress.message - } + viewModelScope.launch { + if (!checkStorageAndNotify(context)) { + return@launch + } + if (checkToolsIsInstalled()) { + // Tools already installed + _state.update { InstallationComplete } + return@launch + } + try { + _state.update { Installing() } + + withContext(Dispatchers.IO) { + val result = + withStopWatch("Assets installation") { + AssetsInstallationHelper.install(context) { progress -> + log.debug("Assets installation progress: {}", progress.message) + _installationProgress.value = progress.message } + } - log.info("Assets installation result: {}", result) + log.info("Assets installation result: {}", result) - when (result) { - is AssetsInstallationHelper.Result.Success -> { - val distributionProvider = IJdkDistributionProvider.getInstance() - distributionProvider.loadDistributions() + when (result) { + is AssetsInstallationHelper.Result.Success -> { + val distributionProvider = IJdkDistributionProvider.getInstance() + distributionProvider.loadDistributions() - _state.update { InstallationComplete } + _state.update { InstallationComplete } + } + is AssetsInstallationHelper.Result.Failure -> { + if (result.shouldReportToSentry) { + result.cause?.let { Sentry.captureException(it) } } - is AssetsInstallationHelper.Result.Failure -> { - if (result.shouldReportToSentry) { - result.cause?.let { Sentry.captureException(it) } - } - val errorMsg = result.errorMessage - ?: context.getString(R.string.title_installation_failed) - viewModelScope.launch { - _events.emit(InstallationEvent.ShowError(errorMsg)) - } - _state.update { - InstallationError(errorMsg) - } + val errorMsg = result.errorMessage + ?: context.getString(R.string.title_installation_failed) + _events.emit(InstallationEvent.ShowError(errorMsg)) + _state.update { + InstallationError(errorMsg) } } } - } catch (e: Exception) { - if (e is CancellationException) { - _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) - viewModelScope.launch { - _events.emit(InstallationEvent.ShowError(errorMsg)) - } - _state.update { - InstallationError(errorMsg) - } + } + } catch (e: Exception) { + if (e is CancellationException) { + _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)) + _state.update { + InstallationError(errorMsg) } } - } else { - // Tools already installed - _state.update { InstallationComplete } } } @@ -147,7 +143,7 @@ class InstallationViewModel : ViewModel() { return StorageInfo(isLowStorage, availableStorageInBytes, additionalBytesNeeded) } - fun checkStorageAndNotify(context: Context): Boolean { + suspend fun checkStorageAndNotify(context: Context): Boolean = withContext(Dispatchers.IO) { val storageInfo = getStorageInfo(context) if (storageInfo.isLowStorage) { @@ -160,13 +156,11 @@ class InstallationViewModel : ViewModel() { availableGB ) - viewModelScope.launch { - _events.emit(InstallationEvent.ShowError(errorMessage)) - } - return false + _events.emit(InstallationEvent.ShowError(errorMessage)) + return@withContext false } - return true + return@withContext true } } From 298ab0eef753caf751ed151a4348c274643c9f73 Mon Sep 17 00:00:00 2001 From: Hal Eisen Date: Fri, 24 Apr 2026 19:38:20 -0700 Subject: [PATCH 2/2] Address CodeRabbit comments --- .../androidide/fragments/onboarding/PermissionsFragment.kt | 2 +- .../java/com/itsaky/androidide/localWebServer/WebServer.kt | 4 ++-- .../com/itsaky/androidide/viewmodel/InstallationViewModel.kt | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) 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 435f27e2fd..1e667568bb 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 @@ -239,7 +239,7 @@ class PermissionsFragment : return@launch } - if (viewModel.isSetupComplete()) { + if (viewModel.isSetupCompleteAsync()) { (activity as? OnboardingActivity)?.tryNavigateToMainIfSetupIsCompleted() return@launch } diff --git a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt index 7303fa3fbc..b0f9a1d149 100644 --- a/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt +++ b/app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt @@ -111,6 +111,7 @@ FROM LastChange } fun start() { + TrafficStats.setThreadStatsTag(0xC0DE) try { log.info( "Starting WebServer on {}, port {}, debugEnabled={}, debugEnablePath='{}', debugDatabasePath='{}', experimentsEnabled={}, experimentsEnablePath='{}'.", @@ -135,10 +136,8 @@ FROM LastChange // NEW FEATURE: Log database metadata when debug is enabled if (debugEnabled) logDatabaseLastChanged() - TrafficStats.setThreadStatsTag(0xC0DE) serverSocket = ServerSocket().apply { reuseAddress = true } serverSocket.bind(InetSocketAddress(config.bindName, config.port)) - TrafficStats.clearThreadStatsTag() log.info("WebServer started successfully on '{}', port {}.", config.bindName, config.port) while (true) { @@ -223,6 +222,7 @@ clientSocket and the catch block logic are updated accordingly. if (::serverSocket.isInitialized) { serverSocket.close() } + TrafficStats.clearThreadStatsTag() } } 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 2a4c1868e6..2da418b61e 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt @@ -64,7 +64,7 @@ class InstallationViewModel : ViewModel() { if (!checkStorageAndNotify(context)) { return@launch } - if (checkToolsIsInstalled()) { + if (withContext(Dispatchers.IO) { checkToolsIsInstalled() }) { // Tools already installed _state.update { InstallationComplete } return@launch @@ -121,6 +121,8 @@ class InstallationViewModel : ViewModel() { fun isSetupComplete(): Boolean = checkToolsIsInstalled() + suspend fun isSetupCompleteAsync(): Boolean = withContext(Dispatchers.IO) { checkToolsIsInstalled() } + private fun checkToolsIsInstalled(): Boolean = IJdkDistributionProvider.getInstance().installedDistributions.isNotEmpty() && Environment.ANDROID_HOME.exists()