You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-21 14:57:09 +00:00
Make use of activity-alias as the launcher mechanism for the Godot editor and the Godot app template
This commit is contained in:
@@ -91,6 +91,17 @@ android {
|
||||
]
|
||||
|
||||
ndk { debugSymbolLevel 'NONE' }
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// The following argument makes the Android Test Orchestrator run its
|
||||
// "pm clear" command after each test invocation. This command ensures
|
||||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
}
|
||||
|
||||
testOptions {
|
||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
}
|
||||
|
||||
base {
|
||||
@@ -196,4 +207,11 @@ dependencies {
|
||||
horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:$versions.openxrVendorsVersion"
|
||||
// Pico dependencies
|
||||
picoosImplementation "org.godotengine:godot-openxr-vendors-pico:$versions.openxrVendorsVersion"
|
||||
|
||||
// Android instrumented test dependencies
|
||||
androidTestImplementation "androidx.test.ext:junit:$versions.junitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCoreVersion"
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$versions.kotlinTestVersion"
|
||||
androidTestImplementation "androidx.test:runner:$versions.testRunnerVersion"
|
||||
androidTestUtil "androidx.test:orchestrator:$versions.testOrchestratorVersion"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/**************************************************************************/
|
||||
/* GodotEditorTest.kt */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
package org.godotengine.editor
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.godotengine.godot.GodotActivity.Companion.EXTRA_COMMAND_LINE_PARAMS
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Instrumented test for the Godot editor.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GodotEditorTest {
|
||||
companion object {
|
||||
private val TAG = GodotEditorTest::class.simpleName
|
||||
|
||||
private val TEST_COMMAND_LINE_PARAMS = arrayOf("This is a test")
|
||||
private const val PROJECT_MANAGER_CLASS_NAME = "org.godotengine.editor.ProjectManager"
|
||||
private const val GODOT_EDITOR_CLASS_NAME = "org.godotengine.editor.GodotEditor"
|
||||
}
|
||||
|
||||
/**
|
||||
* Implicitly launch the project manager.
|
||||
*/
|
||||
@Test
|
||||
fun testImplicitProjectManagerLaunch() {
|
||||
val implicitLaunchIntent = Intent().apply {
|
||||
setPackage(BuildConfig.APPLICATION_ID)
|
||||
action = Intent.ACTION_MAIN
|
||||
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
putExtra(EXTRA_COMMAND_LINE_PARAMS, TEST_COMMAND_LINE_PARAMS)
|
||||
}
|
||||
ActivityScenario.launch<GodotEditor>(implicitLaunchIntent).use { scenario ->
|
||||
scenario.onActivity { activity ->
|
||||
assertEquals(activity.intent.component?.className, PROJECT_MANAGER_CLASS_NAME)
|
||||
|
||||
val commandLineParams = activity.intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
assertNull(commandLineParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly launch the project manager.
|
||||
*/
|
||||
@Test
|
||||
fun testExplicitProjectManagerLaunch() {
|
||||
val explicitProjectManagerIntent = Intent().apply {
|
||||
component = ComponentName(BuildConfig.APPLICATION_ID, PROJECT_MANAGER_CLASS_NAME)
|
||||
putExtra(EXTRA_COMMAND_LINE_PARAMS, TEST_COMMAND_LINE_PARAMS)
|
||||
}
|
||||
ActivityScenario.launch<GodotEditor>(explicitProjectManagerIntent).use { scenario ->
|
||||
scenario.onActivity { activity ->
|
||||
assertEquals(activity.intent.component?.className, PROJECT_MANAGER_CLASS_NAME)
|
||||
|
||||
val commandLineParams = activity.intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
assertNull(commandLineParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly launch the `GodotEditor` activity.
|
||||
*/
|
||||
@Test
|
||||
fun testExplicitGodotEditorLaunch() {
|
||||
val godotEditorIntent = Intent().apply {
|
||||
component = ComponentName(BuildConfig.APPLICATION_ID, GODOT_EDITOR_CLASS_NAME)
|
||||
putExtra(EXTRA_COMMAND_LINE_PARAMS, TEST_COMMAND_LINE_PARAMS)
|
||||
}
|
||||
ActivityScenario.launch<GodotEditor>(godotEditorIntent).use { scenario ->
|
||||
scenario.onActivity { activity ->
|
||||
assertEquals(activity.intent.component?.className, GODOT_EDITOR_CLASS_NAME)
|
||||
|
||||
val commandLineParams = activity.intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
assertNotNull(commandLineParams)
|
||||
assertTrue(commandLineParams.contentEquals(TEST_COMMAND_LINE_PARAMS))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,20 +47,31 @@
|
||||
|
||||
<activity
|
||||
android:name=".GodotEditor"
|
||||
android:exported="true"
|
||||
android:exported="false"
|
||||
android:screenOrientation="landscape"
|
||||
tools:node="merge"
|
||||
tools:replace="android:screenOrientation">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="com.oculus.intent.category.2D" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.oculus.vrshell.free_resizing_lock_aspect_ratio" android:value="true"/>
|
||||
</activity>
|
||||
<activity-alias
|
||||
android:name=".ProjectManager"
|
||||
android:exported="true"
|
||||
tools:node="merge"
|
||||
android:targetActivity=".GodotEditor">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="com.oculus.intent.category.2D" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
<activity
|
||||
android:name=".GodotXRGame"
|
||||
android:exported="false"
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<activity
|
||||
android:name=".GodotEditor"
|
||||
android:configChanges="layoutDirection|locale|orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
|
||||
android:exported="true"
|
||||
android:exported="false"
|
||||
android:icon="@mipmap/themed_icon"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="userLandscape">
|
||||
@@ -54,13 +54,6 @@
|
||||
android:defaultWidth="@dimen/editor_default_window_width"
|
||||
android:defaultHeight="@dimen/editor_default_window_height" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter used to intercept hybrid PANEL launch for the current editor project, and route it
|
||||
properly through the editor 'run' logic (e.g: debugger setup) -->
|
||||
<intent-filter>
|
||||
@@ -77,6 +70,18 @@
|
||||
<category android:name="org.godotengine.xr.hybrid.IMMERSIVE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity-alias
|
||||
android:name=".ProjectManager"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/themed_icon"
|
||||
android:targetActivity=".GodotEditor">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
<activity
|
||||
android:name=".GodotGame"
|
||||
android:configChanges="layoutDirection|locale|orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
|
||||
|
||||
@@ -58,6 +58,7 @@ import org.godotengine.editor.embed.GameMenuFragment
|
||||
import org.godotengine.editor.utils.signApk
|
||||
import org.godotengine.editor.utils.verifyApk
|
||||
import org.godotengine.godot.BuildConfig
|
||||
import org.godotengine.godot.Godot
|
||||
import org.godotengine.godot.GodotActivity
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.editor.utils.EditorUtils
|
||||
@@ -158,6 +159,20 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
internal const val SNACKBAR_SHOW_DURATION_MS = 5000L
|
||||
|
||||
private const val PREF_KEY_DONT_SHOW_GAME_RESUME_HINT = "pref_key_dont_show_game_resume_hint"
|
||||
|
||||
@JvmStatic
|
||||
fun isRunningInInstrumentation(): Boolean {
|
||||
if (BuildConfig.BUILD_TYPE == "release") {
|
||||
return false
|
||||
}
|
||||
|
||||
return try {
|
||||
Class.forName("org.godotengine.editor.GodotEditorTest")
|
||||
true
|
||||
} catch (_: ClassNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val editorMessageDispatcher = EditorMessageDispatcher(this)
|
||||
@@ -229,9 +244,15 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
enableEdgeToEdge()
|
||||
}
|
||||
|
||||
// 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())
|
||||
// Skip permissions request if running in a device farm (e.g. firebase test lab) or if requested via the launch
|
||||
// intent (e.g. instrumentation tests).
|
||||
val skipPermissionsRequest = isRunningInInstrumentation() ||
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ActivityManager.isRunningInUserTestHarness()
|
||||
if (!skipPermissionsRequest) {
|
||||
// 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)
|
||||
|
||||
@@ -247,7 +268,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
|
||||
override fun onNewIntent(newIntent: Intent) {
|
||||
if (newIntent.hasCategory(HYBRID_APP_PANEL_CATEGORY) || newIntent.hasCategory(HYBRID_APP_IMMERSIVE_CATEGORY)) {
|
||||
val params = newIntent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
val params = retrieveCommandLineParamsFromLaunchIntent(newIntent)
|
||||
Log.d(TAG, "Received hybrid transition intent $newIntent with parameters ${params.contentToString()}")
|
||||
// Override EXTRA_NEW_LAUNCH so the editor is not restarted
|
||||
newIntent.putExtra(EXTRA_NEW_LAUNCH, false)
|
||||
@@ -257,7 +278,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
var scene = ""
|
||||
var xrMode = XR_MODE_DEFAULT
|
||||
var path = ""
|
||||
if (params != null) {
|
||||
if (params.isNotEmpty()) {
|
||||
val sceneIndex = params.indexOf(SCENE_ARG)
|
||||
if (sceneIndex != -1 && sceneIndex + 1 < params.size) {
|
||||
scene = params[sceneIndex +1]
|
||||
@@ -511,6 +532,14 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
return editorWindowInfo.windowId
|
||||
}
|
||||
|
||||
override fun onGodotForceQuit(instance: Godot) {
|
||||
if (!isRunningInInstrumentation()) {
|
||||
// For instrumented tests, we disable force-quitting to allow the tests to complete successfully, otherwise
|
||||
// they fail when the process crashes.
|
||||
super.onGodotForceQuit(instance)
|
||||
}
|
||||
}
|
||||
|
||||
final override fun onGodotForceQuit(godotInstanceId: Int): Boolean {
|
||||
val editorWindowInfo = getEditorWindowInfoForInstanceId(godotInstanceId) ?: return super.onGodotForceQuit(godotInstanceId)
|
||||
|
||||
|
||||
@@ -63,20 +63,18 @@ abstract class BaseGodotGame: GodotEditor() {
|
||||
|
||||
// Check if we should be running in XR instead (if available) as it's possible we were
|
||||
// launched from the project manager which doesn't have that information.
|
||||
val launchingArgs = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
if (launchingArgs != null) {
|
||||
val editorWindowInfo = retrieveEditorWindowInfo(launchingArgs, getEditorGameEmbedMode())
|
||||
if (editorWindowInfo != getEditorWindowInfo()) {
|
||||
val relaunchIntent = getNewGodotInstanceIntent(editorWindowInfo, launchingArgs)
|
||||
relaunchIntent.putExtra(EXTRA_NEW_LAUNCH, true)
|
||||
.putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, intent.getBundleExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD))
|
||||
val launchingArgs = retrieveCommandLineParamsFromLaunchIntent()
|
||||
val editorWindowInfo = retrieveEditorWindowInfo(launchingArgs, getEditorGameEmbedMode())
|
||||
if (editorWindowInfo != getEditorWindowInfo()) {
|
||||
val relaunchIntent = getNewGodotInstanceIntent(editorWindowInfo, launchingArgs)
|
||||
relaunchIntent.putExtra(EXTRA_NEW_LAUNCH, true)
|
||||
.putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, intent.getBundleExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD))
|
||||
|
||||
Log.d(TAG, "Relaunching XR project using ${editorWindowInfo.windowClassName} with parameters ${launchingArgs.contentToString()}")
|
||||
Godot.getInstance(applicationContext).destroyAndKillProcess {
|
||||
ProcessPhoenix.triggerRebirth(this, relaunchIntent)
|
||||
}
|
||||
return
|
||||
Log.d(TAG, "Relaunching XR project using ${editorWindowInfo.windowClassName} with parameters ${launchingArgs.contentToString()}")
|
||||
Godot.getInstance(applicationContext).destroyAndKillProcess {
|
||||
ProcessPhoenix.triggerRebirth(this, relaunchIntent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Request project runtime permissions if necessary.
|
||||
|
||||
@@ -16,16 +16,10 @@
|
||||
|
||||
<activity
|
||||
android:name=".GodotEditor"
|
||||
android:exported="true"
|
||||
android:exported="false"
|
||||
android:screenOrientation="landscape"
|
||||
tools:node="merge"
|
||||
tools:replace="android:screenOrientation">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
tools:replace="android:screenOrientation"/>
|
||||
|
||||
<activity
|
||||
android:name=".GodotXRGame"
|
||||
|
||||
Reference in New Issue
Block a user