From 2960d7b6d06550b2d494fc9308cb24b65d2b0a5a Mon Sep 17 00:00:00 2001 From: GDevSh007 Date: Sat, 1 Aug 2026 00:25:27 +0530 Subject: [PATCH 1/6] Fix AAPT2 daemon crash, resolve V1 plugin paths, and improve build phase logging --- .../BuildEnvironment.kt | 186 +++++++++++------- .../BuildEnvironmentService.kt | 38 +++- .../FileUtils.kt | 61 +++++- .../TarXzExtractor.kt | 86 ++++++-- .../screens/RootfsScreen.kt | 24 ++- gradle.properties | 34 ++-- 6 files changed, 303 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt index c0dd9ee..ddc6951 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt @@ -35,8 +35,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, private const val ROOTFS_FILENAME = "alpine-android-35-jdk17.tar.xz" private const val ROOTFS_ASSET_PATH = "linux-rootfs/$ROOTFS_FILENAME" - private const val DIR_ACCESS_WAIT_DURATION = 120_000L // in milliseconds - + private const val DIR_ACCESS_WAIT_DURATION = 120_000L } private var currentProcess: Process? = null @@ -100,16 +99,15 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, env["PROOT_TMP_DIR"] = prootTmpDir.absolutePath env["PROOT_LOADER"] = File(libDir, "libproot-loader.so").absolutePath env["PROOT_LOADER_32"] = File(libDir, "libproot-loader32.so").absolutePath - //env["PROOT_NO_SECCOMP"] = "1" + //env["PROOT_NO_SECCOMP"] = "1" //env["PROOT_VERBOSE"] = "9" - - //val qemu = File(libDir, "libqemu-x86_64.so") + // PROOT_NO_SECCOMP and PROOT_VERBOSE kept commented out for system stability val cmd = buildList { addAll( listOf( proot, - //"-0", + // "-0" kept commented out to prevent strict OS access crashes "-R", rootfs, "-w", workDir, //"-q", qemu.absolutePath, @@ -135,10 +133,16 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, Log.d(TAG, "Cmd: " + sanitizeCommand(cmd)) } - currentProcess = ProcessBuilder(cmd).apply { - directory(context.filesDir) - environment().putAll(env) - }.start() + try { + currentProcess = ProcessBuilder(cmd).apply { + directory(context.filesDir) + environment().putAll(env) + }.start() + } catch (e: Exception) { + outputHandler(OUTPUT_STDERR, "> CRITICAL ERROR: Android OS blocked the Linux environment from starting!") + outputHandler(OUTPUT_STDERR, "> Details: ${e.message}") + return 255 + } val stdoutThread = logAndCaptureStream(BufferedReader(InputStreamReader(currentProcess?.inputStream))) { line -> Log.i(STDOUT_TAG, line) @@ -157,13 +161,23 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, val exitCode = currentProcess?.waitFor() ?: 255 Log.i(TAG, "ExitCode: $exitCode") + + if (exitCode != 0) { + outputHandler(OUTPUT_STDERR, "> Linux Environment crashed instantly with Exit Code: $exitCode") + } currentProcess = null return exitCode } - - private fun setupProject(projectPath: String, gradleBuildDir: String, outputHandler: (Int, String) -> Unit): File { + + private fun setupProject( + projectPath: String, + gradleBuildDir: String, + isPackagingPhase: Boolean, + outputHandler: (Int, String) -> Unit + ): File { val workDir = Utils.getProjectCacheDir(context, projectPath, gradleBuildDir) + val actionText = if (isPackagingPhase) "Packaging" else "Importing" if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") { val sourceDir = File(projectPath, gradleBuildDir) @@ -178,7 +192,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, workDir.mkdirs() } - outputHandler(OUTPUT_INFO, "> Importing project files...") + outputHandler(OUTPUT_INFO, "> $actionText project files...") if (!FileUtils.tryCopyDirectory(sourceDir, workDir)) { throw IOException("Failed to copy $sourceDir to $workDir") } @@ -209,58 +223,67 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, ?: throw Exception("Directory access not granted in time. Build canceled.") if (!FileUtils.isValidDirSelected(context, projectTreeUri)) { - throw Exception("The selected folder is not a valid project directory. Please try exporting again and select $projectPath." + - "\nIf the problem persists, please create a bug report at [color=#3182CE][url]https://github.com/godotengine/android-editor-buildenv-app/issues[/url][/color]") + throw Exception("The selected folder is not a valid project directory.") } outputHandler(OUTPUT_INFO, "Access granted for project directory. Starting Gradle build...") FileUtils.saveProjectTreeUri(context, projectPath, projectTreeUri) - - // Notify user if limit is reached so they can clear older projects. - val persistedCount = context.contentResolver.persistedUriPermissions.size - val limit = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) 128 else 512 - if (persistedCount == limit) { - outputHandler( - OUTPUT_INFO, "Warning: Persisted directory access limit reached." + - "This build will continue, but new projects would require " + - "clearing older ones in ${context.getString(R.string.app_launcher_name)} app" - ) - } } - + if (!workDir.exists()) { workDir.mkdirs() ProjectInfo.writeToDirectory(context, workDir, projectPath, gradleBuildDir, projectTreeUri) } - outputHandler(OUTPUT_INFO, "> Importing project files...") - FileUtils.importAndroidProject(context, projectTreeUri, gradleBuildDir, workDir) + outputHandler(OUTPUT_INFO, "> $actionText project files...") + + // Dynamically intercept the strings coming from FileUtils and replace them if we are packaging! + FileUtils.importAndroidProject(context, projectTreeUri, gradleBuildDir, workDir) { fileName -> + val finalMsg = if (isPackagingPhase) fileName.replace("Importing", "Packaging") else fileName + outputHandler(OUTPUT_INFO, finalMsg) + } + + val gradlewFile = File(workDir, "gradlew") + if (gradlewFile.exists()) { + gradlewFile.setExecutable(true, false) + } + + val finishText = if (isPackagingPhase) "Packaged" else "Imported" + outputHandler(OUTPUT_INFO, "> Project $finishText!") return workDir } - + private fun fixGradleArgs(projectPath: String, rawGradleArgs: List): List { val normalizedProjectPath = projectPath.trimEnd('/') - return rawGradleArgs.map { arg -> + val verboseArgs = rawGradleArgs.toMutableList() + + return verboseArgs.map { arg -> when { arg.startsWith("-Pdebug_keystore_file=") -> "-Pdebug_keystore_file=/project/.android/debug.keystore" arg.startsWith("-Prelease_keystore_file=") -> "-Prelease_keystore_file=/project/.android/release.keystore" arg.startsWith("-Paddons_directory=") -> "-Paddons_directory=/project/${FileUtils.ADDONS_DIR_NAME}" - + arg.startsWith("-Pplugins_local_binaries=") -> { val prefix = "-Pplugins_local_binaries=" val value = arg.removePrefix(prefix) - val updated = value.replace( - "$normalizedProjectPath/${FileUtils.ADDONS_DIR_NAME}", - "/project/${FileUtils.ADDONS_DIR_NAME}" - ) - prefix + updated + + // Godot sends a comma-separated list of absolute Android OS paths. + // Since FileUtils.kt now dumps all V1 plugins directly into the PRoot workspace, + // we just isolate the file name and point Gradle to /project/FileName.aar + val updatedPaths = value.split(",").map { rawPath -> + val cleanPath = rawPath.trim().removeSurrounding("\"").removeSurrounding("'") + val fileName = File(cleanPath).name + "/project/$fileName" + }.joinToString(",") + + prefix + updatedPaths } - + else -> arg } } } - + fun cleanProject(projectPath: String, gradleBuildDir: String) { val workDir = Utils.getProjectCacheDir(context, projectPath, gradleBuildDir) if (workDir.exists()) { @@ -275,7 +298,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, gradleCache.deleteRecursively() } } - + fun installRootfs(localUri: Uri? = null, outputHandler: (Int, String) -> Unit) { val rootfs = File(this.rootfs) @@ -286,10 +309,19 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, rootfs.mkdirs() + val progressCallback: (Int, Long) -> Unit = { count, elapsedMs -> + val totalSeconds = (elapsedMs / 1000).toInt() + val minutes = totalSeconds / 60 + val seconds = totalSeconds % 60 + val timeString = String.format("%02d:%02d", minutes, seconds) + + outputHandler(99, "Extracted: $count files ($timeString)") + } + val version: String if (localUri != null) { outputHandler(OUTPUT_INFO, "> Extracting rootfs from local file...") - TarXzExtractor.extractLocalTarXz(context, localUri, rootfs) + TarXzExtractor.extractLocalTarXz(context, localUri, rootfs, progressCallback) version = ROOTFS_VERSION_CUSTOM } else { val hasAsset = try { @@ -299,7 +331,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, } if (hasAsset) { outputHandler(OUTPUT_INFO, "> Extracting rootfs from assets...") - TarXzExtractor.extractAssetTarXz(context, ROOTFS_ASSET_PATH, rootfs) + TarXzExtractor.extractAssetTarXz(context, ROOTFS_ASSET_PATH, rootfs, progressCallback) version = ROOTFS_VERSION_CUSTOM } else { val tempFile = File(context.cacheDir, ROOTFS_FILENAME) @@ -314,7 +346,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, version = releaseTag outputHandler(OUTPUT_INFO, "> Extracting rootfs...") - TarXzExtractor.extractFileTarXz(tempFile, rootfs) + TarXzExtractor.extractFileTarXz(tempFile, rootfs, progressCallback) } finally { if (tempFile.exists()) { tempFile.delete() @@ -338,7 +370,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, } outputHandler(OUTPUT_INFO, "> Rootfs installation complete!") } - + fun deleteRootfs() { val rootfs = File(this.rootfs) if (rootfs.exists()) { @@ -362,15 +394,6 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, .toList() } - /** - * Patches AAPT2 JAR files in the specified directory by replacing the aapt2 binary - * with the one bundled in the rootfs. - * - * @param hostDir The directory on the host filesystem to search for AAPT2 JARs - * @param boundPath The path where hostDir is bound inside the proot environment - * @param outputHandler Handler for output messages - * @return true if all patches succeeded or no JARs found; otherwise, false if any patch failed - */ private fun patchAapt2Jars(hostDir: File, boundPath: String, outputHandler: (Int, String) -> Unit): Boolean { val jarFiles = findAapt2Jars(hostDir) if (jarFiles.isEmpty()) { @@ -401,7 +424,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, return true } - + private fun executeGradleInternal(gradleArgs: List, workDir: File, outputHandler: (Int, String) -> Unit): Int { val gradleCache = AppPaths.getGlobalGradleCache(context) gradleCache.mkdirs() @@ -438,18 +461,35 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, gradleBuildDir: String, outputHandler: (Int, String) -> Unit ): Int { - if (!isRootfsReady()) { + + if (!isRootfsReady()) { outputHandler(OUTPUT_STDERR, "Rootfs isn't installed. Install it in the Godot Gradle Build Environment app.") return 255 } + // Check if Godot is running the final build/packaging tasks + val isPackagingPhase = !rawGradleArgs.any { it.contains("assemble") || it.contains("bundle") } + val workDir = try { - setupProject(projectPath, gradleBuildDir, outputHandler) + setupProject(projectPath, gradleBuildDir, isPackagingPhase, outputHandler) } catch (e: Exception) { outputHandler(OUTPUT_STDERR, "Unable to setup project: ${e.message}") return 255 } + + /*if (!isRootfsReady()) { + outputHandler(OUTPUT_STDERR, "Rootfs isn't installed. Install it in the Godot Gradle Build Environment app.") + return 255 + } + + val workDir = try { + setupProject(projectPath, gradleBuildDir, outputHandler) + } catch (e: Exception) { + outputHandler(OUTPUT_STDERR, "Unable to setup project: ${e.message}") + return 255 + }*/ + val stderrBuilder = StringBuilder() val captureOutputHandler: (Int, String) -> Unit = { type, line -> if (type == OUTPUT_STDERR) { @@ -459,42 +499,50 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, } outputHandler(type, line) } + + val baseArgs = if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") { + rawGradleArgs + } else { + fixGradleArgs(projectPath, rawGradleArgs) + } + + val gradleArgs = baseArgs.toMutableList() + // Let the Linux shell dynamically find the exact path to AAPT2 (e.g., /opt/sdk/build-tools/36.1.0/aapt2) + // Since the gradleCmd string uses double quotes, bash will evaluate $(which aapt2) perfectly! + gradleArgs.add("-Pandroid.aapt2FromMavenOverride=\$(which aapt2)") + + var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler) - val gradleArgs = if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") { - // GABE has full storage access on XR devices, so we are not pulling addons dir, or keystore files. + + /*val gradleArgs = if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") { rawGradleArgs } else { - fixGradleArgs(projectPath, rawGradleArgs) + fixGradleArgs(projectPath, rawGradleArgs) } - - var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler) + + var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler)*/ val stderr = stderrBuilder.toString() if (result == 0 && stderr.contains("BUILD FAILED")) { - // Sometimes Gradle builds fail, but it still gives an exit code of 0. result = 1 } stderrBuilder.clear() - // Detect if we hit the AAPT2 issue. if (result != 0 && stderr.contains(Regex("""AAPT2 aapt2.*Daemon startup failed"""))) { outputHandler(OUTPUT_INFO, "> Detected AAPT2 issue - attempting to patch the JAR files...") - // Patch AAPT2 JARs in both the project directory and the global gradle cache val gradleCache = AppPaths.getGlobalGradleCache(context) val patchSuccess = patchAapt2Jars(workDir, "/project", outputHandler) && patchAapt2Jars(gradleCache, "/project/?", outputHandler) if (!patchSuccess) { - // If patching failed, there's not much else we can do. return 1 } - // Now, try running Gradle again! outputHandler(OUTPUT_INFO, "> Retrying Gradle build...") result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler) - val stderr = stderrBuilder.toString() - if (result == 0 && stderr.contains("BUILD FAILED")) { + val stderr2 = stderrBuilder.toString() + if (result == 0 && stderr2.contains("BUILD FAILED")) { result = 1 } } @@ -523,8 +571,6 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, Thread.currentThread().interrupt() } } - // We reset the value to `null` after returning it to avoid returning a previous value when this method is - // invoked again. return grantedTreeUri.getAndSet(null) } } diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt index 8a111ad..96562b2 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt @@ -28,6 +28,9 @@ class BuildEnvironmentService : Service() { const val MSG_INSTALL_ROOTFS = 7 const val MSG_DELETE_ROOTFS = 8 const val MSG_BUILD_DIR_ACCESS_GRANTED = 9 + + // NEW: Constant for live timer updates sent to the UI + const val MSG_EXTRACTION_PROGRESS = 99 const val EXTRA_LOCAL_ROOTFS_URI = "local_rootfs_uri" } @@ -201,15 +204,31 @@ class BuildEnvironmentService : Service() { try { mBuildEnvironment.installRootfs(localUri) { type, line -> - val outputMsg = Message.obtain(null, MSG_COMMAND_OUTPUT, id, type) - val outputData = Bundle() - outputData.putString("line", line) - outputMsg.data = outputData - - try { - msg.replyTo.send(outputMsg) - } catch (e: RemoteException) { - Log.e(TAG, "Error sending output to client: ${e.message}") + + // NEW: If type is 99, route it to the UI's progress timer state + if (type == MSG_EXTRACTION_PROGRESS) { + val progressMsg = Message.obtain(null, MSG_EXTRACTION_PROGRESS) + val bundle = Bundle() + bundle.putString("timerText", line) + progressMsg.data = bundle + + try { + msg.replyTo.send(progressMsg) + } catch (e: RemoteException) { + Log.e(TAG, "Error sending progress to client: ${e.message}") + } + } else { + // Standard command output routing + val outputMsg = Message.obtain(null, MSG_COMMAND_OUTPUT, id, type) + val outputData = Bundle() + outputData.putString("line", line) + outputMsg.data = outputData + + try { + msg.replyTo.send(outputMsg) + } catch (e: RemoteException) { + Log.e(TAG, "Error sending output to client: ${e.message}") + } } } } catch (e: Exception) { @@ -258,5 +277,4 @@ class BuildEnvironmentService : Service() { Log.e(TAG, "Error sending result to client: ${e.message}") } } - } diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt index bb95c40..4492f61 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt @@ -91,8 +91,14 @@ object FileUtils { return String.format("%.1f %s", value, units[unitIndex]) } - - fun importAndroidProject(context: Context, projectTreeUri: Uri, gradleBuildDir: String, destDir: File) { + + fun importAndroidProject( + context: Context, + projectTreeUri: Uri, + gradleBuildDir: String, + destDir: File, + onProgress: (String) -> Unit + ) { val root = DocumentFile.fromTreeUri(context, projectTreeUri) ?: throw IOException("Invalid tree uri") @@ -103,6 +109,8 @@ object FileUtils { it.isDirectory && it.name == ADDONS_DIR_NAME } + val pluginsDir = findDirByPath(root, "android/plugins") + if (destDir.exists()) { val apkAssetsDir = File(destDir, "src/main/assets") if (apkAssetsDir.exists()) apkAssetsDir.deleteRecursively() @@ -113,7 +121,8 @@ object FileUtils { destDir.mkdirs() } - copyDirectoryMerge(context, gradleDir, destDir) + onProgress("> Importing Gradle build directory...") + copyDirectoryMerge(context, gradleDir, destDir, onProgress) if (addonsDir != null) { val localAddons = File(destDir, ADDONS_DIR_NAME) @@ -121,10 +130,28 @@ object FileUtils { localAddons.deleteRecursively() } localAddons.mkdirs() - copyDirectoryMerge(context, addonsDir, localAddons) + onProgress("> Importing Addons directory...") + copyDirectoryMerge(context, addonsDir, localAddons, onProgress) } - } + + if (pluginsDir != null) { + // Force V1 plugins to copy directly into the project root directory! + onProgress("> Importing v1 Plugins directory...") + copyDirectoryMerge(context, pluginsDir, destDir, onProgress) + } + + /*if (pluginsDir != null) { + val localPlugins = File(destDir.parentFile, "plugins") + if (localPlugins.exists()) { + localPlugins.deleteRecursively() + } + localPlugins.mkdirs() + onProgress("> Importing v1 Plugins directory...") + copyDirectoryMerge(context, pluginsDir, localPlugins, onProgress) + }*/ + } + private fun findDirByPath(parent: DocumentFile, relativePath: String): DocumentFile? { var current: DocumentFile? = parent @@ -139,16 +166,34 @@ object FileUtils { return current } - private fun copyDirectoryMerge(context: Context, src: DocumentFile, dest: File) { + private fun copyDirectoryMerge( + context: Context, + src: DocumentFile, + dest: File, + onProgress: (String) -> Unit + ) { src.listFiles().forEach { file -> val name = file.name ?: return@forEach if (file.isDirectory) { val newDir = File(dest, name) if (!newDir.exists()) newDir.mkdirs() - copyDirectoryMerge(context, file, newDir) + + // Log directory milestones instead of every internal file + onProgress("Importing Directory: $name") + copyDirectoryMerge(context, file, newDir, onProgress) } else { val outFile = File(dest, name) + + // Only log high-value plugin binaries, configuration, or build files to the UI + if (name.endsWith(".aar", true) || + name.endsWith(".jar", true) || + name.endsWith(".gdap", true) || + name.endsWith(".gradle", true) || + name == "AndroidManifest.xml") { + onProgress("Importing Plugin/Config: $name") + } + context.contentResolver.openInputStream(file.uri).use { input -> FileOutputStream(outFile, false).use { output -> input?.copyTo(output) @@ -157,7 +202,7 @@ object FileUtils { } } } - + fun saveProjectTreeUri(context: Context, projectPath: String, projectTreeUri: Uri) { context.contentResolver.takePersistableUriPermission( projectTreeUri, diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/TarXzExtractor.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/TarXzExtractor.kt index ee2c6b5..a78e59b 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/TarXzExtractor.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/TarXzExtractor.kt @@ -13,39 +13,77 @@ import java.io.IOException import java.io.InputStream object TarXzExtractor { - fun extractLocalTarXz(context: Context, localTarXzUri: Uri, destDir: File) { + + fun extractLocalTarXz( + context: Context, + localTarXzUri: Uri, + destDir: File, + onProgress: ((Int, Long) -> Unit)? = null + ) { context.contentResolver.openInputStream(localTarXzUri).use { inputStream -> if (inputStream == null) { throw IOException("Failed to open local rootfs file") } - extractTarXz(inputStream, destDir) + extractTarXz(inputStream, destDir, onProgress) } } - fun extractAssetTarXz(context: Context, assetTarXz: String, destDir: File) { + fun extractAssetTarXz( + context: Context, + assetTarXz: String, + destDir: File, + onProgress: ((Int, Long) -> Unit)? = null + ) { context.assets.open(assetTarXz).use { inputStream -> - extractTarXz(inputStream, destDir) + extractTarXz(inputStream, destDir, onProgress) } } - fun extractFileTarXz(sourceFile: File, destDir: File) { + fun extractFileTarXz( + sourceFile: File, + destDir: File, + onProgress: ((Int, Long) -> Unit)? = null + ) { sourceFile.inputStream().use { inputStream -> - extractTarXz(inputStream, destDir) + extractTarXz(inputStream, destDir, onProgress) } } - private fun extractTarXz(inputStream: InputStream, destDir: File) { + private fun extractTarXz( + inputStream: InputStream, + destDir: File, + onProgress: ((Int, Long) -> Unit)? + ) { if (!destDir.exists() && !destDir.mkdirs()) { throw IllegalStateException("Could not create destination dir: ${destDir.absolutePath}") } val destRoot = destDir.canonicalFile + val sharedBuffer = ByteArray(128 * 1024) + + val startTime = System.currentTimeMillis() + var lastUpdateTime = startTime + var fileCount = 0 + + // NEW: We define a reusable function to check the time and update the UI + val tickProgress = { + val currentTime = System.currentTimeMillis() + if (currentTime - lastUpdateTime >= 50) { + lastUpdateTime = currentTime + onProgress?.invoke(fileCount, currentTime - startTime) + } + } BufferedInputStream(inputStream).use { buf -> XZCompressorInputStream(buf).use { xz -> TarArchiveInputStream(xz).use { tar -> var entry = tar.nextTarEntry while (entry != null) { + fileCount++ + + // Check time before starting a new file + tickProgress() + val outFile = File(destDir, entry.name) val outCanonical = outFile.canonicalFile @@ -79,7 +117,8 @@ object TarXzExtractor { try { Os.link(target, outCanonical.path) } catch (e: ErrnoException) { - copyFromFile(File(target), outCanonical) + // Pass the tickProgress function down + copyFromFile(File(target), outCanonical, sharedBuffer, tickProgress) } applyMode(outCanonical, entry.mode) applyMtime(outCanonical, entry.modTime.time) @@ -88,7 +127,8 @@ object TarXzExtractor { else -> { outCanonical.parentFile?.let { if (!it.exists()) it.mkdirs() } FileOutputStream(outCanonical).use { fos -> - copyStream(tar, fos) + // Pass the tickProgress function down + copyStream(tar, fos, sharedBuffer, tickProgress) } applyMode(outCanonical, entry.mode) applyMtime(outCanonical, entry.modTime.time) @@ -100,26 +140,42 @@ object TarXzExtractor { } } } + + // Final UI update when 100% complete + val finalElapsedMs = System.currentTimeMillis() - startTime + onProgress?.invoke(fileCount, finalElapsedMs) } - private fun copyStream(input: TarArchiveInputStream, output: FileOutputStream) { - val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + // NEW: Accepts tickProgress and calls it while looping through large files + private fun copyStream( + input: TarArchiveInputStream, + output: FileOutputStream, + buffer: ByteArray, + tickProgress: () -> Unit + ) { while (true) { val read = input.read(buffer) if (read <= 0) break output.write(buffer, 0, read) + tickProgress() // Update UI during massive file writes! } output.flush() } - private fun copyFromFile(src: File, dst: File) { + // NEW: Accepts tickProgress and calls it while looping through large files + private fun copyFromFile( + src: File, + dst: File, + buffer: ByteArray, + tickProgress: () -> Unit + ) { src.inputStream().use { `in` -> dst.outputStream().use { out -> - val buf = ByteArray(DEFAULT_BUFFER_SIZE) while (true) { - val n = `in`.read(buf) + val n = `in`.read(buffer) if (n <= 0) break - out.write(buf, 0, n) + out.write(buffer, 0, n) + tickProgress() // Update UI during massive file copies! } } } diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt index 38031e6..147f20e 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt @@ -152,8 +152,11 @@ fun RootfsInstallOrDeleteButton( var isLoading by rememberSaveable { mutableStateOf(false) } var errorMessage by rememberSaveable { mutableStateOf(null) } var progressMessages by rememberSaveable { mutableStateOf>(emptyList()) } + + // New state for live timer updates + var extractionProgress by rememberSaveable { mutableStateOf("") } + var commandId by remember { mutableIntStateOf(0) } - var serviceMessenger by remember { mutableStateOf(null) } var replyMessenger by remember { mutableStateOf(null) } @@ -166,6 +169,7 @@ fun RootfsInstallOrDeleteButton( isLoading = true errorMessage = null progressMessages = emptyList() + extractionProgress = "" // Reset progress on new extraction commandId++ val msg = Message.obtain(null, msgType, commandId, 0) @@ -200,6 +204,12 @@ fun RootfsInstallOrDeleteButton( val handler = object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { when (msg.what) { + // Catching the new timer message from BuildEnvironmentService + BuildEnvironmentService.MSG_EXTRACTION_PROGRESS -> { + val timerText = msg.data.getString("timerText") ?: "" + extractionProgress = timerText + } + BuildEnvironmentService.MSG_COMMAND_OUTPUT -> { val line = msg.data.getString("line") ?: "" progressMessages = progressMessages + line @@ -208,6 +218,7 @@ fun RootfsInstallOrDeleteButton( BuildEnvironmentService.MSG_COMMAND_RESULT -> { val result = msg.arg2 isLoading = false + extractionProgress = "" // Clear the timer when finished if (result == 0) { fileExists = rootfsReadyFile.exists() @@ -243,10 +254,21 @@ fun RootfsInstallOrDeleteButton( isLoading -> { CircularProgressIndicator() Spacer(modifier = Modifier.height(20.dp)) + if (fileExists) { Text(stringResource(R.string.deleting_rootfs_message)) } else { Text(stringResource(R.string.installing_rootfs_message)) + + // Display the live timer progress if data is available + if (extractionProgress.isNotEmpty()) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = extractionProgress, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary + ) + } } Spacer(modifier = Modifier.height(10.dp)) diff --git a/gradle.properties b/gradle.properties index 20e2a01..cb507dd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,23 +1,13 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. For more details, visit -# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Kotlin code style for this project: "official" or "obsolete": +#AndroidCS: enforce UTF-8 & locale for Gradle daemon +#Fri Jul 31 18:52:27 GMT 2026 +android.nonTransitiveRClass=true +org.gradle.daemon.idletimeout=10800000 kotlin.code.style=official -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +systemProp.user.language=en +systemProp.user.country=US +systemProp.sun.jnu.encoding=UTF-8 +systemProp.file.encoding=UTF-8 +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding\=UTF-8 +android.useAndroidX=true From d9ac8b2a2de936d9428970dc5b4d2a1db21fabb9 Mon Sep 17 00:00:00 2001 From: GDevSh007 Date: Sat, 1 Aug 2026 02:04:05 +0530 Subject: [PATCH 2/6] Revert auto-generated changes to gradle.properties --- gradle.properties | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/gradle.properties b/gradle.properties index cb507dd..20e2a01 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,23 @@ -#AndroidCS: enforce UTF-8 & locale for Gradle daemon -#Fri Jul 31 18:52:27 GMT 2026 -android.nonTransitiveRClass=true -org.gradle.daemon.idletimeout=10800000 -kotlin.code.style=official -systemProp.user.language=en -systemProp.user.country=US -systemProp.sun.jnu.encoding=UTF-8 -systemProp.file.encoding=UTF-8 -org.gradle.daemon=true -org.gradle.parallel=true -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding\=UTF-8 +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file From f1155a4bf705855adc0cd6c64452dfc7078fefb3 Mon Sep 17 00:00:00 2001 From: GDevSh007 Date: Sat, 1 Aug 2026 03:04:20 +0530 Subject: [PATCH 3/6] Update code comments in RootfsScreen and BuildEnvironment --- .../BuildEnvironment.kt | 80 ++++++++++++------- .../screens/RootfsScreen.kt | 1 + 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt index ddc6951..a925c01 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt @@ -35,7 +35,8 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, private const val ROOTFS_FILENAME = "alpine-android-35-jdk17.tar.xz" private const val ROOTFS_ASSET_PATH = "linux-rootfs/$ROOTFS_FILENAME" - private const val DIR_ACCESS_WAIT_DURATION = 120_000L + private const val DIR_ACCESS_WAIT_DURATION = 120_000L // in milliseconds + } private var currentProcess: Process? = null @@ -99,15 +100,16 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, env["PROOT_TMP_DIR"] = prootTmpDir.absolutePath env["PROOT_LOADER"] = File(libDir, "libproot-loader.so").absolutePath env["PROOT_LOADER_32"] = File(libDir, "libproot-loader32.so").absolutePath - //env["PROOT_NO_SECCOMP"] = "1" + //env["PROOT_NO_SECCOMP"] = "1" //env["PROOT_VERBOSE"] = "9" - // PROOT_NO_SECCOMP and PROOT_VERBOSE kept commented out for system stability + + //val qemu = File(libDir, "libqemu-x86_64.so") val cmd = buildList { addAll( listOf( proot, - // "-0" kept commented out to prevent strict OS access crashes + //"-0", "-R", rootfs, "-w", workDir, //"-q", qemu.absolutePath, @@ -223,11 +225,23 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, ?: throw Exception("Directory access not granted in time. Build canceled.") if (!FileUtils.isValidDirSelected(context, projectTreeUri)) { - throw Exception("The selected folder is not a valid project directory.") + throw Exception("The selected folder is not a valid project directory. Please try exporting again and select $projectPath." + + "\nIf the problem persists, please create a bug report at [color=#3182CE][url]https://github.com/godotengine/android-editor-buildenv-app/issues[/url][/color]") } outputHandler(OUTPUT_INFO, "Access granted for project directory. Starting Gradle build...") FileUtils.saveProjectTreeUri(context, projectPath, projectTreeUri) + + // Notify user if limit is reached so they can clear older projects. + val persistedCount = context.contentResolver.persistedUriPermissions.size + val limit = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) 128 else 512 + if (persistedCount == limit) { + outputHandler( + OUTPUT_INFO, "Warning: Persisted directory access limit reached." + + "This build will continue, but new projects would require " + + "clearing older ones in ${context.getString(R.string.app_launcher_name)} app" + ) + } } if (!workDir.exists()) { @@ -252,7 +266,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, outputHandler(OUTPUT_INFO, "> Project $finishText!") return workDir } - + private fun fixGradleArgs(projectPath: String, rawGradleArgs: List): List { val normalizedProjectPath = projectPath.trimEnd('/') val verboseArgs = rawGradleArgs.toMutableList() @@ -262,7 +276,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, arg.startsWith("-Pdebug_keystore_file=") -> "-Pdebug_keystore_file=/project/.android/debug.keystore" arg.startsWith("-Prelease_keystore_file=") -> "-Prelease_keystore_file=/project/.android/release.keystore" arg.startsWith("-Paddons_directory=") -> "-Paddons_directory=/project/${FileUtils.ADDONS_DIR_NAME}" - + arg.startsWith("-Pplugins_local_binaries=") -> { val prefix = "-Pplugins_local_binaries=" val value = arg.removePrefix(prefix) @@ -278,12 +292,12 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, prefix + updatedPaths } - + else -> arg } } } - + fun cleanProject(projectPath: String, gradleBuildDir: String) { val workDir = Utils.getProjectCacheDir(context, projectPath, gradleBuildDir) if (workDir.exists()) { @@ -298,7 +312,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, gradleCache.deleteRecursively() } } - + fun installRootfs(localUri: Uri? = null, outputHandler: (Int, String) -> Unit) { val rootfs = File(this.rootfs) @@ -370,7 +384,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, } outputHandler(OUTPUT_INFO, "> Rootfs installation complete!") } - + fun deleteRootfs() { val rootfs = File(this.rootfs) if (rootfs.exists()) { @@ -387,6 +401,15 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, } } + /** + * Patches AAPT2 JAR files in the specified directory by replacing the aapt2 binary + * with the one bundled in the rootfs. + * + * @param hostDir The directory on the host filesystem to search for AAPT2 JARs + * @param boundPath The path where hostDir is bound inside the proot environment + * @param outputHandler Handler for output messages + * @return true if all patches succeeded or no JARs found; otherwise, false if any patch failed + */ private fun findAapt2Jars(root: File): List { val regex = Regex("""aapt2-.*-linux\.jar""") return root.walkTopDown() @@ -424,7 +447,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, return true } - + private fun executeGradleInternal(gradleArgs: List, workDir: File, outputHandler: (Int, String) -> Unit): Int { val gradleCache = AppPaths.getGlobalGradleCache(context) gradleCache.mkdirs() @@ -461,8 +484,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, gradleBuildDir: String, outputHandler: (Int, String) -> Unit ): Int { - - if (!isRootfsReady()) { + if (!isRootfsReady()) { outputHandler(OUTPUT_STDERR, "Rootfs isn't installed. Install it in the Godot Gradle Build Environment app.") return 255 } @@ -477,19 +499,6 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, return 255 } - - /*if (!isRootfsReady()) { - outputHandler(OUTPUT_STDERR, "Rootfs isn't installed. Install it in the Godot Gradle Build Environment app.") - return 255 - } - - val workDir = try { - setupProject(projectPath, gradleBuildDir, outputHandler) - } catch (e: Exception) { - outputHandler(OUTPUT_STDERR, "Unable to setup project: ${e.message}") - return 255 - }*/ - val stderrBuilder = StringBuilder() val captureOutputHandler: (Int, String) -> Unit = { type, line -> if (type == OUTPUT_STDERR) { @@ -514,31 +523,37 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler) - /*val gradleArgs = if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") { + /**val gradleArgs = if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") { + // GABE has full storage access on XR devices, so we are not pulling addons dir, or keystore files. rawGradleArgs } else { - fixGradleArgs(projectPath, rawGradleArgs) + fixGradleArgs(projectPath, rawGradleArgs) } - - var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler)*/ + + var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler)**/ val stderr = stderrBuilder.toString() if (result == 0 && stderr.contains("BUILD FAILED")) { + // Sometimes Gradle builds fail, but it still gives an exit code of 0. result = 1 } stderrBuilder.clear() + // Detect if we hit the AAPT2 issue. if (result != 0 && stderr.contains(Regex("""AAPT2 aapt2.*Daemon startup failed"""))) { outputHandler(OUTPUT_INFO, "> Detected AAPT2 issue - attempting to patch the JAR files...") + // Patch AAPT2 JARs in both the project directory and the global gradle cache val gradleCache = AppPaths.getGlobalGradleCache(context) val patchSuccess = patchAapt2Jars(workDir, "/project", outputHandler) && patchAapt2Jars(gradleCache, "/project/?", outputHandler) if (!patchSuccess) { + // If patching failed, there's not much else we can do. return 1 } + // Now, try running Gradle again! outputHandler(OUTPUT_INFO, "> Retrying Gradle build...") result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler) val stderr2 = stderrBuilder.toString() @@ -571,6 +586,9 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, Thread.currentThread().interrupt() } } + + // We reset the value to `null` after returning it to avoid returning a previous value when this method is + // invoked again. return grantedTreeUri.getAndSet(null) } } diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt index 147f20e..242279b 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt @@ -157,6 +157,7 @@ fun RootfsInstallOrDeleteButton( var extractionProgress by rememberSaveable { mutableStateOf("") } var commandId by remember { mutableIntStateOf(0) } + var serviceMessenger by remember { mutableStateOf(null) } var replyMessenger by remember { mutableStateOf(null) } From cad48f2e15dc141582afbcd3d9d86811f7ad4bac Mon Sep 17 00:00:00 2001 From: GDevSh007 Date: Sat, 1 Aug 2026 03:34:09 +0530 Subject: [PATCH 4/6] Unneccesary comparisons reduction --- .../BuildEnvironment.kt | 23 +++++++++---------- .../BuildEnvironmentService.kt | 1 + .../FileUtils.kt | 2 +- .../screens/RootfsScreen.kt | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt index a925c01..7910616 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt @@ -36,7 +36,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, private const val ROOTFS_ASSET_PATH = "linux-rootfs/$ROOTFS_FILENAME" private const val DIR_ACCESS_WAIT_DURATION = 120_000L // in milliseconds - + } private var currentProcess: Process? = null @@ -102,7 +102,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, env["PROOT_LOADER_32"] = File(libDir, "libproot-loader32.so").absolutePath //env["PROOT_NO_SECCOMP"] = "1" //env["PROOT_VERBOSE"] = "9" - + //val qemu = File(libDir, "libqemu-x86_64.so") val cmd = buildList { @@ -231,7 +231,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, outputHandler(OUTPUT_INFO, "Access granted for project directory. Starting Gradle build...") FileUtils.saveProjectTreeUri(context, projectPath, projectTreeUri) - + // Notify user if limit is reached so they can clear older projects. val persistedCount = context.contentResolver.persistedUriPermissions.size val limit = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) 128 else 512 @@ -243,7 +243,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, ) } } - + if (!workDir.exists()) { workDir.mkdirs() ProjectInfo.writeToDirectory(context, workDir, projectPath, gradleBuildDir, projectTreeUri) @@ -401,6 +401,11 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, } } + private fun findAapt2Jars(root: File): List { + val regex = Regex("""aapt2-.*-linux\.jar""") + return root.walkTopDown() + .filter { it.isFile && regex.matches(it.name) } + .toList() /** * Patches AAPT2 JAR files in the specified directory by replacing the aapt2 binary * with the one bundled in the rootfs. @@ -410,11 +415,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, * @param outputHandler Handler for output messages * @return true if all patches succeeded or no JARs found; otherwise, false if any patch failed */ - private fun findAapt2Jars(root: File): List { - val regex = Regex("""aapt2-.*-linux\.jar""") - return root.walkTopDown() - .filter { it.isFile && regex.matches(it.name) } - .toList() + } private fun patchAapt2Jars(hostDir: File, boundPath: String, outputHandler: (Int, String) -> Unit): Boolean { @@ -529,7 +530,6 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, } else { fixGradleArgs(projectPath, rawGradleArgs) } - var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler)**/ val stderr = stderrBuilder.toString() @@ -586,9 +586,8 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, Thread.currentThread().interrupt() } } - // We reset the value to `null` after returning it to avoid returning a previous value when this method is - // invoked again. + // invoked again. return grantedTreeUri.getAndSet(null) } } diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt index 96562b2..3ad825f 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt @@ -277,4 +277,5 @@ class BuildEnvironmentService : Service() { Log.e(TAG, "Error sending result to client: ${e.message}") } } + } diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt index 4492f61..cc0749c 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt @@ -202,7 +202,7 @@ object FileUtils { } } } - + fun saveProjectTreeUri(context: Context, projectPath: String, projectTreeUri: Uri) { context.contentResolver.takePersistableUriPermission( projectTreeUri, diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt index 242279b..6a19688 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/screens/RootfsScreen.kt @@ -157,7 +157,7 @@ fun RootfsInstallOrDeleteButton( var extractionProgress by rememberSaveable { mutableStateOf("") } var commandId by remember { mutableIntStateOf(0) } - + var serviceMessenger by remember { mutableStateOf(null) } var replyMessenger by remember { mutableStateOf(null) } From bb2fa51e07fd6736eba92a03830c685dc1d7adfa Mon Sep 17 00:00:00 2001 From: GDevSh007 Date: Sat, 1 Aug 2026 03:47:04 +0530 Subject: [PATCH 5/6] More reduction --- .../godot_gradle_build_environment/BuildEnvironment.kt | 4 ++-- .../godot_gradle_build_environment/FileUtils.kt | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt index 7910616..eb12b5b 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt @@ -406,6 +406,8 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, return root.walkTopDown() .filter { it.isFile && regex.matches(it.name) } .toList() + } + /** * Patches AAPT2 JAR files in the specified directory by replacing the aapt2 binary * with the one bundled in the rootfs. @@ -415,8 +417,6 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, * @param outputHandler Handler for output messages * @return true if all patches succeeded or no JARs found; otherwise, false if any patch failed */ - - } private fun patchAapt2Jars(hostDir: File, boundPath: String, outputHandler: (Int, String) -> Unit): Boolean { val jarFiles = findAapt2Jars(hostDir) diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt index cc0749c..3f19db6 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/FileUtils.kt @@ -91,8 +91,8 @@ object FileUtils { return String.format("%.1f %s", value, units[unitIndex]) } - - fun importAndroidProject( + + fun importAndroidProject( context: Context, projectTreeUri: Uri, gradleBuildDir: String, @@ -139,7 +139,7 @@ object FileUtils { onProgress("> Importing v1 Plugins directory...") copyDirectoryMerge(context, pluginsDir, destDir, onProgress) } - + } /*if (pluginsDir != null) { val localPlugins = File(destDir.parentFile, "plugins") @@ -150,8 +150,7 @@ object FileUtils { onProgress("> Importing v1 Plugins directory...") copyDirectoryMerge(context, pluginsDir, localPlugins, onProgress) }*/ - } - + private fun findDirByPath(parent: DocumentFile, relativePath: String): DocumentFile? { var current: DocumentFile? = parent From 4596c9057ba8bc3bd14bb83c2e17d2dd5076ba7c Mon Sep 17 00:00:00 2001 From: GDevSh007 Date: Sat, 1 Aug 2026 03:53:00 +0530 Subject: [PATCH 6/6] Here we go again :P XD --- .../godot_gradle_build_environment/BuildEnvironment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt index eb12b5b..4216f67 100644 --- a/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt +++ b/app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt @@ -406,7 +406,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, return root.walkTopDown() .filter { it.isFile && regex.matches(it.name) } .toList() - } + } /** * Patches AAPT2 JAR files in the specified directory by replacing the aapt2 binary @@ -417,7 +417,6 @@ class BuildEnvironment(private val context: Context, private val rootfs: String, * @param outputHandler Handler for output messages * @return true if all patches succeeded or no JARs found; otherwise, false if any patch failed */ - private fun patchAapt2Jars(hostDir: File, boundPath: String, outputHandler: (Int, String) -> Unit): Boolean { val jarFiles = findAapt2Jars(hostDir) if (jarFiles.isEmpty()) {