diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index bfc123c8cd8a..340c712c84e3 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -123,6 +123,14 @@ android {
applicationIdSuffix ".dev"
manifestPlaceholders += [editorBuildSuffix: " (dev)"]
}
+ benchmark {
+ initWith release
+ applicationIdSuffix ".benchmark"
+ manifestPlaceholders += [editorBuildSuffix: " (benchmark)"]
+ signingConfig signingConfigs.debug
+ matchingFallbacks = ['release']
+ debuggable false
+ }
debug {
initWith release
@@ -186,6 +194,7 @@ dependencies {
implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "org.bouncycastle:bcprov-jdk15to18:1.78"
+ implementation "androidx.profileinstaller:profileinstaller:1.4.1"
// Meta dependencies
horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:$versions.openxrVendorsVersion"
diff --git a/platform/android/java/editor/macrobenchmark/.gitignore b/platform/android/java/editor/macrobenchmark/.gitignore
new file mode 100644
index 000000000000..42afabfd2abe
--- /dev/null
+++ b/platform/android/java/editor/macrobenchmark/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/platform/android/java/editor/macrobenchmark/build.gradle b/platform/android/java/editor/macrobenchmark/build.gradle
new file mode 100644
index 000000000000..adfdf7b41613
--- /dev/null
+++ b/platform/android/java/editor/macrobenchmark/build.gradle
@@ -0,0 +1,55 @@
+plugins {
+ id 'com.android.test'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'org.godotengine.editor.macrobenchmark'
+ compileSdk versions.compileSdk
+
+ compileOptions {
+ sourceCompatibility versions.javaVersion
+ targetCompatibility versions.javaVersion
+ }
+
+ kotlinOptions {
+ jvmTarget = versions.javaVersion
+ }
+
+ defaultConfig {
+ minSdk 24
+ targetSdk versions.targetSdk
+ missingDimensionStrategy 'products', 'editor'
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = 'EMULATOR'
+ }
+
+ buildTypes {
+ // This benchmark buildType is used for benchmarking, and should function like your
+ // release build (for example, with minification on). It's signed with a debug key
+ // for easy local/CI testing.
+ benchmark {
+ debuggable = true
+ signingConfig = debug.signingConfig
+ matchingFallbacks = ["release"]
+ }
+ }
+
+ targetProjectPath = ":editor"
+ experimentalProperties["android.experimental.self-instrumenting"] = true
+}
+
+dependencies {
+ implementation 'androidx.test:rules:1.5.0'
+ implementation 'androidx.test.ext:junit:1.1.5'
+ implementation 'androidx.test.espresso:espresso-core:3.5.1'
+ implementation 'androidx.test.uiautomator:uiautomator:2.3.0'
+ implementation 'androidx.benchmark:benchmark-macro-junit4:1.2.4'
+}
+
+androidComponents {
+ beforeVariants(selector().all()) {
+ enable = buildType == "benchmark"
+ }
+}
diff --git a/platform/android/java/editor/macrobenchmark/src/main/AndroidManifest.xml b/platform/android/java/editor/macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 000000000000..227314eeb7de
--- /dev/null
+++ b/platform/android/java/editor/macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/platform/android/java/editor/macrobenchmark/src/main/java/org/godotengine/editor/macrobenchmark/EditorBenchmarks.kt b/platform/android/java/editor/macrobenchmark/src/main/java/org/godotengine/editor/macrobenchmark/EditorBenchmarks.kt
new file mode 100644
index 000000000000..5fbe6e253551
--- /dev/null
+++ b/platform/android/java/editor/macrobenchmark/src/main/java/org/godotengine/editor/macrobenchmark/EditorBenchmarks.kt
@@ -0,0 +1,57 @@
+package org.godotengine.editor.macrobenchmark
+
+import android.Manifest
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.MemoryUsageMetric
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.rule.GrantPermissionRule
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.StaleObjectException
+import androidx.test.uiautomator.Until
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Set of editor macro benchmarks.
+ *
+ * Before running, switch the editor's active build variant to 'benchmark'
+ */
+@RunWith(AndroidJUnit4::class)
+class EditorBenchmarks {
+
+ companion object {
+ const val PACKAGE_NAME = "org.godotengine.editor.v4.benchmark"
+ }
+
+ @get:Rule val benchmarkRule = MacrobenchmarkRule()
+ @get:Rule val grantPermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
+
+ /**
+ * Navigates to the device's home screen, and launches the Project Manager.
+ */
+ @OptIn(ExperimentalMetricApi::class)
+ @Test
+ fun startupProjectManager() = benchmarkRule.measureRepeated(
+ packageName = PACKAGE_NAME,
+ metrics = listOf(
+ StartupTimingMetric(),
+ MemoryUsageMetric(MemoryUsageMetric.Mode.Max),
+ ),
+ iterations = 5,
+ startupMode = StartupMode.COLD
+ ) {
+ pressHome()
+ startActivityAndWait()
+
+ try {
+ val editorLoadingIndicator = device.findObject(By.res(PACKAGE_NAME, "editor_loading_indicator"))
+ editorLoadingIndicator.wait(Until.gone(By.res(PACKAGE_NAME, "editor_loading_indicator")), 5_000)
+ } catch (ignored: StaleObjectException) {}
+ }
+}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
index 13ce53ebbbcd..4da950abccc5 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
@@ -222,9 +222,11 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
}
- // We exclude certain permissions from the set we request at startup, as they'll be
- // requested on demand based on use cases.
- PermissionsUtil.requestManifestPermissions(this, getExcludedPermissions())
+ if (BuildConfig.BUILD_TYPE != "benchmark") {
+ // We exclude certain permissions from the set we request at startup, as they'll be
+ // requested on demand based on use cases.
+ PermissionsUtil.requestManifestPermissions(this, getExcludedPermissions())
+ }
editorMessageDispatcher.parseStartIntent(packageManager, intent)
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 52e90bbada3c..cef7ac3a65fd 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -749,6 +749,7 @@ class Godot(private val context: Context) {
magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
runOnUiThread {
+ getActivity()?.reportFullyDrawn()
registerSensorsIfNeeded()
enableImmersiveMode(useImmersive.get(), true)
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
index 738f27e877c2..8d56f6dcbead 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
@@ -64,7 +64,7 @@ private val benchmarkTracker = Collections.synchronizedMap(LinkedHashMap