You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
Merge pull request #102866 from m4gr3d/implement_godot_service
Add support for using an Android Service to host the Godot engine
This commit is contained in:
@@ -208,7 +208,9 @@ android {
|
||||
flavorDimensions 'edition'
|
||||
|
||||
productFlavors {
|
||||
standard {}
|
||||
standard {
|
||||
getIsDefault().set(true)
|
||||
}
|
||||
mono {}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ import android.util.Log;
|
||||
|
||||
import androidx.core.splashscreen.SplashScreen;
|
||||
|
||||
import com.godot.game.BuildConfig;
|
||||
|
||||
/**
|
||||
* Template activity for Godot Android builds.
|
||||
* Feel free to extend and modify this class for your custom logic.
|
||||
|
||||
@@ -33,6 +33,7 @@ package org.godotengine.editor
|
||||
import android.Manifest
|
||||
import android.util.Log
|
||||
import androidx.annotation.CallSuper
|
||||
import org.godotengine.godot.Godot
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
@@ -69,12 +70,7 @@ abstract class BaseGodotGame: GodotEditor() {
|
||||
.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()}")
|
||||
val godot = godot
|
||||
if (godot != null) {
|
||||
godot.destroyAndKillProcess {
|
||||
ProcessPhoenix.triggerRebirth(this, relaunchIntent)
|
||||
}
|
||||
} else {
|
||||
Godot.getInstance(applicationContext).destroyAndKillProcess {
|
||||
ProcessPhoenix.triggerRebirth(this, relaunchIntent)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/remote_godot_window_surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -63,7 +63,6 @@ import org.godotengine.godot.plugin.AndroidRuntimePlugin
|
||||
import org.godotengine.godot.plugin.GodotPlugin
|
||||
import org.godotengine.godot.plugin.GodotPluginRegistry
|
||||
import org.godotengine.godot.tts.GodotTTS
|
||||
import org.godotengine.godot.utils.CommandLineFileParser
|
||||
import org.godotengine.godot.utils.DialogUtils
|
||||
import org.godotengine.godot.utils.GodotNetUtils
|
||||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
@@ -89,54 +88,51 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
* Can be hosted by [Activity], [Fragment] or [Service] android components, so long as its
|
||||
* lifecycle methods are properly invoked.
|
||||
*/
|
||||
class Godot(private val context: Context) {
|
||||
class Godot private constructor(val context: Context) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
private val TAG = Godot::class.java.simpleName
|
||||
|
||||
@Volatile private var INSTANCE: Godot? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(context: Context): Godot {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: Godot(context.applicationContext).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
|
||||
// Supported build flavors
|
||||
const val EDITOR_FLAVOR = "editor"
|
||||
const val TEMPLATE_FLAVOR = "template"
|
||||
private const val EDITOR_FLAVOR = "editor"
|
||||
private const val TEMPLATE_FLAVOR = "template"
|
||||
|
||||
/**
|
||||
* @return true if this is an editor build, false if this is a template build
|
||||
*/
|
||||
fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR
|
||||
internal fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR
|
||||
}
|
||||
|
||||
private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
private val mClipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
private val vibratorService: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
|
||||
private val pluginRegistry: GodotPluginRegistry by lazy {
|
||||
GodotPluginRegistry.getPluginRegistry()
|
||||
}
|
||||
private val mSensorManager: SensorManager? by lazy { context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager }
|
||||
private val mClipboard: ClipboardManager? by lazy { context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager }
|
||||
private val vibratorService: Vibrator? by lazy { context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator }
|
||||
private val pluginRegistry: GodotPluginRegistry by lazy { GodotPluginRegistry.getPluginRegistry() }
|
||||
|
||||
private val accelerometerEnabled = AtomicBoolean(false)
|
||||
private val mAccelerometer: Sensor? by lazy {
|
||||
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
||||
}
|
||||
private val mAccelerometer: Sensor? by lazy { mSensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) }
|
||||
|
||||
private val gravityEnabled = AtomicBoolean(false)
|
||||
private val mGravity: Sensor? by lazy {
|
||||
mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
|
||||
}
|
||||
private val mGravity: Sensor? by lazy { mSensorManager?.getDefaultSensor(Sensor.TYPE_GRAVITY) }
|
||||
|
||||
private val magnetometerEnabled = AtomicBoolean(false)
|
||||
private val mMagnetometer: Sensor? by lazy {
|
||||
mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
|
||||
}
|
||||
private val mMagnetometer: Sensor? by lazy { mSensorManager?.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) }
|
||||
|
||||
private val gyroscopeEnabled = AtomicBoolean(false)
|
||||
private val mGyroscope: Sensor? by lazy {
|
||||
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||
}
|
||||
private val mGyroscope: Sensor? by lazy { mSensorManager?.getDefaultSensor(Sensor.TYPE_GYROSCOPE) }
|
||||
|
||||
val tts = GodotTTS(context)
|
||||
val directoryAccessHandler = DirectoryAccessHandler(context)
|
||||
val fileAccessHandler = FileAccessHandler(context)
|
||||
val netUtils = GodotNetUtils(context)
|
||||
private val commandLineFileParser = CommandLineFileParser()
|
||||
private val godotInputHandler = GodotInputHandler(context, this)
|
||||
|
||||
/**
|
||||
@@ -144,11 +140,6 @@ class Godot(private val context: Context) {
|
||||
*/
|
||||
private val runOnTerminate = AtomicReference<Runnable>()
|
||||
|
||||
/**
|
||||
* Tracks whether [onCreate] was completed successfully.
|
||||
*/
|
||||
private var initializationStarted = false
|
||||
|
||||
/**
|
||||
* Tracks whether [GodotLib.initialize] was completed successfully.
|
||||
*/
|
||||
@@ -176,17 +167,15 @@ class Godot(private val context: Context) {
|
||||
*/
|
||||
private val godotMainLoopStarted = AtomicBoolean(false)
|
||||
|
||||
var io: GodotIO? = null
|
||||
val io = GodotIO(this)
|
||||
|
||||
private var commandLine : MutableList<String> = ArrayList<String>()
|
||||
private var xrMode = XRMode.REGULAR
|
||||
private var expansionPackPath: String = ""
|
||||
private var useApkExpansion = false
|
||||
private val useImmersive = AtomicBoolean(false)
|
||||
private var useDebugOpengl = false
|
||||
private var darkMode = false
|
||||
|
||||
private var containerLayout: FrameLayout? = null
|
||||
internal var containerLayout: FrameLayout? = null
|
||||
var renderView: GodotRenderView? = null
|
||||
|
||||
/**
|
||||
@@ -197,52 +186,45 @@ class Godot(private val context: Context) {
|
||||
/**
|
||||
* Returns true if the engine has been initialized, false otherwise.
|
||||
*/
|
||||
fun isInitialized() = initializationStarted && isNativeInitialized() && renderViewInitialized
|
||||
fun isInitialized() = primaryHost != null && isNativeInitialized() && renderViewInitialized
|
||||
|
||||
/**
|
||||
* Provides access to the primary host [Activity]
|
||||
*/
|
||||
fun getActivity() = primaryHost?.activity
|
||||
private fun requireActivity() = getActivity() ?: throw IllegalStateException("Host activity must be non-null")
|
||||
|
||||
/**
|
||||
* Start initialization of the Godot engine.
|
||||
*
|
||||
* This must be followed by [onInitNativeLayer] and [onInitRenderView] in that order to complete
|
||||
* initialization of the engine.
|
||||
* This must be followed by [onInitRenderView] to complete initialization of the engine.
|
||||
*
|
||||
* @return false if initialization of the native layer fails, true otherwise.
|
||||
*
|
||||
* @throws IllegalArgumentException exception if the specified expansion pack (if any)
|
||||
* is invalid.
|
||||
*/
|
||||
fun onCreate(primaryHost: GodotHost) {
|
||||
if (this.primaryHost != null || initializationStarted) {
|
||||
Log.d(TAG, "OnCreate already invoked")
|
||||
return
|
||||
fun initEngine(commandLineParams: List<String>, hostPlugins: Set<GodotPlugin>): Boolean {
|
||||
if (isNativeInitialized()) {
|
||||
Log.d(TAG, "Engine already initialized")
|
||||
return true
|
||||
}
|
||||
|
||||
Log.v(TAG, "OnCreate: $primaryHost")
|
||||
Log.v(TAG, "InitEngine with params: $commandLineParams")
|
||||
|
||||
darkMode = context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
beginBenchmarkMeasure("Startup", "Godot::onCreate")
|
||||
beginBenchmarkMeasure("Startup", "Godot::initEngine")
|
||||
try {
|
||||
this.primaryHost = primaryHost
|
||||
val activity = requireActivity()
|
||||
val window = activity.window
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
||||
|
||||
Log.v(TAG, "Initializing Godot plugin registry")
|
||||
val runtimePlugins = mutableSetOf<GodotPlugin>(AndroidRuntimePlugin(this))
|
||||
runtimePlugins.addAll(primaryHost.getHostPlugins(this))
|
||||
runtimePlugins.addAll(hostPlugins)
|
||||
GodotPluginRegistry.initializePluginRegistry(this, runtimePlugins)
|
||||
if (io == null) {
|
||||
io = GodotIO(activity)
|
||||
}
|
||||
|
||||
// check for apk expansion API
|
||||
commandLine = getCommandLine()
|
||||
commandLine.addAll(commandLineParams)
|
||||
var mainPackMd5: String? = null
|
||||
var mainPackKey: String? = null
|
||||
var useApkExpansion = false
|
||||
val newArgs: MutableList<String> = ArrayList()
|
||||
var i = 0
|
||||
while (i < commandLine.size) {
|
||||
@@ -263,7 +245,7 @@ class Godot(private val context: Context) {
|
||||
i++
|
||||
} else if (hasExtra && commandLine[i] == "--apk_expansion_key") {
|
||||
mainPackKey = commandLine[i + 1]
|
||||
val prefs = activity.getSharedPreferences(
|
||||
val prefs = context.getSharedPreferences(
|
||||
"app_data_keys",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
@@ -288,15 +270,17 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
var expansionPackPath = ""
|
||||
commandLine = if (newArgs.isEmpty()) { mutableListOf() } else { newArgs }
|
||||
if (useApkExpansion && mainPackMd5 != null && mainPackKey != null) {
|
||||
// Build the full path to the app's expansion files
|
||||
try {
|
||||
expansionPackPath = Helpers.getSaveFilePath(context)
|
||||
expansionPackPath += "/main." + activity.packageManager.getPackageInfo(
|
||||
activity.packageName,
|
||||
expansionPackPath += "/main." + context.packageManager.getPackageInfo(
|
||||
context.packageName,
|
||||
0
|
||||
).versionCode + "." + activity.packageName + ".obb"
|
||||
).versionCode + "." + context.packageName + ".obb"
|
||||
} catch (e: java.lang.Exception) {
|
||||
Log.e(TAG, "Unable to build full path to the app's expansion files", e)
|
||||
}
|
||||
@@ -317,15 +301,35 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
initializationStarted = true
|
||||
} catch (e: java.lang.Exception) {
|
||||
// Clear the primary host and rethrow
|
||||
this.primaryHost = null
|
||||
initializationStarted = false
|
||||
throw e
|
||||
} finally {
|
||||
endBenchmarkMeasure("Startup", "Godot::onCreate")
|
||||
if (expansionPackPath.isNotEmpty()) {
|
||||
commandLine.add("--main-pack")
|
||||
commandLine.add(expansionPackPath)
|
||||
}
|
||||
if (!nativeLayerInitializeCompleted) {
|
||||
nativeLayerInitializeCompleted = GodotLib.initialize(
|
||||
this,
|
||||
context.assets,
|
||||
io,
|
||||
netUtils,
|
||||
directoryAccessHandler,
|
||||
fileAccessHandler,
|
||||
useApkExpansion,
|
||||
)
|
||||
Log.v(TAG, "Godot native layer initialization completed: $nativeLayerInitializeCompleted")
|
||||
}
|
||||
|
||||
if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) {
|
||||
nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts)
|
||||
if (!nativeLayerSetupCompleted) {
|
||||
throw IllegalStateException("Unable to setup the Godot engine! Aborting...")
|
||||
} else {
|
||||
Log.v(TAG, "Godot native layer setup completed")
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
endBenchmarkMeasure("Startup", "Godot::initEngine")
|
||||
}
|
||||
return isNativeInitialized()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +372,7 @@ class Godot(private val context: Context) {
|
||||
*/
|
||||
@Keep
|
||||
private fun nativeEnableImmersiveMode(enabled: Boolean) {
|
||||
runOnUiThread {
|
||||
runOnHostThread {
|
||||
enableImmersiveMode(enabled)
|
||||
}
|
||||
}
|
||||
@@ -376,103 +380,51 @@ class Godot(private val context: Context) {
|
||||
@Keep
|
||||
fun isInImmersiveMode() = useImmersive.get()
|
||||
|
||||
/**
|
||||
* Initializes the native layer of the Godot engine.
|
||||
*
|
||||
* This must be preceded by [onCreate] and followed by [onInitRenderView] to complete
|
||||
* initialization of the engine.
|
||||
*
|
||||
* @return false if initialization of the native layer fails, true otherwise.
|
||||
*
|
||||
* @throws IllegalStateException if [onCreate] has not been called.
|
||||
*/
|
||||
fun onInitNativeLayer(host: GodotHost): Boolean {
|
||||
if (!initializationStarted) {
|
||||
throw IllegalStateException("OnCreate must be invoked successfully prior to initializing the native layer")
|
||||
}
|
||||
if (isNativeInitialized()) {
|
||||
Log.d(TAG, "OnInitNativeLayer already invoked")
|
||||
return true
|
||||
}
|
||||
if (host != primaryHost) {
|
||||
Log.e(TAG, "Native initialization is only supported for the primary host")
|
||||
return false
|
||||
}
|
||||
|
||||
Log.v(TAG, "OnInitNativeLayer: $host")
|
||||
|
||||
beginBenchmarkMeasure("Startup", "Godot::onInitNativeLayer")
|
||||
try {
|
||||
if (expansionPackPath.isNotEmpty()) {
|
||||
commandLine.add("--main-pack")
|
||||
commandLine.add(expansionPackPath)
|
||||
}
|
||||
val activity = requireActivity()
|
||||
if (!nativeLayerInitializeCompleted) {
|
||||
nativeLayerInitializeCompleted = GodotLib.initialize(
|
||||
activity,
|
||||
this,
|
||||
activity.assets,
|
||||
io,
|
||||
netUtils,
|
||||
directoryAccessHandler,
|
||||
fileAccessHandler,
|
||||
useApkExpansion,
|
||||
)
|
||||
Log.v(TAG, "Godot native layer initialization completed: $nativeLayerInitializeCompleted")
|
||||
}
|
||||
|
||||
if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) {
|
||||
nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts)
|
||||
if (!nativeLayerSetupCompleted) {
|
||||
throw IllegalStateException("Unable to setup the Godot engine! Aborting...")
|
||||
} else {
|
||||
Log.v(TAG, "Godot native layer setup completed")
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
endBenchmarkMeasure("Startup", "Godot::onInitNativeLayer")
|
||||
}
|
||||
return isNativeInitialized()
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to complete initialization of the view used by the engine for rendering.
|
||||
*
|
||||
* This must be preceded by [onCreate] and [onInitNativeLayer] in that order to properly
|
||||
* initialize the engine.
|
||||
* This must be preceded by [initEngine] to properly initialize the engine.
|
||||
*
|
||||
* @param host The [GodotHost] that's initializing the render views
|
||||
* @param providedContainerLayout Optional argument; if provided, this is reused to host the Godot's render views
|
||||
*
|
||||
* @return A [FrameLayout] instance containing Godot's render views if initialization is successful, null otherwise.
|
||||
*
|
||||
* @throws IllegalStateException if [onInitNativeLayer] has not been called
|
||||
* @throws IllegalStateException if [initEngine] has not been called
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun onInitRenderView(host: GodotHost, providedContainerLayout: FrameLayout = FrameLayout(host.activity)): FrameLayout? {
|
||||
fun onInitRenderView(host: GodotHost, providedContainerLayout: FrameLayout = FrameLayout(context)): FrameLayout? {
|
||||
if (!isNativeInitialized()) {
|
||||
throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view")
|
||||
throw IllegalStateException("initEngine(...) must be invoked successfully prior to initializing the render view")
|
||||
}
|
||||
|
||||
Log.v(TAG, "OnInitRenderView: $host")
|
||||
|
||||
beginBenchmarkMeasure("Startup", "Godot::onInitRenderView")
|
||||
Log.v(TAG, "OnInitRenderView: $host")
|
||||
try {
|
||||
val activity: Activity = host.activity
|
||||
this.primaryHost = host
|
||||
getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
||||
|
||||
if (containerLayout != null) {
|
||||
assert(renderViewInitialized)
|
||||
return containerLayout
|
||||
}
|
||||
|
||||
containerLayout = providedContainerLayout
|
||||
containerLayout?.removeAllViews()
|
||||
containerLayout?.layoutParams = ViewGroup.LayoutParams(
|
||||
val layoutParams = containerLayout?.layoutParams ?: ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
containerLayout?.layoutParams = layoutParams
|
||||
|
||||
// GodotEditText layout
|
||||
val editText = GodotEditText(activity)
|
||||
val editText = GodotEditText(context)
|
||||
editText.layoutParams =
|
||||
ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
activity.resources.getDimension(R.dimen.text_edit_height).toInt()
|
||||
context.resources.getDimension(R.dimen.text_edit_height).toInt()
|
||||
)
|
||||
// Prevent GodotEditText from showing on splash screen on devices with Android 14 or newer.
|
||||
editText.setBackgroundColor(Color.TRANSPARENT)
|
||||
@@ -484,25 +436,22 @@ class Godot(private val context: Context) {
|
||||
!isProjectManagerHint() && !isEditorHint() && java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/per_pixel_transparency/allowed"))
|
||||
Log.d(TAG, "Render view should be transparent: $shouldBeTransparent")
|
||||
renderView = if (usesVulkan()) {
|
||||
if (meetsVulkanRequirements(activity.packageManager)) {
|
||||
GodotVulkanRenderView(host, this, godotInputHandler, shouldBeTransparent)
|
||||
if (meetsVulkanRequirements(context.packageManager)) {
|
||||
GodotVulkanRenderView(this, godotInputHandler, shouldBeTransparent)
|
||||
} else if (canFallbackToOpenGL()) {
|
||||
// Fallback to OpenGl.
|
||||
GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
|
||||
GodotGLRenderView(this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
|
||||
} else {
|
||||
throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message))
|
||||
throw IllegalStateException(context.getString(R.string.error_missing_vulkan_requirements_message))
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback to OpenGl.
|
||||
GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
|
||||
}
|
||||
|
||||
if (host == primaryHost) {
|
||||
renderView?.startRenderer()
|
||||
GodotGLRenderView(this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
|
||||
}
|
||||
|
||||
renderView?.let {
|
||||
it.startRenderer()
|
||||
containerLayout?.addView(
|
||||
it.view,
|
||||
ViewGroup.LayoutParams(
|
||||
@@ -513,20 +462,21 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
|
||||
editText.setView(renderView)
|
||||
io?.setEdit(editText)
|
||||
io.setEdit(editText)
|
||||
|
||||
val activity = host.activity
|
||||
// Listeners for keyboard height.
|
||||
val decorView = activity.window.decorView
|
||||
val topView = activity?.window?.decorView ?: providedContainerLayout
|
||||
// Report the height of virtual keyboard as it changes during the animation.
|
||||
ViewCompat.setWindowInsetsAnimationCallback(decorView, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
||||
ViewCompat.setWindowInsetsAnimationCallback(topView, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
||||
var startBottom = 0
|
||||
var endBottom = 0
|
||||
override fun onPrepare(animation: WindowInsetsAnimationCompat) {
|
||||
startBottom = ViewCompat.getRootWindowInsets(decorView)?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
|
||||
startBottom = ViewCompat.getRootWindowInsets(topView)?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
|
||||
}
|
||||
|
||||
override fun onStart(animation: WindowInsetsAnimationCompat, bounds: WindowInsetsAnimationCompat.BoundsCompat): WindowInsetsAnimationCompat.BoundsCompat {
|
||||
endBottom = ViewCompat.getRootWindowInsets(decorView)?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
|
||||
endBottom = ViewCompat.getRootWindowInsets(topView)?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
|
||||
return bounds
|
||||
}
|
||||
|
||||
@@ -553,7 +503,6 @@ class Godot(private val context: Context) {
|
||||
override fun onEnd(animation: WindowInsetsAnimationCompat) {}
|
||||
})
|
||||
|
||||
if (host == primaryHost) {
|
||||
renderView?.queueOnRenderThread {
|
||||
for (plugin in pluginRegistry.allPlugins) {
|
||||
plugin.onRegisterPluginWithGodotNative()
|
||||
@@ -572,7 +521,6 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
renderViewInitialized = true
|
||||
} finally {
|
||||
if (!renderViewInitialized) {
|
||||
@@ -615,16 +563,16 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
|
||||
if (accelerometerEnabled.get() && mAccelerometer != null) {
|
||||
mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
|
||||
mSensorManager?.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
|
||||
}
|
||||
if (gravityEnabled.get() && mGravity != null) {
|
||||
mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME)
|
||||
mSensorManager?.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME)
|
||||
}
|
||||
if (magnetometerEnabled.get() && mMagnetometer != null) {
|
||||
mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
|
||||
mSensorManager?.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
|
||||
}
|
||||
if (gyroscopeEnabled.get() && mGyroscope != null) {
|
||||
mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
|
||||
mSensorManager?.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,7 +584,7 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
|
||||
renderView?.onActivityPaused()
|
||||
mSensorManager.unregisterListener(godotInputHandler)
|
||||
mSensorManager?.unregisterListener(godotInputHandler)
|
||||
for (plugin in pluginRegistry.allPlugins) {
|
||||
plugin.onMainPause()
|
||||
}
|
||||
@@ -652,16 +600,17 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
|
||||
fun onDestroy(primaryHost: GodotHost) {
|
||||
Log.v(TAG, "OnDestroy: $primaryHost")
|
||||
if (this.primaryHost != primaryHost) {
|
||||
return
|
||||
}
|
||||
Log.v(TAG, "OnDestroy: $primaryHost")
|
||||
|
||||
for (plugin in pluginRegistry.allPlugins) {
|
||||
plugin.onMainDestroy()
|
||||
}
|
||||
|
||||
renderView?.onActivityDestroyed()
|
||||
this.primaryHost = null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -721,7 +670,7 @@ class Godot(private val context: Context) {
|
||||
val overrideVolumeButtons = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/override_volume_buttons"))
|
||||
val scrollDeadzoneDisabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/disable_scroll_deadzone"))
|
||||
|
||||
runOnUiThread {
|
||||
runOnHostThread {
|
||||
renderView?.inputHandler?.apply {
|
||||
enableLongPress(longPressEnabled)
|
||||
enablePanningAndScalingGestures(panScaleEnabled)
|
||||
@@ -753,7 +702,7 @@ class Godot(private val context: Context) {
|
||||
gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
|
||||
magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
|
||||
|
||||
runOnUiThread {
|
||||
runOnHostThread {
|
||||
registerSensorsIfNeeded()
|
||||
enableImmersiveMode(useImmersive.get(), true)
|
||||
}
|
||||
@@ -782,15 +731,15 @@ class Godot(private val context: Context) {
|
||||
@StringRes titleResId: Int,
|
||||
okCallback: Runnable?
|
||||
) {
|
||||
val res: Resources = getActivity()?.resources ?: return
|
||||
val res: Resources = context.resources ?: return
|
||||
alert(res.getString(messageResId), res.getString(titleResId), okCallback)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
@Keep
|
||||
fun alert(message: String, title: String, okCallback: Runnable? = null) {
|
||||
val activity: Activity = getActivity() ?: return
|
||||
runOnUiThread {
|
||||
val activity = getActivity() ?: return
|
||||
runOnHostThread {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.setMessage(message).setTitle(title)
|
||||
builder.setPositiveButton(
|
||||
@@ -814,14 +763,10 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified action on the UI thread.
|
||||
* If the current thread is the UI thread, then the action is executed immediately.
|
||||
* If the current thread is not the UI thread, the action is posted to the event queue
|
||||
* of the UI thread.
|
||||
* Runs the specified action on the host thread.
|
||||
*/
|
||||
fun runOnUiThread(action: Runnable) {
|
||||
val activity: Activity = getActivity() ?: return
|
||||
activity.runOnUiThread(action)
|
||||
fun runOnHostThread(action: Runnable) {
|
||||
primaryHost?.runOnHostThread(action)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -838,7 +783,7 @@ class Godot(private val context: Context) {
|
||||
var renderingDevice = rendererInfo[0]
|
||||
var rendererSource = "ProjectSettings"
|
||||
var renderer = rendererInfo[1]
|
||||
val cmdline = getCommandLine()
|
||||
val cmdline = commandLine
|
||||
var index = cmdline.indexOf("--rendering-method")
|
||||
if (index > -1 && cmdline.size > index + 1) {
|
||||
rendererSource = "CommandLine"
|
||||
@@ -880,7 +825,7 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
|
||||
private fun setKeepScreenOn(enabled: Boolean) {
|
||||
runOnUiThread {
|
||||
runOnHostThread {
|
||||
if (enabled) {
|
||||
getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
} else {
|
||||
@@ -905,19 +850,21 @@ class Godot(private val context: Context) {
|
||||
return darkMode
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun hasClipboard(): Boolean {
|
||||
return mClipboard.hasPrimaryClip()
|
||||
return mClipboard?.hasPrimaryClip() == true
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun getClipboard(): String {
|
||||
val clipData = mClipboard.primaryClip ?: return ""
|
||||
val clipData = mClipboard?.primaryClip ?: return ""
|
||||
val text = clipData.getItemAt(0).text ?: return ""
|
||||
return text.toString()
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun setClipboard(text: String?) {
|
||||
val clip = ClipData.newPlainText("myLabel", text)
|
||||
mClipboard.setPrimaryClip(clip)
|
||||
mClipboard?.setPrimaryClip(ClipData.newPlainText("myLabel", text))
|
||||
}
|
||||
|
||||
@Keep
|
||||
@@ -971,8 +918,7 @@ class Godot(private val context: Context) {
|
||||
@JvmOverloads
|
||||
fun destroyAndKillProcess(destroyRunnable: Runnable? = null) {
|
||||
val host = primaryHost
|
||||
val activity = host?.activity
|
||||
if (host == null || activity == null) {
|
||||
if (host == null) {
|
||||
// Run the destroyRunnable right away as we are about to force quit.
|
||||
destroyRunnable?.run()
|
||||
|
||||
@@ -984,7 +930,7 @@ class Godot(private val context: Context) {
|
||||
// Store the destroyRunnable so it can be run when the engine is terminating
|
||||
runOnTerminate.set(destroyRunnable)
|
||||
|
||||
runOnUiThread {
|
||||
runOnHostThread {
|
||||
onDestroy(host)
|
||||
}
|
||||
}
|
||||
@@ -1019,14 +965,14 @@ class Godot(private val context: Context) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (amplitude <= -1) {
|
||||
vibratorService.vibrate(
|
||||
vibratorService?.vibrate(
|
||||
VibrationEffect.createOneShot(
|
||||
durationMs.toLong(),
|
||||
VibrationEffect.DEFAULT_AMPLITUDE
|
||||
)
|
||||
)
|
||||
} else {
|
||||
vibratorService.vibrate(
|
||||
vibratorService?.vibrate(
|
||||
VibrationEffect.createOneShot(
|
||||
durationMs.toLong(),
|
||||
amplitude
|
||||
@@ -1035,7 +981,7 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
} else {
|
||||
// deprecated in API 26
|
||||
vibratorService.vibrate(durationMs.toLong())
|
||||
vibratorService?.vibrate(durationMs.toLong())
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.w(TAG, "SecurityException: VIBRATE permission not found. Make sure it is declared in the manifest or enabled in the export preset.")
|
||||
@@ -1043,21 +989,6 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCommandLine(): MutableList<String> {
|
||||
val commandLine = try {
|
||||
commandLineFileParser.parseCommandLine(requireActivity().assets.open("_cl_"))
|
||||
} catch (ignored: Exception) {
|
||||
mutableListOf()
|
||||
}
|
||||
|
||||
val hostCommandLine = primaryHost?.commandLine
|
||||
if (!hostCommandLine.isNullOrEmpty()) {
|
||||
commandLine.addAll(hostCommandLine)
|
||||
}
|
||||
|
||||
return commandLine
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the native code (java_godot_wrapper.h) to access the input fallback mapping.
|
||||
* @return The input fallback mapping for the current XR mode.
|
||||
@@ -1077,7 +1008,7 @@ class Godot(private val context: Context) {
|
||||
}
|
||||
|
||||
fun getGrantedPermissions(): Array<String?>? {
|
||||
return PermissionsUtil.getGrantedPermissions(getActivity())
|
||||
return PermissionsUtil.getGrantedPermissions(context)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,7 @@ import android.util.Log
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import org.godotengine.godot.utils.CommandLineFileParser
|
||||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
|
||||
@@ -73,6 +74,13 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
|
||||
|
||||
@CallSuper
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val assetsCommandLine = try {
|
||||
CommandLineFileParser.parseCommandLine(assets.open("_cl_"))
|
||||
} catch (ignored: Exception) {
|
||||
mutableListOf()
|
||||
}
|
||||
commandLineParams.addAll(assetsCommandLine)
|
||||
|
||||
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}")
|
||||
commandLineParams.addAll(params ?: emptyArray())
|
||||
@@ -107,12 +115,7 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
|
||||
|
||||
protected fun triggerRebirth(bundle: Bundle?, intent: Intent) {
|
||||
// Launch a new activity
|
||||
val godot = godot
|
||||
if (godot != null) {
|
||||
godot.destroyAndKillProcess {
|
||||
ProcessPhoenix.triggerRebirth(this, bundle, intent)
|
||||
}
|
||||
} else {
|
||||
Godot.getInstance(applicationContext).destroyAndKillProcess {
|
||||
ProcessPhoenix.triggerRebirth(this, bundle, intent)
|
||||
}
|
||||
}
|
||||
@@ -159,8 +162,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
|
||||
intent = newIntent
|
||||
|
||||
handleStartIntent(newIntent, false)
|
||||
|
||||
godotFragment?.onNewIntent(newIntent)
|
||||
}
|
||||
|
||||
private fun handleStartIntent(intent: Intent, newLaunch: Boolean) {
|
||||
@@ -215,5 +216,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
|
||||
return GodotFragment()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun getCommandLine(): MutableList<String> = commandLineParams
|
||||
}
|
||||
|
||||
@@ -89,26 +89,14 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
|
||||
private View mCellMessage;
|
||||
|
||||
private Button mPauseButton;
|
||||
private Button mWiFiSettingsButton;
|
||||
|
||||
private FrameLayout godotContainerLayout;
|
||||
private boolean mStatePaused;
|
||||
private int mState;
|
||||
|
||||
@Nullable
|
||||
private GodotHost parentHost;
|
||||
private Godot godot;
|
||||
|
||||
static private Intent mCurrentIntent;
|
||||
|
||||
public void onNewIntent(Intent intent) {
|
||||
mCurrentIntent = intent;
|
||||
}
|
||||
|
||||
static public Intent getCurrentIntent() {
|
||||
return mCurrentIntent;
|
||||
}
|
||||
|
||||
private void setState(int newState) {
|
||||
if (mState != newState) {
|
||||
mState = newState;
|
||||
@@ -117,16 +105,10 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
|
||||
}
|
||||
|
||||
private void setButtonPausedState(boolean paused) {
|
||||
mStatePaused = paused;
|
||||
int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause;
|
||||
mPauseButton.setText(stringResourceID);
|
||||
}
|
||||
|
||||
public interface ResultCallback {
|
||||
void callback(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
public ResultCallback resultCallback;
|
||||
|
||||
@Override
|
||||
public Godot getGodot() {
|
||||
return godot;
|
||||
@@ -159,11 +141,6 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCallback != null) {
|
||||
resultCallback.callback(requestCode, resultCode, data);
|
||||
resultCallback = null;
|
||||
}
|
||||
|
||||
godot.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@@ -185,14 +162,11 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
|
||||
BenchmarkUtils.beginBenchmarkMeasure("Startup", "GodotFragment::onCreate");
|
||||
super.onCreate(icicle);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
mCurrentIntent = activity.getIntent();
|
||||
|
||||
if (parentHost != null) {
|
||||
godot = parentHost.getGodot();
|
||||
}
|
||||
if (godot == null) {
|
||||
godot = new Godot(requireContext());
|
||||
godot = Godot.getInstance(requireContext());
|
||||
}
|
||||
performEngineInitialization();
|
||||
BenchmarkUtils.endBenchmarkMeasure("Startup", "GodotFragment::onCreate");
|
||||
@@ -200,10 +174,8 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
|
||||
|
||||
private void performEngineInitialization() {
|
||||
try {
|
||||
godot.onCreate(this);
|
||||
|
||||
if (!godot.onInitNativeLayer(this)) {
|
||||
throw new IllegalStateException("Unable to initialize engine native layer");
|
||||
if (!godot.initEngine(getCommandLine(), getHostPlugins(godot))) {
|
||||
throw new IllegalStateException("Unable to initialize Godot engine");
|
||||
}
|
||||
|
||||
godotContainerLayout = godot.onInitRenderView(this);
|
||||
@@ -257,7 +229,6 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
|
||||
mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard);
|
||||
mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular);
|
||||
mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton);
|
||||
mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton);
|
||||
|
||||
return downloadingExpansionView;
|
||||
}
|
||||
|
||||
@@ -76,16 +76,14 @@ import java.io.InputStream;
|
||||
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
|
||||
*/
|
||||
class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
|
||||
private final GodotHost host;
|
||||
private final Godot godot;
|
||||
private final GodotInputHandler inputHandler;
|
||||
private final GodotRenderer godotRenderer;
|
||||
private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
|
||||
|
||||
public GodotGLRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl, boolean shouldBeTranslucent) {
|
||||
super(host.getActivity());
|
||||
public GodotGLRenderView(Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl, boolean shouldBeTranslucent) {
|
||||
super(godot.getContext());
|
||||
|
||||
this.host = host;
|
||||
this.godot = godot;
|
||||
this.inputHandler = inputHandler;
|
||||
this.godotRenderer = new GodotRenderer();
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.godotengine.godot.plugin.GodotPlugin;
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -96,8 +97,9 @@ public interface GodotHost {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to the Activity hosting the {@link Godot} engine.
|
||||
* Provide access to the Activity hosting the {@link Godot} engine if any.
|
||||
*/
|
||||
@Nullable
|
||||
Activity getActivity();
|
||||
|
||||
/**
|
||||
@@ -150,4 +152,18 @@ public interface GodotHost {
|
||||
* Invoked on the render thread when an editor workspace has been selected.
|
||||
*/
|
||||
default void onEditorWorkspaceSelected(String workspace) {}
|
||||
|
||||
/**
|
||||
* Runs the specified action on a host provided thread.
|
||||
*/
|
||||
default void runOnHostThread(Runnable action) {
|
||||
if (action == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
@@ -62,7 +63,8 @@ import java.util.Locale;
|
||||
public class GodotIO {
|
||||
private static final String TAG = GodotIO.class.getSimpleName();
|
||||
|
||||
private final Activity activity;
|
||||
private final Godot godot;
|
||||
|
||||
private final String uniqueId;
|
||||
GodotEditText edit;
|
||||
|
||||
@@ -74,9 +76,9 @@ public class GodotIO {
|
||||
final int SCREEN_SENSOR_PORTRAIT = 5;
|
||||
final int SCREEN_SENSOR = 6;
|
||||
|
||||
GodotIO(Activity p_activity) {
|
||||
activity = p_activity;
|
||||
String androidId = Settings.Secure.getString(activity.getContentResolver(),
|
||||
GodotIO(Godot godot) {
|
||||
this.godot = godot;
|
||||
String androidId = Settings.Secure.getString(godot.getContext().getContentResolver(),
|
||||
Settings.Secure.ANDROID_ID);
|
||||
if (androidId == null) {
|
||||
androidId = "";
|
||||
@@ -85,12 +87,22 @@ public class GodotIO {
|
||||
uniqueId = androidId;
|
||||
}
|
||||
|
||||
private Context getContext() {
|
||||
Context context = godot.getActivity();
|
||||
if (context == null) {
|
||||
context = godot.getContext();
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// MISCELLANEOUS OS IO
|
||||
/////////////////////////
|
||||
|
||||
public int openURI(String uriString) {
|
||||
try {
|
||||
Context context = getContext();
|
||||
|
||||
Uri dataUri;
|
||||
String dataType = "";
|
||||
boolean grantReadUriPermission = false;
|
||||
@@ -104,14 +116,14 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
File targetFile = new File(filePath);
|
||||
dataUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", targetFile);
|
||||
dataType = activity.getContentResolver().getType(dataUri);
|
||||
dataUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", targetFile);
|
||||
dataType = context.getContentResolver().getType(dataUri);
|
||||
} else {
|
||||
dataUri = Uri.parse(uriString);
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setAction(Intent.ACTION_VIEW).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (TextUtils.isEmpty(dataType)) {
|
||||
intent.setData(dataUri);
|
||||
} else {
|
||||
@@ -121,7 +133,7 @@ public class GodotIO {
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
|
||||
activity.startActivity(intent);
|
||||
context.startActivity(intent);
|
||||
return Error.OK.toNativeValue();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to open uri " + uriString, e);
|
||||
@@ -130,7 +142,7 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
public String getCacheDir() {
|
||||
return activity.getCacheDir().getAbsolutePath();
|
||||
return getContext().getCacheDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getTempDir() {
|
||||
@@ -146,7 +158,7 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
public String getDataDir() {
|
||||
return activity.getFilesDir().getAbsolutePath();
|
||||
return getContext().getFilesDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
@@ -158,14 +170,14 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
public int getScreenDPI() {
|
||||
return activity.getResources().getDisplayMetrics().densityDpi;
|
||||
return getContext().getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bucketized density values.
|
||||
*/
|
||||
public float getScaledDensity() {
|
||||
int densityDpi = activity.getResources().getDisplayMetrics().densityDpi;
|
||||
int densityDpi = getContext().getResources().getDisplayMetrics().densityDpi;
|
||||
float selectedScaledDensity;
|
||||
if (densityDpi >= DisplayMetrics.DENSITY_XXXHIGH) {
|
||||
selectedScaledDensity = 4.0f;
|
||||
@@ -184,7 +196,15 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
public double getScreenRefreshRate(double fallback) {
|
||||
Display display = activity.getWindowManager().getDefaultDisplay();
|
||||
Activity activity = godot.getActivity();
|
||||
|
||||
Display display = null;
|
||||
if (activity != null) {
|
||||
display = activity.getWindowManager().getDefaultDisplay();
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
display = godot.getContext().getDisplay();
|
||||
}
|
||||
|
||||
if (display != null) {
|
||||
return display.getRefreshRate();
|
||||
}
|
||||
@@ -193,11 +213,24 @@ public class GodotIO {
|
||||
|
||||
public int[] getDisplaySafeArea() {
|
||||
Rect rect = new Rect();
|
||||
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
|
||||
int[] result = new int[4];
|
||||
|
||||
View topView = null;
|
||||
if (godot.getActivity() != null) {
|
||||
topView = godot.getActivity().getWindow().getDecorView();
|
||||
} else if (godot.getRenderView() != null) {
|
||||
topView = godot.getRenderView().getView();
|
||||
}
|
||||
|
||||
if (topView != null) {
|
||||
topView.getWindowVisibleDisplayFrame(rect);
|
||||
result[0] = rect.left;
|
||||
result[1] = rect.top;
|
||||
result[2] = rect.right;
|
||||
result[3] = rect.bottom;
|
||||
|
||||
int[] result = { rect.left, rect.top, rect.right, rect.bottom };
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets();
|
||||
WindowInsets insets = topView.getRootWindowInsets();
|
||||
DisplayCutout cutout = insets.getDisplayCutout();
|
||||
if (cutout != null) {
|
||||
int insetLeft = cutout.getSafeInsetLeft();
|
||||
@@ -208,15 +241,29 @@ public class GodotIO {
|
||||
result[3] -= insetTop + cutout.getSafeInsetBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] getDisplayCutouts() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
return new int[0];
|
||||
DisplayCutout cutout = activity.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
|
||||
if (cutout == null)
|
||||
}
|
||||
|
||||
View topView = null;
|
||||
if (godot.getActivity() != null) {
|
||||
topView = godot.getActivity().getWindow().getDecorView();
|
||||
} else if (godot.getRenderView() != null) {
|
||||
topView = godot.getRenderView().getView();
|
||||
}
|
||||
|
||||
if (topView == null) {
|
||||
return new int[0];
|
||||
}
|
||||
DisplayCutout cutout = topView.getRootWindowInsets().getDisplayCutout();
|
||||
if (cutout == null) {
|
||||
return new int[0];
|
||||
}
|
||||
List<Rect> rects = cutout.getBoundingRects();
|
||||
int cutouts = rects.size();
|
||||
int[] result = new int[cutouts * 4];
|
||||
@@ -242,9 +289,6 @@ public class GodotIO {
|
||||
if (edit != null) {
|
||||
edit.showKeyboard(p_existing_text, GodotEditText.VirtualKeyboardType.values()[p_type], p_max_input_length, p_cursor_start, p_cursor_end);
|
||||
}
|
||||
|
||||
//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||
}
|
||||
|
||||
public void hideKeyboard() {
|
||||
@@ -253,6 +297,11 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
public void setScreenOrientation(int p_orientation) {
|
||||
final Activity activity = godot.getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (p_orientation) {
|
||||
case SCREEN_LANDSCAPE: {
|
||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
@@ -279,6 +328,11 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
public int getScreenOrientation() {
|
||||
final Activity activity = godot.getActivity();
|
||||
if (activity == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int orientation = activity.getRequestedOrientation();
|
||||
switch (orientation) {
|
||||
case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
|
||||
@@ -310,8 +364,17 @@ public class GodotIO {
|
||||
}
|
||||
|
||||
public int getDisplayRotation() {
|
||||
WindowManager windowManager = (WindowManager)activity.getSystemService(Context.WINDOW_SERVICE);
|
||||
int rotation = windowManager.getDefaultDisplay().getRotation();
|
||||
Activity activity = godot.getActivity();
|
||||
|
||||
Display display = null;
|
||||
if (activity != null) {
|
||||
display = activity.getWindowManager().getDefaultDisplay();
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
display = godot.getContext().getDisplay();
|
||||
}
|
||||
|
||||
if (display != null) {
|
||||
int rotation = display.getRotation();
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
return 90;
|
||||
} else if (rotation == Surface.ROTATION_180) {
|
||||
@@ -319,6 +382,7 @@ public class GodotIO {
|
||||
} else if (rotation == Surface.ROTATION_270) {
|
||||
return 270;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -382,7 +446,7 @@ public class GodotIO {
|
||||
return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
|
||||
}
|
||||
} else {
|
||||
return activity.getExternalFilesDir(what).getAbsolutePath();
|
||||
return getContext().getExternalFilesDir(what).getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class GodotLib {
|
||||
/**
|
||||
* Invoked on the main thread to initialize Godot native layer.
|
||||
*/
|
||||
public static native boolean initialize(Activity activity,
|
||||
public static native boolean initialize(
|
||||
Godot p_instance,
|
||||
AssetManager p_asset_manager,
|
||||
GodotIO godotIO,
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.godotengine.godot
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Godot service responsible for hosting the Godot engine instance.
|
||||
*
|
||||
* Note: Still in development, so it's made private and inaccessible until completed.
|
||||
*/
|
||||
private class GodotService : Service() {
|
||||
|
||||
companion object {
|
||||
private val TAG = GodotService::class.java.simpleName
|
||||
}
|
||||
|
||||
private var boundIntent: Intent? = null
|
||||
private val godot by lazy {
|
||||
Godot(applicationContext)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
if (boundIntent != null) {
|
||||
Log.d(TAG, "GodotService already bound")
|
||||
return null
|
||||
}
|
||||
|
||||
boundIntent = intent
|
||||
return GodotHandle(godot)
|
||||
}
|
||||
|
||||
override fun onRebind(intent: Intent?) {
|
||||
super.onRebind(intent)
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
super.onTaskRemoved(rootIntent)
|
||||
}
|
||||
|
||||
class GodotHandle(val godot: Godot) : Binder()
|
||||
}
|
||||
@@ -51,16 +51,14 @@ import androidx.annotation.Keep;
|
||||
import java.io.InputStream;
|
||||
|
||||
class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
|
||||
private final GodotHost host;
|
||||
private final Godot godot;
|
||||
private final GodotInputHandler mInputHandler;
|
||||
private final VkRenderer mRenderer;
|
||||
private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
|
||||
|
||||
public GodotVulkanRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, boolean shouldBeTranslucent) {
|
||||
super(host.getActivity());
|
||||
public GodotVulkanRenderView(Godot godot, GodotInputHandler inputHandler, boolean shouldBeTranslucent) {
|
||||
super(godot.getContext());
|
||||
|
||||
this.host = host;
|
||||
this.godot = godot;
|
||||
mInputHandler = inputHandler;
|
||||
mRenderer = new VkRenderer();
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.godotengine.godot.BuildConfig;
|
||||
import org.godotengine.godot.Godot;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@@ -46,10 +47,8 @@ import androidx.annotation.Nullable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -109,6 +108,13 @@ public abstract class GodotPlugin {
|
||||
return godot.getActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the {@link Context}.
|
||||
*/
|
||||
protected Context getContext() {
|
||||
return godot.getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the plugin with Godot native code.
|
||||
* <p>
|
||||
@@ -179,7 +185,7 @@ public abstract class GodotPlugin {
|
||||
* @return the plugin's view to be included; null if no views should be included.
|
||||
*/
|
||||
@Nullable
|
||||
public View onMainCreate(Activity activity) {
|
||||
public View onMainCreate(@Nullable Activity activity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -323,14 +329,24 @@ public abstract class GodotPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified action on the UI thread. If the current thread is the UI
|
||||
* thread, then the action is executed immediately. If the current thread is
|
||||
* not the UI thread, the action is posted to the event queue of the UI thread.
|
||||
* Runs the specified action on the host thread.
|
||||
*
|
||||
* @param action the action to run on the UI thread
|
||||
* @param action the action to run on the host thread
|
||||
*
|
||||
* @deprecated Use the {@link GodotPlugin#runOnHostThread} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected void runOnUiThread(Runnable action) {
|
||||
godot.runOnUiThread(action);
|
||||
runOnHostThread(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified action on the host thread.
|
||||
*
|
||||
* @param action the action to run on the host thread
|
||||
*/
|
||||
protected void runOnHostThread(Runnable action) {
|
||||
godot.runOnHostThread(action);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,7 @@ package org.godotengine.godot.plugin;
|
||||
|
||||
import org.godotengine.godot.Godot;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
@@ -134,10 +134,10 @@ public final class GodotPluginRegistry {
|
||||
|
||||
// Register the manifest plugins
|
||||
try {
|
||||
final Activity activity = godot.getActivity();
|
||||
ApplicationInfo appInfo = activity
|
||||
final Context context = godot.getContext();
|
||||
ApplicationInfo appInfo = context
|
||||
.getPackageManager()
|
||||
.getApplicationInfo(activity.getPackageName(),
|
||||
.getApplicationInfo(context.getPackageName(),
|
||||
PackageManager.GET_META_DATA);
|
||||
Bundle metaData = appInfo.metaData;
|
||||
if (metaData == null || metaData.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
/**************************************************************************/
|
||||
/* GodotService.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.godot.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.os.Process
|
||||
import android.os.RemoteException
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.SurfaceControlViewHost
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.os.bundleOf
|
||||
import org.godotengine.godot.Godot
|
||||
import org.godotengine.godot.GodotHost
|
||||
import org.godotengine.godot.R
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Specialized [Service] implementation able to host a Godot engine instance.
|
||||
*
|
||||
* When used remotely (from another process), this component lacks access to an [android.app.Activity]
|
||||
* instance, and as such it does not have full access to the set of Godot UI capabilities.
|
||||
*
|
||||
* Limitations: As of version 4.5, use of vulkan + swappy causes [GodotService] to crash as swappy requires an Activity
|
||||
* context. So [GodotService] should be used with OpenGL or with Vulkan with swappy disabled.
|
||||
*/
|
||||
open class GodotService : Service() {
|
||||
|
||||
companion object {
|
||||
private val TAG = GodotService::class.java.simpleName
|
||||
|
||||
const val EXTRA_MSG_PAYLOAD = "extraMsgPayload"
|
||||
|
||||
// Keys to store / retrieve msg payloads
|
||||
const val KEY_COMMAND_LINE_PARAMETERS = "commandLineParameters"
|
||||
const val KEY_HOST_TOKEN = "hostToken"
|
||||
const val KEY_DISPLAY_ID = "displayId"
|
||||
const val KEY_WIDTH = "width"
|
||||
const val KEY_HEIGHT = "height"
|
||||
const val KEY_SURFACE_PACKAGE = "surfacePackage"
|
||||
const val KEY_ENGINE_STATUS = "engineStatus"
|
||||
const val KEY_ENGINE_ERROR = "engineError"
|
||||
|
||||
// Set of commands from the client to the service
|
||||
const val MSG_INIT_ENGINE = 0
|
||||
const val MSG_START_ENGINE = MSG_INIT_ENGINE + 1
|
||||
const val MSG_STOP_ENGINE = MSG_START_ENGINE + 1
|
||||
const val MSG_DESTROY_ENGINE = MSG_STOP_ENGINE + 1
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
const val MSG_WRAP_ENGINE_WITH_SCVH = MSG_DESTROY_ENGINE + 1
|
||||
|
||||
// Set of commands from the service to the client
|
||||
const val MSG_ENGINE_ERROR = 100
|
||||
const val MSG_ENGINE_STATUS_UPDATE = 101
|
||||
const val MSG_ENGINE_RESTART_REQUESTED = 102
|
||||
}
|
||||
|
||||
enum class EngineStatus {
|
||||
INITIALIZED,
|
||||
SCVH_CREATED,
|
||||
STARTED,
|
||||
STOPPED,
|
||||
DESTROYED,
|
||||
}
|
||||
|
||||
enum class EngineError {
|
||||
ALREADY_BOUND,
|
||||
INIT_FAILED,
|
||||
SCVH_CREATION_FAILED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to subscribe to engine's updates.
|
||||
*/
|
||||
private class RemoteListener(val handlerRef: WeakReference<IncomingHandler>, val replyTo: Messenger) {
|
||||
fun onEngineError(error: EngineError, extras: Bundle? = null) {
|
||||
try {
|
||||
replyTo.send(Message.obtain().apply {
|
||||
what = MSG_ENGINE_ERROR
|
||||
data.putString(KEY_ENGINE_ERROR, error.name)
|
||||
if (extras != null && !extras.isEmpty) {
|
||||
data.putAll(extras)
|
||||
}
|
||||
})
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to send engine error", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun onEngineStatusUpdate(status: EngineStatus, extras: Bundle? = null) {
|
||||
try {
|
||||
replyTo.send(Message.obtain().apply {
|
||||
what = MSG_ENGINE_STATUS_UPDATE
|
||||
data.putString(KEY_ENGINE_STATUS, status.name)
|
||||
if (extras != null && !extras.isEmpty) {
|
||||
data.putAll(extras)
|
||||
}
|
||||
})
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to send engine status update", e)
|
||||
}
|
||||
|
||||
if (status == EngineStatus.DESTROYED) {
|
||||
val handler = handlerRef.get() ?: return
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && handler.viewHost != null) {
|
||||
Log.d(TAG, "Releasing SurfaceControlViewHost")
|
||||
handler.viewHost?.release()
|
||||
handler.viewHost = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEngineRestartRequested() {
|
||||
try {
|
||||
replyTo.send(Message.obtain(null, MSG_ENGINE_RESTART_REQUESTED))
|
||||
} catch (e: RemoteException) {
|
||||
Log.w(TAG, "Unable to send restart request", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler of incoming messages from remote clients.
|
||||
*/
|
||||
private class IncomingHandler(private val serviceRef: WeakReference<GodotService>) : Handler() {
|
||||
|
||||
var viewHost: SurfaceControlViewHost? = null
|
||||
|
||||
override fun handleMessage(msg: Message) {
|
||||
val service = serviceRef.get() ?: return
|
||||
|
||||
Log.d(TAG, "HandleMessage: $msg")
|
||||
|
||||
if (msg.replyTo == null) {
|
||||
// Messages for this handler must have a valid 'replyTo' field
|
||||
super.handleMessage(msg)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val serviceListener = service.listener
|
||||
if (serviceListener == null) {
|
||||
service.listener = RemoteListener(WeakReference(this), msg.replyTo)
|
||||
} else if (serviceListener.replyTo != msg.replyTo) {
|
||||
Log.e(TAG, "Engine is already bound to another client")
|
||||
msg.replyTo.send(Message.obtain().apply {
|
||||
what = MSG_ENGINE_ERROR
|
||||
data.putString(KEY_ENGINE_ERROR, EngineError.ALREADY_BOUND.name)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
when (msg.what) {
|
||||
MSG_INIT_ENGINE -> service.initEngine(msg.data.getStringArray(KEY_COMMAND_LINE_PARAMETERS))
|
||||
|
||||
MSG_START_ENGINE -> service.startEngine()
|
||||
|
||||
MSG_STOP_ENGINE -> service.stopEngine()
|
||||
|
||||
MSG_DESTROY_ENGINE -> service.destroyEngine()
|
||||
|
||||
MSG_WRAP_ENGINE_WITH_SCVH -> {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
Log.e(TAG, "SDK version is less than the minimum required (${Build.VERSION_CODES.R})")
|
||||
service.listener?.onEngineError(EngineError.SCVH_CREATION_FAILED)
|
||||
return
|
||||
}
|
||||
|
||||
var currentViewHost = viewHost
|
||||
if (currentViewHost != null) {
|
||||
Log.i(TAG, "Attached Godot engine to SurfaceControlViewHost")
|
||||
service.listener?.onEngineStatusUpdate(
|
||||
EngineStatus.SCVH_CREATED,
|
||||
bundleOf(KEY_SURFACE_PACKAGE to currentViewHost.surfacePackage)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val msgData = msg.data
|
||||
if (msgData.isEmpty) {
|
||||
Log.e(TAG, "Invalid message data from binding client.. Aborting")
|
||||
service.listener?.onEngineError(EngineError.SCVH_CREATION_FAILED)
|
||||
return
|
||||
}
|
||||
|
||||
val godotContainerLayout = service.godot.containerLayout
|
||||
if (godotContainerLayout == null) {
|
||||
Log.e(TAG, "Invalid godot layout.. Aborting")
|
||||
service.listener?.onEngineError(EngineError.SCVH_CREATION_FAILED)
|
||||
return
|
||||
}
|
||||
|
||||
val hostToken = msgData.getBinder(KEY_HOST_TOKEN)
|
||||
val width = msgData.getInt(KEY_WIDTH)
|
||||
val height = msgData.getInt(KEY_HEIGHT)
|
||||
val displayId = msgData.getInt(KEY_DISPLAY_ID)
|
||||
val display = service.getSystemService(DisplayManager::class.java)
|
||||
.getDisplay(displayId)
|
||||
|
||||
Log.d(TAG, "Setting up SurfaceControlViewHost")
|
||||
currentViewHost = SurfaceControlViewHost(service, display, hostToken).apply {
|
||||
setView(godotContainerLayout, width, height)
|
||||
|
||||
Log.i(TAG, "Attached Godot engine to SurfaceControlViewHost")
|
||||
service.listener?.onEngineStatusUpdate(
|
||||
EngineStatus.SCVH_CREATED,
|
||||
bundleOf(KEY_SURFACE_PACKAGE to surfacePackage)
|
||||
)
|
||||
}
|
||||
viewHost = currentViewHost
|
||||
}
|
||||
|
||||
else -> super.handleMessage(msg)
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to handle message", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class GodotServiceHost : GodotHost {
|
||||
override fun getActivity() = null
|
||||
override fun getGodot() = this@GodotService.godot
|
||||
override fun getCommandLine() = commandLineParams
|
||||
|
||||
override fun runOnHostThread(action: Runnable) {
|
||||
if (Thread.currentThread() != handler.looper.thread) {
|
||||
handler.post(action)
|
||||
} else {
|
||||
action.run()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGodotForceQuit(instance: Godot) {
|
||||
if (instance === godot) {
|
||||
Log.d(TAG, "Force quitting Godot service")
|
||||
forceQuitService()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGodotRestartRequested(instance: Godot) {
|
||||
if (instance === godot) {
|
||||
Log.d(TAG, "Restarting Godot service")
|
||||
listener?.onEngineRestartRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val commandLineParams = ArrayList<String>()
|
||||
private val handler = IncomingHandler(WeakReference(this))
|
||||
private val messenger = Messenger(handler)
|
||||
private val godotHost = GodotServiceHost()
|
||||
|
||||
private val godot: Godot by lazy { Godot.getInstance(applicationContext) }
|
||||
private var listener: RemoteListener? = null
|
||||
|
||||
override fun onCreate() {
|
||||
Log.d(TAG, "OnCreate")
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
// Dispatch the start payload to the incoming handler
|
||||
Log.d(TAG, "Processing start command $intent")
|
||||
val msg = intent?.getParcelableExtra<Message>(EXTRA_MSG_PAYLOAD)
|
||||
if (msg != null) {
|
||||
handler.sendMessage(msg)
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
protected open fun updateCommandLineParams(args: List<String>) {
|
||||
// Update the list of command line params with the new args
|
||||
commandLineParams.clear()
|
||||
if (args.isNotEmpty()) {
|
||||
commandLineParams.addAll(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun performEngineInitialization(): Boolean {
|
||||
Log.d(TAG, "Performing engine initialization")
|
||||
try {
|
||||
// Initialize the Godot instance
|
||||
if (!godot.initEngine(godotHost.commandLine, godotHost.getHostPlugins(godot))) {
|
||||
throw IllegalStateException("Unable to initialize Godot engine layer")
|
||||
}
|
||||
|
||||
if (godot.onInitRenderView(godotHost) == null) {
|
||||
throw IllegalStateException("Unable to initialize engine render view")
|
||||
}
|
||||
return true
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.e(TAG, "Engine initialization failed", e)
|
||||
val errorMessage = if (TextUtils.isEmpty(e.message)
|
||||
) {
|
||||
getString(R.string.error_engine_setup_message)
|
||||
} else {
|
||||
e.message!!
|
||||
}
|
||||
godot.alert(errorMessage, getString(R.string.text_error_title)) { godot.destroyAndKillProcess() }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "OnDestroy")
|
||||
super.onDestroy()
|
||||
destroyEngine()
|
||||
}
|
||||
|
||||
private fun forceQuitService() {
|
||||
Log.d(TAG, "Force quitting service")
|
||||
stopSelf()
|
||||
Process.killProcess(Process.myPid())
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = messenger.binder
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
stopEngine()
|
||||
return false
|
||||
}
|
||||
|
||||
private fun initEngine(args: Array<String>?): FrameLayout? {
|
||||
if (!godot.isInitialized()) {
|
||||
if (!args.isNullOrEmpty()) {
|
||||
updateCommandLineParams(args.asList())
|
||||
}
|
||||
|
||||
if (!performEngineInitialization()) {
|
||||
Log.e(TAG, "Unable to initialize Godot engine")
|
||||
return null
|
||||
} else {
|
||||
Log.i(TAG, "Engine initialization complete!")
|
||||
}
|
||||
}
|
||||
val godotContainerLayout = godot.containerLayout
|
||||
if (godotContainerLayout == null) {
|
||||
listener?.onEngineError(EngineError.INIT_FAILED)
|
||||
} else {
|
||||
Log.i(TAG, "Initialized Godot engine")
|
||||
listener?.onEngineStatusUpdate(EngineStatus.INITIALIZED)
|
||||
}
|
||||
|
||||
return godotContainerLayout
|
||||
}
|
||||
|
||||
private fun startEngine() {
|
||||
if (!godot.isInitialized()) {
|
||||
Log.e(TAG, "Attempting to start uninitialized Godot engine instance")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Starting Godot engine")
|
||||
godot.onStart(godotHost)
|
||||
godot.onResume(godotHost)
|
||||
|
||||
listener?.onEngineStatusUpdate(EngineStatus.STARTED)
|
||||
}
|
||||
|
||||
private fun stopEngine() {
|
||||
if (!godot.isInitialized()) {
|
||||
Log.e(TAG, "Attempting to stop uninitialized Godot engine instance")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Stopping Godot engine")
|
||||
godot.onPause(godotHost)
|
||||
godot.onStop(godotHost)
|
||||
|
||||
listener?.onEngineStatusUpdate(EngineStatus.STOPPED)
|
||||
}
|
||||
|
||||
private fun destroyEngine() {
|
||||
if (!godot.isInitialized()) {
|
||||
return
|
||||
}
|
||||
|
||||
godot.onDestroy(godotHost)
|
||||
|
||||
listener?.onEngineStatusUpdate(EngineStatus.DESTROYED)
|
||||
listener = null
|
||||
forceQuitService()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
/**************************************************************************/
|
||||
/* RemoteGodotFragment.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.godot.service
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.SurfaceControlViewHost
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.godotengine.godot.GodotHost
|
||||
import org.godotengine.godot.R
|
||||
import org.godotengine.godot.service.GodotService.EngineStatus.*
|
||||
import org.godotengine.godot.service.GodotService.EngineError.*
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Godot [Fragment] component showcasing how to drive rendering from another process using a [GodotService] instance.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
class RemoteGodotFragment: Fragment() {
|
||||
|
||||
companion object {
|
||||
internal val TAG = RemoteGodotFragment::class.java.simpleName
|
||||
}
|
||||
|
||||
/**
|
||||
* Target we publish for receiving messages from the service.
|
||||
*/
|
||||
private val messengerForReply = Messenger(IncomingHandler(WeakReference<RemoteGodotFragment>(this)))
|
||||
|
||||
/**
|
||||
* Messenger for sending messages to the [GodotService] implementation.
|
||||
*/
|
||||
private var serviceMessenger: Messenger? = null
|
||||
|
||||
private var remoteSurface : SurfaceView? = null
|
||||
|
||||
private var engineInitialized = false
|
||||
private var fragmentStarted = false
|
||||
private var serviceBound = false
|
||||
private var remoteGameArgs = arrayOf<String>()
|
||||
|
||||
private var godotHost : GodotHost? = null
|
||||
|
||||
private val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
Log.d(TAG, "Connected to service $name")
|
||||
serviceMessenger = Messenger(service)
|
||||
|
||||
// Initialize the Godot engine
|
||||
initGodotEngine()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
Log.d(TAG, "Disconnected from service $name")
|
||||
serviceMessenger = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler of incoming messages from [GodotService] implementations.
|
||||
*/
|
||||
private class IncomingHandler(private val fragmentRef: WeakReference<RemoteGodotFragment>) : Handler() {
|
||||
|
||||
override fun handleMessage(msg: Message) {
|
||||
val fragment = fragmentRef.get() ?: return
|
||||
|
||||
try {
|
||||
Log.d(TAG, "HandleMessage: $msg")
|
||||
|
||||
when (msg.what) {
|
||||
GodotService.MSG_ENGINE_STATUS_UPDATE -> {
|
||||
try {
|
||||
val engineStatus = GodotService.EngineStatus.valueOf(
|
||||
msg.data.getString(GodotService.KEY_ENGINE_STATUS, "")
|
||||
)
|
||||
Log.d(TAG, "Received engine status $engineStatus")
|
||||
|
||||
when (engineStatus) {
|
||||
INITIALIZED -> {
|
||||
Log.d(TAG, "Engine initialized!")
|
||||
|
||||
try {
|
||||
Log.i(TAG, "Creating SurfaceControlViewHost...")
|
||||
fragment.remoteSurface?.let {
|
||||
fragment.serviceMessenger?.send(Message.obtain().apply {
|
||||
what = GodotService.MSG_WRAP_ENGINE_WITH_SCVH
|
||||
data.apply {
|
||||
putBinder(GodotService.KEY_HOST_TOKEN, it.hostToken)
|
||||
putInt(GodotService.KEY_DISPLAY_ID, it.display.displayId)
|
||||
putInt(GodotService.KEY_WIDTH, it.width)
|
||||
putInt(GodotService.KEY_HEIGHT, it.height)
|
||||
}
|
||||
replyTo = fragment.messengerForReply
|
||||
})
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to set up SurfaceControlViewHost", e)
|
||||
}
|
||||
}
|
||||
|
||||
STARTED -> {
|
||||
Log.d(TAG, "Engine started!")
|
||||
}
|
||||
|
||||
STOPPED -> {
|
||||
Log.d(TAG, "Engine stopped!")
|
||||
}
|
||||
|
||||
DESTROYED -> {
|
||||
Log.d(TAG, "Engine destroyed!")
|
||||
fragment.engineInitialized = false
|
||||
}
|
||||
|
||||
SCVH_CREATED -> {
|
||||
Log.d(TAG, "SurfaceControlViewHost created!")
|
||||
|
||||
val surfacePackage = msg.data.getParcelable<SurfaceControlViewHost.SurfacePackage>(
|
||||
GodotService.KEY_SURFACE_PACKAGE)
|
||||
if (surfacePackage == null) {
|
||||
Log.e(TAG, "Unable to retrieve surface package from GodotService")
|
||||
} else {
|
||||
fragment.remoteSurface?.setChildSurfacePackage(surfacePackage)
|
||||
fragment.engineInitialized = true
|
||||
fragment.startGodotEngine()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to retrieve engine status update from $msg")
|
||||
}
|
||||
}
|
||||
|
||||
GodotService.MSG_ENGINE_ERROR -> {
|
||||
try {
|
||||
val engineError = GodotService.EngineError.valueOf(
|
||||
msg.data.getString(GodotService.KEY_ENGINE_ERROR, "")
|
||||
)
|
||||
Log.d(TAG, "Received engine error $engineError")
|
||||
|
||||
when (engineError) {
|
||||
ALREADY_BOUND -> {
|
||||
// Engine is already connected to another client, unbind for now
|
||||
fragment.stopRemoteGame(false)
|
||||
}
|
||||
|
||||
INIT_FAILED -> {
|
||||
Log.e(TAG, "Engine initialization failed")
|
||||
}
|
||||
|
||||
SCVH_CREATION_FAILED -> {
|
||||
Log.e(TAG, "SurfaceControlViewHost creation failed")
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to retrieve engine error from message $msg", e)
|
||||
}
|
||||
}
|
||||
|
||||
GodotService.MSG_ENGINE_RESTART_REQUESTED -> {
|
||||
Log.d(TAG, "Engine restart requested")
|
||||
// Validate the engine is actually running
|
||||
if (!fragment.serviceBound || !fragment.engineInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve the current game args since stopping the engine will clear them out
|
||||
val currentArgs = fragment.remoteGameArgs
|
||||
|
||||
// Stop the engine
|
||||
fragment.stopRemoteGame()
|
||||
|
||||
// Restart the engine
|
||||
fragment.startRemoteGame(currentArgs)
|
||||
}
|
||||
|
||||
else -> super.handleMessage(msg)
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to handle message $msg", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
val parentActivity = activity
|
||||
if (parentActivity is GodotHost) {
|
||||
godotHost = parentActivity
|
||||
} else {
|
||||
val parentFragment = parentFragment
|
||||
if (parentFragment is GodotHost) {
|
||||
godotHost = parentFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
super.onDetach()
|
||||
godotHost = null
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, bundle: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.remote_godot_fragment_layout, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, bundle: Bundle?) {
|
||||
super.onViewCreated(view, bundle)
|
||||
remoteSurface = view.findViewById(R.id.remote_godot_window_surface)
|
||||
remoteSurface?.setZOrderOnTop(false)
|
||||
|
||||
initGodotEngine()
|
||||
}
|
||||
|
||||
fun startRemoteGame(args: Array<String>) {
|
||||
Log.d(TAG, "Starting remote game with args: ${args.contentToString()}")
|
||||
remoteSurface?.setZOrderOnTop(true)
|
||||
remoteGameArgs = args
|
||||
context?.bindService(
|
||||
Intent(context, GodotService::class.java),
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
serviceBound = true
|
||||
}
|
||||
|
||||
fun stopRemoteGame(destroyEngine: Boolean = true) {
|
||||
Log.d(TAG, "Stopping remote game")
|
||||
remoteSurface?.setZOrderOnTop(false)
|
||||
remoteGameArgs = arrayOf()
|
||||
|
||||
if (serviceBound) {
|
||||
if (destroyEngine) {
|
||||
serviceMessenger?.send(Message.obtain().apply {
|
||||
what = GodotService.MSG_DESTROY_ENGINE
|
||||
replyTo = messengerForReply
|
||||
})
|
||||
}
|
||||
context?.unbindService(serviceConnection)
|
||||
serviceBound = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun initGodotEngine() {
|
||||
if (!serviceBound) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
serviceMessenger?.send(Message.obtain().apply {
|
||||
what = GodotService.MSG_INIT_ENGINE
|
||||
data.apply {
|
||||
putStringArray(GodotService.KEY_COMMAND_LINE_PARAMETERS, remoteGameArgs)
|
||||
}
|
||||
replyTo = messengerForReply
|
||||
})
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to initialize Godot engine", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startGodotEngine() {
|
||||
if (!serviceBound || !engineInitialized || !fragmentStarted) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
serviceMessenger?.send(Message.obtain().apply {
|
||||
what = GodotService.MSG_START_ENGINE
|
||||
replyTo = messengerForReply
|
||||
})
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to start Godot engine", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopGodotEngine() {
|
||||
if (!serviceBound || !engineInitialized || fragmentStarted) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
serviceMessenger?.send(Message.obtain().apply {
|
||||
what = GodotService.MSG_STOP_ENGINE
|
||||
replyTo = messengerForReply
|
||||
})
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Unable to stop Godot engine", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
fragmentStarted = true
|
||||
startGodotEngine()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
fragmentStarted = false
|
||||
stopGodotEngine()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopRemoteGame()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ import java.util.ArrayList
|
||||
*
|
||||
* Returns a mutable list of command lines
|
||||
*/
|
||||
internal class CommandLineFileParser {
|
||||
internal object CommandLineFileParser {
|
||||
fun parseCommandLine(inputStream: InputStream): MutableList<String> {
|
||||
return try {
|
||||
val headerBytes = ByteArray(4)
|
||||
|
||||
@@ -140,12 +140,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion) {
|
||||
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion) {
|
||||
JavaVM *jvm;
|
||||
env->GetJavaVM(&jvm);
|
||||
|
||||
// create our wrapper classes
|
||||
godot_java = new GodotJavaWrapper(env, p_activity, p_godot_instance);
|
||||
godot_java = new GodotJavaWrapper(env, p_godot_instance);
|
||||
godot_io_java = new GodotIOJavaWrapper(env, p_godot_io);
|
||||
|
||||
init_thread_jandroid(jvm, env);
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
// These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code.
|
||||
// See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names)
|
||||
extern "C" {
|
||||
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion);
|
||||
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz);
|
||||
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts);
|
||||
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height);
|
||||
|
||||
@@ -37,9 +37,8 @@
|
||||
|
||||
// TODO we could probably create a base class for this...
|
||||
|
||||
GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance) {
|
||||
GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
|
||||
godot_instance = p_env->NewGlobalRef(p_godot_instance);
|
||||
activity = p_env->NewGlobalRef(p_activity);
|
||||
|
||||
// get info about our Godot class so we can get pointers and stuff...
|
||||
godot_class = p_env->FindClass("org/godotengine/godot/Godot");
|
||||
@@ -49,13 +48,6 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
|
||||
// this is a pretty serious fail.. bail... pointers will stay 0
|
||||
return;
|
||||
}
|
||||
activity_class = p_env->FindClass("android/app/Activity");
|
||||
if (activity_class) {
|
||||
activity_class = (jclass)p_env->NewGlobalRef(activity_class);
|
||||
} else {
|
||||
// this is a pretty serious fail.. bail... pointers will stay 0
|
||||
return;
|
||||
}
|
||||
|
||||
// get some Godot method pointers...
|
||||
_restart = p_env->GetMethodID(godot_class, "restart", "()V");
|
||||
@@ -94,6 +86,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
|
||||
_enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V");
|
||||
_is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z");
|
||||
_on_editor_workspace_selected = p_env->GetMethodID(godot_class, "nativeOnEditorWorkspaceSelected", "(Ljava/lang/String;)V");
|
||||
_get_activity = p_env->GetMethodID(godot_class, "getActivity", "()Landroid/app/Activity;");
|
||||
}
|
||||
|
||||
GodotJavaWrapper::~GodotJavaWrapper() {
|
||||
@@ -105,13 +98,17 @@ GodotJavaWrapper::~GodotJavaWrapper() {
|
||||
ERR_FAIL_NULL(env);
|
||||
env->DeleteGlobalRef(godot_instance);
|
||||
env->DeleteGlobalRef(godot_class);
|
||||
env->DeleteGlobalRef(activity);
|
||||
env->DeleteGlobalRef(activity_class);
|
||||
}
|
||||
|
||||
jobject GodotJavaWrapper::get_activity() {
|
||||
if (_get_activity) {
|
||||
JNIEnv *env = get_jni_env();
|
||||
ERR_FAIL_NULL_V(env, nullptr);
|
||||
jobject activity = env->CallObjectMethod(godot_instance, _get_activity);
|
||||
return activity;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() {
|
||||
if (godot_view != nullptr) {
|
||||
|
||||
@@ -42,9 +42,7 @@
|
||||
class GodotJavaWrapper {
|
||||
private:
|
||||
jobject godot_instance;
|
||||
jobject activity;
|
||||
jclass godot_class;
|
||||
jclass activity_class;
|
||||
|
||||
GodotJavaViewWrapper *godot_view = nullptr;
|
||||
|
||||
@@ -84,9 +82,10 @@ private:
|
||||
jmethodID _enable_immersive_mode = nullptr;
|
||||
jmethodID _is_in_immersive_mode = nullptr;
|
||||
jmethodID _on_editor_workspace_selected = nullptr;
|
||||
jmethodID _get_activity = nullptr;
|
||||
|
||||
public:
|
||||
GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
|
||||
GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance);
|
||||
~GodotJavaWrapper();
|
||||
|
||||
jobject get_activity();
|
||||
|
||||
Reference in New Issue
Block a user