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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ android {
}
horizonos {
dimension "android_distribution"
applicationIdSuffix ".horizonos"

@syntaxerror247 syntaxerror247 Apr 18, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If we want to add this suffix then we'd also need to add logic to detect the OS here https://github.com/godotengine/godot/blob/caf16709af9f08bb430ddb8cffc10cbf66c62437/platform/android/java/editor/src/main/java/org/godotengine/editor/buildprovider/GradleBuildEnvironmentClient.kt#L113-L114

But this would break the backward compatibility.

I think it's fine to ommit the applicationIdSuffix, we have versionNameSuffix to differenciate between builds.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This makes sense to me! I don't think we want to have to synchronize platform specific ids between Godot and GABE

versionNameSuffix "-horizonos"
}
picoos {
dimension "android_distribution"
versionNameSuffix "-picoos"
}
}

compileOptions {
Expand Down
11 changes: 11 additions & 0 deletions app/src/horizonos/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />

</manifest>
6 changes: 0 additions & 6 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" />

<application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,30 @@ class BuildEnvironment(private val context: Context, private val rootfs: String,
}

private fun setupProject(projectPath: String, gradleBuildDir: String, outputHandler: (Int, String) -> Unit): File {
val workDir = Utils.getProjectCacheDir(context, projectPath, gradleBuildDir)

if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") {
val sourceDir = File(projectPath, gradleBuildDir)

if (workDir.exists()) {
val apkAssetsDir = File(workDir, "src/main/assets")
if (apkAssetsDir.exists()) apkAssetsDir.deleteRecursively()

val aabAssetsDir = File(workDir, "assetPackInstallTime/src/main/assets")
if (aabAssetsDir.exists()) aabAssetsDir.deleteRecursively()
} else {
workDir.mkdirs()
}

outputHandler(OUTPUT_INFO, "> Importing project files...")
if (!FileUtils.tryCopyDirectory(sourceDir, workDir)) {
throw IOException("Failed to copy $sourceDir to $workDir")
}

ProjectInfo.writeToDirectoryXR(workDir, projectPath, gradleBuildDir)
return workDir
}

var projectTreeUri = FileUtils.getProjectTreeUri(context, projectPath)
if (projectTreeUri == null) {
val notificationPerm = checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
Expand All @@ -182,7 +206,7 @@ class BuildEnvironment(private val context: Context, private val rootfs: String,

projectTreeUri = waitForDirectoryAccess(DIR_ACCESS_WAIT_DURATION)
?: throw Exception("Directory access not granted in time. Build canceled.")
outputHandler(OUTPUT_INFO, "Access granted for $projectPath. Starting Gradle build...")
outputHandler(OUTPUT_INFO, "Access granted for $projectPath.\nStarting Gradle build...")
FileUtils.saveProjectTreeUri(context, projectPath, projectTreeUri)

// Notify user if limit is reached so they can clear older projects.
Expand All @@ -197,7 +221,6 @@ class BuildEnvironment(private val context: Context, private val rootfs: String,
}
}

val workDir = Utils.getProjectCacheDir(context, projectPath, gradleBuildDir)
if (!workDir.exists()) {
workDir.mkdirs()
ProjectInfo.writeToDirectory(context, workDir, projectPath, gradleBuildDir, projectTreeUri)
Expand Down Expand Up @@ -425,7 +448,12 @@ class BuildEnvironment(private val context: Context, private val rootfs: String,
outputHandler(type, line)
}

val gradleArgs = fixGradleArgs(projectPath, rawGradleArgs)
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)
}

var result = executeGradleInternal(gradleArgs, workDir, captureOutputHandler)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,23 @@ object FileUtils {
private const val PREF_NAME = "tree_uri_prefs"
const val ADDONS_DIR_NAME = "addons"

fun tryCopyDirectory(sourceDir: File, destDir: File): Boolean {
if (!sourceDir.isDirectory) {
Log.e(TAG, "Source directory ${sourceDir.absolutePath} not found")
return false
}
try {
sourceDir.copyRecursively(destDir, overwrite = true)
} catch (e: Exception) {
Log.e(TAG, "Failed to copy: ${e.message}")
return false
}
return true
}

fun tryCopyFile(source: File, dest: File): Boolean {
try {
source.copyTo(dest)
source.copyTo(dest, overwrite = true)
} catch (e: Exception) {
Log.e(TAG, "Failed to copy ${source.absolutePath} to ${dest.absolutePath}: ${e.message}", e)
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ package org.godotengine.godot_gradle_build_environment

import android.Manifest
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import org.godotengine.godot_gradle_build_environment.ui.theme.GodotGradleBuildEnvironmentTheme
import androidx.core.net.toUri

class MainActivity : ComponentActivity() {
private val permissionRequestLauncher =
Expand All @@ -22,12 +27,41 @@ class MainActivity : ComponentActivity() {
}
}

@RequiresApi(Build.VERSION_CODES.R)
private val manageStorageLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (Environment.isExternalStorageManager()) {
Toast.makeText(this, "Storage Access Granted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Storage Access Denied", Toast.LENGTH_SHORT).show()
}
}


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
Utils.createNotificationChannel(this)

if (BuildConfig.FLAVOR == "picoos" || BuildConfig.FLAVOR == "horizonos") {
requestManageStoragePermission()
} else {
requestNotificationPermission()
}

setContent {
GodotGradleBuildEnvironmentTheme {
MainScreen(
this,
AppPaths.getRootfs(this),
AppPaths.getRootfsReadyFile(this),
SettingsManager(this),
)
}
}
}

private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
AlertDialog.Builder(this)
Expand All @@ -41,15 +75,19 @@ class MainActivity : ComponentActivity() {
.show()
}
}
}

setContent {
GodotGradleBuildEnvironmentTheme {
MainScreen(
this,
AppPaths.getRootfs(this),
AppPaths.getRootfsReadyFile(this),
SettingsManager(this),
)
private fun requestManageStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
data = "package:${packageName}".toUri()
}
manageStorageLauncher.launch(intent)
}
} else {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissionRequestLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ data class ProjectInfo(
file.writeText(jsonString)
}

fun writeToDirectoryXR(directory: File, projectPath: String, gradleBuildDir: String) {
val name = File(projectPath).name
val projectInfo = ProjectInfo(projectPath, gradleBuildDir, "", name)
val jsonString = json.encodeToString(projectInfo)
val file = File(directory, PROJECT_INFO_FILENAME)
file.writeText(jsonString)
}

fun readFromDirectory(directory: File): ProjectInfo? {
val file = File(directory, PROJECT_INFO_FILENAME)
return if (file.exists()) {
Expand Down
11 changes: 11 additions & 0 deletions app/src/picoos/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />

</manifest>
Loading