Skip to content

Commit 00a8d10

Browse files
Migrate to SAF for file access
1 parent 02577a9 commit 00a8d10

File tree

12 files changed

+370
-87
lines changed

12 files changed

+370
-87
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ dependencies {
8484
implementation libs.androidx.compose.ui.graphics
8585
implementation libs.androidx.compose.ui.tooling.preview
8686
implementation libs.androidx.compose.material3
87+
implementation libs.androidx.documentfile
8788
testImplementation libs.junit
8889
androidTestImplementation libs.androidx.junit
8990
androidTestImplementation libs.androidx.espresso.core

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools">
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
43

5-
<uses-permission
6-
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
7-
tools:ignore="ScopedStorage" />
4+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
85
<uses-permission
96
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
107
android:maxSdkVersion="29" />

app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironment.kt

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package org.godotengine.godot_gradle_build_environment
22

3+
import android.Manifest
34
import android.content.Context
5+
import android.content.pm.PackageManager
6+
import android.net.Uri
47
import android.os.Build
5-
import android.os.Environment
68
import android.util.Log
9+
import androidx.core.content.ContextCompat.checkSelfPermission
10+
import androidx.core.net.toUri
711
import java.io.BufferedReader
812
import java.io.File
913
import java.io.FileOutputStream
@@ -36,6 +40,9 @@ class BuildEnvironment(
3640

3741
private var currentProcess: Process? = null
3842

43+
private val accessLock = Object()
44+
@Volatile private var grantedTreeUri: Uri? = null
45+
3946
private fun getDefaultEnv(): List<String> {
4047
return try {
4148
File(rootfs, "env").readLines()
@@ -153,41 +160,59 @@ class BuildEnvironment(
153160
return exitCode
154161
}
155162

156-
private fun setupProject(projectPath: String, gradleBuildDir: String): File {
157-
val fullPath = File(projectPath, gradleBuildDir)
158-
val hash = Integer.toHexString(fullPath.absolutePath.hashCode())
159-
val workDir = File(projectRoot, hash)
160-
161-
// Clean up assets from a previous export.
162-
if (workDir.exists()) {
163-
val apkAssetsDir = File(workDir, "src/main/assets")
164-
if (apkAssetsDir.exists()) {
165-
apkAssetsDir.deleteRecursively()
163+
private fun setupProject(projectPath: String, gradleBuildDir: String, outputHandler: (Int, String) -> Unit): File {
164+
var projectTreeUri = FileUtils.getProjectTreeUri(context, projectPath)
165+
if (projectTreeUri == null) {
166+
if (checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
167+
outputHandler(OUTPUT_STDERR, "Project path \"$projectPath\" is not accessible. Click on notification and give GABE app access to this directory.")
168+
Utils.showDirectoryAccessNotification(context, projectPath)
169+
} else {
170+
throw SecurityException("POST_NOTIFICATIONS permission not granted. Please grant POST_NOTIFICATIONS permission for GABE app and retry.")
166171
}
167172

168-
val aabAssetsDir = File(workDir, "assetPackInstallTime/src/main/assets")
169-
if (aabAssetsDir.exists()) {
170-
aabAssetsDir.deleteRecursively()
171-
}
173+
// 2 minute wait should be ideal?
174+
projectTreeUri = waitForDirectoryAccess(2 * 60 * 1000)
175+
?: throw Exception("Directory access not granted in time. Build canceled.")
176+
outputHandler(OUTPUT_INFO, "Access granted for $projectPath. Starting Gradle build...")
177+
FileUtils.saveProjectTreeUri(context, projectPath, projectTreeUri)
172178
}
173179

174-
if (!FileUtils.tryCopyDirectory(fullPath, workDir)) {
175-
throw IOException("Failed to copy $fullPath to $workDir")
180+
val workDir = Utils.getProjectCacheDir(context, projectPath, gradleBuildDir)
181+
if (!workDir.exists()) {
182+
workDir.mkdirs()
183+
ProjectInfo.writeToDirectory(context, workDir, projectPath, gradleBuildDir, projectTreeUri)
176184
}
177185

178-
ProjectInfo.writeToDirectory(workDir, projectPath, gradleBuildDir)
179-
186+
outputHandler(OUTPUT_INFO, "> Importing project files...")
187+
FileUtils.importAndroidProject(context, projectTreeUri, gradleBuildDir, workDir)
180188
return workDir
181189
}
182190

183-
fun cleanProject(projectPath: String, gradleBuildDir: String) {
184-
val fullPath = File(projectPath, gradleBuildDir)
185-
val hash = Integer.toHexString(fullPath.absolutePath.hashCode())
186-
val workDir = File(projectRoot, hash)
191+
private fun fixGradleArgs(projectPath: String, rawGradleArgs: List<String>): List<String> {
192+
val normalizedProjectPath = projectPath.trimEnd('/')
193+
return rawGradleArgs.map { arg ->
194+
when {
195+
arg.startsWith("-Pdebug_keystore_file=") -> "-Pdebug_keystore_file=/project/.android/debug.keystore"
196+
arg.startsWith("-Prelease_keystore_file=") -> "-Prelease_keystore_file=/project/.android/release.keystore"
197+
arg.startsWith("-Paddons_directory=") -> "-Paddons_directory=/project/addons"
198+
199+
arg.startsWith("-Pplugins_local_binaries=") -> {
200+
val prefix = "-Pplugins_local_binaries="
201+
val value = arg.removePrefix(prefix)
202+
val updated = value.replace("$normalizedProjectPath/addons", "/project/addons")
203+
prefix + updated
204+
}
205+
else -> arg
206+
}
207+
}
208+
}
187209

210+
fun cleanProject(projectPath: String, gradleBuildDir: String) {
211+
val workDir = Utils.getProjectCacheDir(context, projectPath, gradleBuildDir)
188212
if (workDir.exists()) {
189213
workDir.deleteRecursively()
190214
}
215+
FileUtils.deleteProjectTreeUri(context, projectPath)
191216
}
192217

193218
fun cleanGlobalCache() {
@@ -336,7 +361,7 @@ class BuildEnvironment(
336361
gradleCmd,
337362
)
338363
val binds = listOf(
339-
Environment.getExternalStorageDirectory().absolutePath,
364+
"/storage/emulated/0:/storage/emulated/0",
340365
"${workDir.absolutePath}:/project",
341366
"${gradleCache.absolutePath}:/project/?",
342367
)
@@ -348,14 +373,14 @@ class BuildEnvironment(
348373
return AppPaths.getRootfsReadyFile(File(rootfs)).exists()
349374
}
350375

351-
fun executeGradle(gradleArgs: List<String>, projectPath: String, gradleBuildDir: String, outputHandler: (Int, String) -> Unit): Int {
376+
fun executeGradle(rawGradleArgs: List<String>, projectPath: String, gradleBuildDir: String, outputHandler: (Int, String) -> Unit): Int {
352377
if (!isRootfsReady()) {
353378
outputHandler(OUTPUT_STDERR, "Rootfs isn't installed. Install it in the Godot Gradle Build Environment app.")
354379
return 255
355380
}
356381

357382
val workDir = try {
358-
setupProject(projectPath, gradleBuildDir)
383+
setupProject(projectPath, gradleBuildDir, outputHandler)
359384
} catch (e: Exception) {
360385
outputHandler(OUTPUT_STDERR, "Unable to setup project: ${e.message}")
361386
return 255
@@ -371,12 +396,14 @@ class BuildEnvironment(
371396
outputHandler(type, line)
372397
}
373398

399+
val gradleArgs = fixGradleArgs(projectPath, rawGradleArgs)
400+
374401
var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler)
375402

376403
val stderr = stderrBuilder.toString()
377404
if (result == 0 && stderr.contains("BUILD FAILED")) {
378405
// Sometimes Gradle builds fail, but it still gives an exit code of 0.
379-
result = 1;
406+
result = 1
380407
}
381408
stderrBuilder.clear()
382409

@@ -399,7 +426,7 @@ class BuildEnvironment(
399426
result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler)
400427
val stderr = stderrBuilder.toString()
401428
if (result == 0 && stderr.contains("BUILD FAILED")) {
402-
result = 1;
429+
result = 1
403430
}
404431
}
405432

@@ -417,4 +444,25 @@ class BuildEnvironment(
417444
}
418445
}
419446

420-
}
447+
fun waitForDirectoryAccess(timeoutMs: Long): Uri? {
448+
val endTime = System.currentTimeMillis() + timeoutMs
449+
450+
synchronized(accessLock) {
451+
while (grantedTreeUri == null) {
452+
val remaining = endTime - System.currentTimeMillis()
453+
if (remaining <= 0) {
454+
return null
455+
}
456+
accessLock.wait(remaining)
457+
}
458+
return grantedTreeUri
459+
}
460+
}
461+
462+
fun onDirectoryAccessGranted(uri: Uri) {
463+
synchronized(accessLock) {
464+
grantedTreeUri = uri
465+
accessLock.notifyAll()
466+
}
467+
}
468+
}

app/src/main/java/org/godotengine/godot_gradle_build_environment/BuildEnvironmentService.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import android.os.Message
1111
import android.os.Messenger
1212
import android.os.RemoteException
1313
import android.util.Log
14+
import androidx.core.net.toUri
1415
import java.util.concurrent.LinkedBlockingQueue
1516

1617
class BuildEnvironmentService : Service() {
@@ -26,6 +27,8 @@ class BuildEnvironmentService : Service() {
2627
const val MSG_CLEAN_GLOBAL_CACHE = 6
2728
const val MSG_INSTALL_ROOTFS = 7
2829
const val MSG_DELETE_ROOTFS = 8
30+
31+
const val MSG_RESUME_PENDING_BUILD = 9
2932
}
3033

3134
private lateinit var mMessenger: Messenger
@@ -62,6 +65,12 @@ class BuildEnvironmentService : Service() {
6265
MSG_CLEAN_GLOBAL_CACHE -> queueWork(WorkItem(copy, msg.arg1))
6366
MSG_INSTALL_ROOTFS -> queueWork(WorkItem(copy, msg.arg1))
6467
MSG_DELETE_ROOTFS -> queueWork(WorkItem(copy, msg.arg1))
68+
MSG_RESUME_PENDING_BUILD -> {
69+
val uri = msg.data.getString("tree_uri")?.toUri()
70+
if (uri != null) {
71+
mBuildEnvironment.onDirectoryAccessGranted(uri)
72+
}
73+
}
6574
}
6675
}
6776
}
@@ -88,7 +97,7 @@ class BuildEnvironmentService : Service() {
8897
return
8998
}
9099

91-
Log.i(TAG, "Canceling command: ${id}")
100+
Log.i(TAG, "Canceling command: $id")
92101

93102
if (currentItem?.id == id && currentItem?.msg?.what == MSG_EXECUTE_GRADLE) {
94103
mBuildEnvironment.killCurrentProcess()
@@ -131,7 +140,7 @@ class BuildEnvironmentService : Service() {
131140
var result = 255
132141

133142
if (args != null && projectPath != null && gradleBuildDir != null) {
134-
result = mBuildEnvironment.executeGradle(args, projectPath, gradleBuildDir, { type, line ->
143+
result = mBuildEnvironment.executeGradle(args, projectPath, gradleBuildDir) { type, line ->
135144
val reply = Message.obtain(null, MSG_COMMAND_OUTPUT, id, type)
136145
val replyData = Bundle()
137146
replyData.putString("line", line)
@@ -142,7 +151,7 @@ class BuildEnvironmentService : Service() {
142151
} catch (e: RemoteException) {
143152
Log.e(TAG, "Error send command output to client: ${e.message}")
144153
}
145-
})
154+
}
146155
}
147156

148157
val reply = Message.obtain(null, MSG_COMMAND_RESULT, id, result)

0 commit comments

Comments
 (0)